diff --git a/app/layout.tsx b/app/layout.tsx index a2dc810..b60402c 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,20 +3,29 @@ import type { ReactNode } from "react"; import "./globals.css"; export const metadata: Metadata = { - title: "hidden11", - description: "Daily football lineup puzzle with evolving difficulty, sharper clues and shareable results.", + title: { + default: "hidden11", + template: "%s | hidden11", + }, + description: "Daily football lineup puzzle with sharper clues, harder challenges and shareable results.", metadataBase: new URL("https://hidden11.app"), + applicationName: "hidden11", + alternates: { + canonical: "https://hidden11.app", + }, + keywords: ["football puzzle", "soccer puzzle", "daily game", "lineup puzzle", "hidden11"], icons: { icon: "/favicon.svg", }, openGraph: { title: "hidden11", - description: "Daily football lineup puzzle. Beat Easy, Medium and Hard, then share the card.", + description: "Beat Easy, Medium and Hard in the daily football lineup puzzle and share the result.", url: "https://hidden11.app", siteName: "hidden11", + locale: "en_US", images: [ { - url: "/opengraph-image", + url: "https://hidden11.app/opengraph-image", width: 1200, height: 630, alt: "hidden11 daily football puzzle", @@ -27,8 +36,8 @@ export const metadata: Metadata = { twitter: { card: "summary_large_image", title: "hidden11", - description: "Daily football lineup puzzle. Beat Easy, Medium and Hard, then share the card.", - images: ["/twitter-image"], + description: "Beat Easy, Medium and Hard in the daily football lineup puzzle and share the result.", + images: ["https://hidden11.app/twitter-image"], }, }; diff --git a/app/opengraph-image.tsx b/app/opengraph-image.tsx index 4358793..fb831b7 100644 --- a/app/opengraph-image.tsx +++ b/app/opengraph-image.tsx @@ -15,48 +15,96 @@ export default function OpenGraphImage() { display: "flex", width: "100%", height: "100%", - background: - "radial-gradient(circle at top, #1f8f5d 0%, #0a2e1f 35%, #04110c 100%)", + background: "linear-gradient(135deg, #04110c 0%, #0d2d1f 42%, #145c3a 100%)", color: "#f7fff9", - padding: 48, + padding: 42, fontFamily: "Arial", + position: "relative", }} > +
+
+
-
+
- Daily + Daily Puzzle
-
hidden11
-
- Daily football lineup puzzle with Easy, Medium and Hard cards that are actually worth solving. + +
+
hidden
+
+
+
+
+
+ +
+ Daily football lineup puzzle with clues, history links and difficulty levels that actually escalate.
-
+
{["Easy", "Medium", "Hard"].map((label, index) => (
{label}
- {index === 0 ? "Sharper club clues" : index === 1 ? "Less obvious player pool" : "Career-history riddles"} + {index === 0 ? "Fast entry" : index === 1 ? "Less obvious pool" : "Career-history riddles"}
))} @@ -83,57 +137,91 @@ export default function OpenGraphImage() {
+
+
+
+ Today +
+
+ hidden11 #124 +
+
+
+ Shareable +
+
+
{[ - ["", "ST", ""], - ["WING", "", "MID"], - ["", "DEF", ""], - ["", "GK", ""], - ].map((row, rowIndex) => ( -
- {row.map((cell, cellIndex) => - cell ? ( + { label: "Easy", time: "1:24", color: "#3ee696" }, + { label: "Medium", time: "2:08", color: "#facc15" }, + { label: "Hard", time: "3:41", color: "#ffffff" }, + ].map((card) => ( +
+
+
{card.label}
+
{card.time}
+
+
+ {Array.from({ length: 5 }).map((_, index) => (
- {cell} -
- ) : ( -
- ), - )} + /> + ))} +
))}
+ +
hidden11.app
diff --git a/app/page.tsx b/app/page.tsx index 992a4ac..5ea2b65 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -7,6 +7,7 @@ import Pitch from "@/components/Pitch"; import PlayerSearch from "@/components/PlayerSearch"; import ShareResult from "@/components/ShareResult"; import StatsPanel from "@/components/StatsPanel"; +import { buildShareText, buildShareUrl } from "@/lib/share"; import { DIFFICULTIES, MAX_ATTEMPTS, @@ -53,6 +54,11 @@ export default function Home() { const isFinished = activeProgress.solved || activeProgress.attempts.length >= MAX_ATTEMPTS; const t = useMemo(() => getDictionary(locale), [locale]); const quote = useMemo(() => quoteForDay(dateKey, locale), [dateKey, locale]); + const shareUrl = useMemo( + () => buildShareUrl(dateKey, progress, typeof window === "undefined" ? "https://hidden11.app" : window.location.origin), + [dateKey, progress], + ); + const shareText = useMemo(() => buildShareText(dateKey, progress, t, shareUrl), [dateKey, progress, shareUrl, t]); useEffect(() => { const savedLocale = window.localStorage.getItem("hidden11-locale") as Locale | null; @@ -167,6 +173,22 @@ export default function Home() { window.localStorage.setItem("hidden11-locale", nextLocale); } + async function shareCurrentResult() { + if (navigator.share) { + await navigator.share({ + title: "hidden11", + text: shareText, + }); + return; + } + + window.open( + `https://twitter.com/intent/tweet?text=${encodeURIComponent(shareText)}`, + "_blank", + "noopener,noreferrer", + ); + } + const actionButtons = (
+
+ + +
diff --git a/app/r/[shareCode]/opengraph-image.tsx b/app/r/[shareCode]/opengraph-image.tsx new file mode 100644 index 0000000..b63424b --- /dev/null +++ b/app/r/[shareCode]/opengraph-image.tsx @@ -0,0 +1 @@ +export { default, alt, contentType, size } from "../../result/[shareCode]/opengraph-image"; diff --git a/app/r/[shareCode]/page.tsx b/app/r/[shareCode]/page.tsx new file mode 100644 index 0000000..628a56f --- /dev/null +++ b/app/r/[shareCode]/page.tsx @@ -0,0 +1 @@ +export { default, generateMetadata } from "../../result/[shareCode]/page"; diff --git a/app/r/[shareCode]/twitter-image.tsx b/app/r/[shareCode]/twitter-image.tsx new file mode 100644 index 0000000..1d364cd --- /dev/null +++ b/app/r/[shareCode]/twitter-image.tsx @@ -0,0 +1 @@ +export { default, alt, contentType, size } from "../../result/[shareCode]/twitter-image"; diff --git a/components/ShareResult.tsx b/components/ShareResult.tsx index c28d906..82eaca1 100644 --- a/components/ShareResult.tsx +++ b/components/ShareResult.tsx @@ -16,7 +16,6 @@ type ShareResultProps = { }; export default function ShareResult({ dateKey, locale, onClose, progress, quote, stats, t }: ShareResultProps) { - const [copied, setCopied] = useState(false); const [shared, setShared] = useState(false); const [origin, setOrigin] = useState("https://hidden11.app"); const shareUrl = useMemo(() => buildShareUrl(dateKey, progress, origin), [dateKey, origin, progress]); @@ -26,18 +25,11 @@ export default function ShareResult({ dateKey, locale, onClose, progress, quote, setOrigin(window.location.origin); }, []); - async function copy() { - await navigator.clipboard.writeText(shareText); - setCopied(true); - window.setTimeout(() => setCopied(false), 1600); - } - async function share() { if (navigator.share) { await navigator.share({ title: "hidden11", text: shareText, - url: shareUrl, }); setShared(true); window.setTimeout(() => setShared(false), 1600); @@ -98,27 +90,6 @@ export default function ShareResult({ dateKey, locale, onClose, progress, quote,
-
-
- - -
-
- {shareUrl} -
-
); diff --git a/lib/share.ts b/lib/share.ts index 18fb32e..8b33ac4 100644 --- a/lib/share.ts +++ b/lib/share.ts @@ -25,6 +25,8 @@ type SharePayload = { }; export function buildShareText(dateKey: string, progress: DailyProgress, t: Dictionary, shareUrl?: string): string { + const resultUrl = shareUrl ?? "https://hidden11.app"; + const homeUrl = "https://hidden11.app"; const rows = DIFFICULTIES.map((difficulty) => { const item = progress[difficulty]; const label = t.difficulty[difficulty]; @@ -43,10 +45,18 @@ export function buildShareText(dateKey: string, progress: DailyProgress, t: Dict .join("\n") : "⬛⬛⬛⬛⬛"; - return `${label} ${result} · ${time}\n${grids}`; + return `${label.padEnd(6)} ${result} · ${time}\n${grids}`; }); - return [`hidden11 #${getPuzzleNumber(dateKey)}`, "", ...rows, "", shareUrl ?? "https://hidden11.app"].join("\n"); + return [ + "┏━ hidden11.app ━┓", + `⚽ hidden11 #${getPuzzleNumber(dateKey)}`, + "", + ...rows, + "", + `Play: ${homeUrl}`, + `Result: ${resultUrl}`, + ].join("\n"); } export function buildShareUrl(dateKey: string, progress: DailyProgress, origin = "https://hidden11.app"): string { @@ -71,7 +81,7 @@ export function buildShareUrl(dateKey: string, progress: DailyProgress, origin = }, {} as Record), }; - return `${origin.replace(/\/$/, "")}/result/${encodeSharePayload(payload)}`; + return `${origin.replace(/\/$/, "")}/r/${encodeSharePayload(payload)}`; } export function parseSharePayload(shareCode: string): SharePayload | null {