Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 24 additions & 8 deletions apps/web/src/app/[locale]/epoch1000/page.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import Epoch1000Experience from "@/components/epoch1000/Epoch1000Experience";
import type { Metadata } from "next";
import { getAlternates, Link } from "@workspace/i18n/routing";
import { getTranslations } from "@workspace/i18n/server";

type Props = { params: Promise<{ locale: string }> };

const EPOCH1000_PATH = "/epoch1000";
const EPOCH1000_SOCIAL_IMAGE = "/api/epoch1000/og?s=1000&fe=0&c=1000";

export default async function Page() {
const t = await getTranslations("epoch1000.page");

return (
<main className="w-full max-w-6xl mx-auto px-5 sm:px-8 pt-6 sm:pt-8 pb-8 sm:pb-12 flex flex-col gap-10 sm:gap-14 min-h-screen">
<header className="flex items-center justify-between gap-4 border-b border-ep-edge/70 pb-4">
<p className="text-xs font-brand-mono uppercase tracking-[0.3em] text-ep-dust">
{t("eyebrow")}
</p>
<header className="flex justify-end border-b border-ep-edge/70 pb-4">
<Link
href="/epoch1000#checker"
className="text-xs font-brand-mono text-ep-dust hover:text-ep-ink transition-colors duration-200 shrink-0 rounded-sm focus-visible:outline-2 focus-visible:outline-offset-4"
Expand All @@ -33,20 +34,35 @@ export default async function Page() {
);
}

export async function generateMetadata({ params }: Props) {
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { locale } = await params;
const t = await getTranslations("epoch1000.meta");
const title = t("title");
const description = t("description");
const alternates = getAlternates(EPOCH1000_PATH, locale);

return {
title: t("title"),
description: t("description"),
alternates: getAlternates("/epoch1000", locale),
title,
description,
alternates,
openGraph: {
title: t("openGraphTitle"),
description: t("openGraphDescription"),
type: "website",
url: alternates.canonical,
images: [
{
url: EPOCH1000_SOCIAL_IMAGE,
width: 1200,
height: 630,
},
],
},
twitter: {
card: "summary_large_image",
title,
description,
images: [EPOCH1000_SOCIAL_IMAGE],
},
};
}
67 changes: 31 additions & 36 deletions apps/web/src/components/epoch1000/Epoch1000Experience.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,12 @@ export default function Epoch1000Experience({ title, description }: Props) {
{live ? (
<>
<span className="ep-live-dot mr-2 inline-block h-2 w-2 rounded-full bg-[#14f195] align-middle" />
LIVE FROM MAINNET · SLOT{" "}
{numberFormatter.format(live.absoluteSlot)}
LIVE · SLOT {numberFormatter.format(live.absoluteSlot)}
</>
) : error ? (
"COULDN'T REACH MAINNET - REFRESH TO RETRY"
"MAINNET UNAVAILABLE"
) : (
<span className="animate-pulse">SYNCING WITH MAINNET…</span>
<span className="animate-pulse">SYNCING...</span>
)}
</p>

Expand All @@ -84,7 +83,7 @@ export default function Epoch1000Experience({ title, description }: Props) {

<p className="text-sm sm:text-base text-ep-dim max-w-xl">
{arrived
? "One thousand epochs of mainnet-beta. 432,000,000,000 slots, give or take a fork. gm to everyone who was here for any of it."
? "Epoch 1000 is here. Celebrate the builders, validators, apps, and users who kept mainnet moving."
: description}
</p>

Expand All @@ -100,23 +99,29 @@ export default function Epoch1000Experience({ title, description }: Props) {
}}
/>
</div>
<p className="font-brand-mono text-xs sm:text-sm text-ep-dust tabular-nums">
T-MINUS{" "}
<span className="text-ep-ink">
{numberFormatter.format(live.slotsRemaining)}
</span>{" "}
SLOTS · ≈{" "}
<span className="text-ep-ink">
{days}d {pad(hours)}h {pad(mins)}m {pad(secs)}s
</span>{" "}
· ARRIVING ~
{live.eta
.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})
.toUpperCase()}
</p>
<div className="grid grid-cols-1 gap-2 font-brand-mono text-xs text-ep-dust tabular-nums sm:grid-cols-3 sm:text-sm">
<div className="flex items-center justify-between gap-3 rounded-full border border-ep-edge bg-ep-panel/70 px-4 py-2">
<span className="uppercase tracking-[0.12em]">Slots</span>
<span className="text-ep-ink">
{numberFormatter.format(live.slotsRemaining)}
</span>
</div>
<div className="flex items-center justify-between gap-3 rounded-full border border-ep-edge bg-ep-panel/70 px-4 py-2">
<span className="uppercase tracking-[0.12em]">Time</span>
<span className="text-ep-ink">
{days}d {pad(hours)}h {pad(mins)}m {pad(secs)}s
</span>
</div>
<div className="flex items-center justify-between gap-3 rounded-full border border-ep-edge bg-ep-panel/70 px-4 py-2">
<span className="uppercase tracking-[0.12em]">ETA</span>
<span className="text-ep-ink">
{live.eta.toLocaleDateString("en-US", {
month: "short",
day: "numeric",
})}
</span>
</div>
</div>
</div>
)}
</div>
Expand All @@ -136,13 +141,13 @@ export default function Epoch1000Experience({ title, description }: Props) {

{/* ── The thousand grid ────────────────────────────────────────── */}
<Reveal>
<section id="timeline" className="flex flex-col gap-5 scroll-mt-24">
<section id="timeline" className="flex flex-col gap-4 scroll-mt-24">
<div className="flex items-end justify-between gap-4">
<h2 className="font-bold tracking-tight text-3xl sm:text-4xl">
Six years, <span className="text-sol-gradient">one grid.</span>
Mainnet timeline
</h2>
<p className="font-brand-mono text-[11px] sm:text-xs text-ep-dust tracking-[0.15em] whitespace-nowrap pb-1.5">
EPOCH 0 → 999
EPOCHS 0-999
</p>
</div>
<ThousandGrid
Expand All @@ -152,22 +157,12 @@ export default function Epoch1000Experience({ title, description }: Props) {
accent={tier?.color}
accentLabel={
result && tier
? `${tier.name} · EPOCH ${result.firstEpoch} NOW`
? `${tier.name} · EPOCH ${result.firstEpoch} TO NOW`
: undefined
}
/>
<p className="font-brand-mono text-[11px] sm:text-xs text-ep-dust">
one cell per epoch · bright cells mark moments · check a wallet
above to paint your era
</p>
</section>
</Reveal>

<footer className="border-t border-ep-edge pt-6 pb-2">
<p className="font-brand-mono text-[11px] sm:text-xs text-ep-dust text-center tracking-[0.15em]">
432,000 SLOTS PER EPOCH · ~2 DAYS EACH · MAINNET-BETA SINCE MARCH 2020
</p>
</footer>
</div>
);
}
4 changes: 2 additions & 2 deletions apps/web/src/components/epoch1000/ThousandGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ export default function ThousandGrid({
const readout = useMemo(() => {
if (hovered === null) {
return {
text: "EPOCH 0 → 999 · MAR 2020 → NOW",
hint: "scrub to travel",
text: "EPOCHS 0-999",
hint: "scrub",
moment: null as string | null,
};
}
Expand Down
29 changes: 14 additions & 15 deletions apps/web/src/components/epoch1000/WalletChecker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {
} from "@/lib/epoch1000/public-key";

const LOADING_LINES = [
"walking your history back to genesis…",
"counting slots…",
"asking the validators about you…",
"replaying the ledger…",
"checking...",
"finding first tx...",
"scanning history...",
"building card...",
];

interface Props {
Expand Down Expand Up @@ -75,7 +75,7 @@ export default function WalletChecker({ onResult }: Props) {
: "";
const tweet = result
? encodeURIComponent(
`I've survived ${result.epochsSurvived}${result.capped ? "+" : ""} epochs on Solana - ${result.tier}.\n\nWere you here? ${cardUrl}`,
`I've survived ${result.epochsSurvived}${result.capped ? "+" : ""} Solana epochs - ${result.tier}.\n\n${cardUrl}`,
)
: "";

Expand All @@ -100,12 +100,11 @@ export default function WalletChecker({ onResult }: Props) {
<section id="checker" className="flex flex-col gap-6">
<div>
<h2 className="font-bold tracking-tight text-3xl sm:text-4xl">
Were you <span className="text-sol-gradient">here?</span>
Check <span className="text-sol-gradient">wallet</span>
</h2>
<p className="mt-2 text-sm text-ep-dim">
Paste any wallet address - no connect, no signature. We find its first
mainnet transaction, light up your era on the timeline below, and
stamp your survivor card.
Find your first epoch and share your survivor card. No connect, no
signature.
</p>
</div>

Expand All @@ -131,7 +130,7 @@ export default function WalletChecker({ onResult }: Props) {
disabled={loading || !isAddressValid}
className="!bg-ep-ink text-ep-void font-semibold rounded-full px-7 py-3 text-sm disabled:opacity-40 disabled:cursor-not-allowed hover:!bg-ep-dim transition"
>
{loading ? "checking…" : "Check wallet"}
{loading ? "Checking..." : "Check"}
</button>
</form>

Expand Down Expand Up @@ -171,15 +170,15 @@ export default function WalletChecker({ onResult }: Props) {
onChange={(e) => setShowAddress(e.target.checked)}
className="accent-[#14f195]"
/>
show wallet on card
Show wallet
</label>
<a
href={`https://twitter.com/intent/tweet?text=${tweet}`}
target="_blank"
rel="noopener noreferrer"
className="bg-ep-ink text-ep-void font-semibold rounded-full px-5 py-2 hover:bg-ep-dim transition"
>
Share on X
Share
</a>
<button
onClick={copyLink}
Expand All @@ -192,12 +191,12 @@ export default function WalletChecker({ onResult }: Props) {
download={`epoch1000-${result.firstEpoch}.png`}
className="border border-ep-edge rounded-full px-5 py-2 text-ep-dim hover:text-ep-ink transition"
>
Download PNG
PNG
</a>
</div>

<p className="text-xs text-ep-dust">
First transaction:{" "}
First seen:{" "}
<a
href={`https://solscan.io/tx/${result.firstSignature}`}
target="_blank"
Expand All @@ -208,7 +207,7 @@ export default function WalletChecker({ onResult }: Props) {
{result.firstSlot.toLocaleString("en-US")}
</a>
{result.capped &&
` - this wallet has over ${result.scanned.toLocaleString("en-US")} transactions, so we report the oldest we could reach. True survival may be even longer.`}
` - oldest found across ${result.scanned.toLocaleString("en-US")} transactions.`}
</p>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ export const Epoch1000Hero: React.FC = () => {
const statSlotsLabel = t("statSlots");
const statProgressLabel = t("statProgress");
const statEtaLabel = t("statEta");
const slotLabel = t("slotLabel");
const numberFormatter = useMemo(
() => new Intl.NumberFormat(locale),
[locale],
Expand Down Expand Up @@ -179,14 +178,14 @@ export const Epoch1000Hero: React.FC = () => {
})}
</h1>
<p
className="eh-rise text-nd-mid-em-text font-medium mt-3 nd-body-xl max-w-[560px]"
className="eh-rise text-nd-mid-em-text font-medium pt-3 nd-body-xl max-w-[560px]"
style={{ animationDelay: "300ms" }}
>
{t("subtitle")}
</p>

<div
className="eh-rise mt-4 md:mt-6 flex flex-col sm:flex-row items-center gap-3 sm:gap-5"
className="eh-rise pt-4 md:pt-6 flex flex-col sm:flex-row items-center gap-3 sm:gap-5"
style={{ animationDelay: "400ms" }}
>
<Button
Expand All @@ -213,12 +212,12 @@ export const Epoch1000Hero: React.FC = () => {
</Link>
</div>

{/* live console — the chain ticking underneath the monument */}
{/* Compact live status under the monument. */}
<div
className="eh-rise mt-auto pt-4 md:pt-6 w-full max-w-4xl"
className="eh-rise mt-auto pt-4 md:pt-6 w-full max-w-3xl"
style={{ animationDelay: "500ms" }}
>
<div className="rounded-xl bg-black/[0.3] px-4 py-2 md:px-6 md:py-4">
<div className="rounded-xl bg-black/[0.3] px-4 py-2.5 md:px-6 md:py-4">
<div className="flex items-baseline justify-between font-brand-mono text-[11px] md:text-xs uppercase tracking-[0.2em] text-nd-mid-em-text">
<span>{statProgressLabel}</span>
<span className="tabular-nums">
Expand All @@ -235,7 +234,7 @@ export const Epoch1000Hero: React.FC = () => {
}}
/>
</div>
<div className="mt-2 md:mt-3 flex flex-wrap items-center justify-center sm:justify-between gap-x-6 gap-y-1.5 md:gap-y-2 font-brand-mono text-[11px] md:text-xs text-nd-mid-em-text tabular-nums">
<div className="mt-2 md:mt-3 flex flex-wrap items-center justify-center gap-x-5 gap-y-1.5 md:gap-y-2 font-brand-mono text-[11px] md:text-xs text-nd-mid-em-text tabular-nums">
{live ? (
<>
<span className="flex items-center gap-2 whitespace-nowrap">
Expand All @@ -246,22 +245,17 @@ export const Epoch1000Hero: React.FC = () => {
<span className="hidden sm:block">
<BlockStream absoluteSlot={live.absoluteSlot} />
</span>
<span>
{slotLabel} {numberFormatter.format(live.absoluteSlot)}
</span>
</span>
<span className="flex flex-wrap justify-center gap-x-4 gap-y-1 uppercase tracking-[0.1em]">
<span className="whitespace-nowrap">
{statSlotsLabel}{" "}
<span className="text-nd-high-em-text">
{numberFormatter.format(live.slotsRemaining)}
</span>
<span className="whitespace-nowrap uppercase tracking-[0.1em]">
{statSlotsLabel}{" "}
<span className="text-nd-high-em-text">
{numberFormatter.format(live.slotsRemaining)}
</span>
<span className="whitespace-nowrap">
{statEtaLabel}{" "}
<span className="text-nd-high-em-text">
{etaFormatter.format(live.eta)}
</span>
</span>
<span className="whitespace-nowrap uppercase tracking-[0.1em]">
{statEtaLabel}{" "}
<span className="text-nd-high-em-text">
{etaFormatter.format(live.eta)}
</span>
</span>
</>
Expand Down
Loading