Files
zeekay 0cbf9dffb3 feat: glossy pass — live activity ticker, count-up, monochrome gloss
Adds real social proof and a glossy depth pass on top of the existing
brand-neutral monochrome. Every new piece is configurable end-to-end.

Activity feed

  • Mock-api: GET /v1/waitlist/activity?waitlist=&limit=&types= returning
    most-recent events with a server-now timestamp so clients render
    consistent relative times across clock skew. Ring-buffer last 200
    events (env override: ACTIVITY_MAX_HISTORY). Cap on limit: 100
    (env: ACTIVITY_LIMIT_CAP). Types filter (join,share,invite,referral)
    is server-validated, unknown types silently dropped.
  • join handler pushes a `join` event + a `referral` event when the
    sender's refCode resolves. track-share pushes a `share` event the
    same day-it-was-credited (skipped on duplicate same-day to match
    the daily-cap economy). invite pushes one event per accepted email.
  • Seeder synthesizes 30 backdated events on first boot so the ticker
    is alive on first paint, not waiting on real traffic.
  • Widget: <WaitlistActivity waitlist baseUrl limit types pollInterval
    formatEvent formatRelative showHeading heading emptyLabel theme
    className style /> — every visible knob is a prop. Polling interval
    is configurable (0 disables, default 12s). Default formatter handles
    copy/webshare/email/sms with natural copy ("copied their link",
    "shared via system", "shared by email", "texted their link"); other
    platforms get a clean platformLabel mapping (twitter→X, etc.).

Animated count-up

  • useCountUp(target, { duration, ease, retrigger }) hook — 1200ms
    cubic-out by default, honors prefers-reduced-motion, 0 disables.
  • <WaitlistLeaderboard animateTotal={true | false | { duration }}/>
    runs the count-up on the "N on the list" subtitle when the meta
    first lands. Truly off-switchable.

Glossy aesthetic (all monochrome — no new color tokens)

  • Cards: multi-layer shadow + a 1px inner highlight on the top edge
    using inset color-mix(--hw-bg). Reads as real material without
    breaking neutrality.
  • Leaderboard rows: 6px radius, translateX(2px) on hover (skipped
    under prefers-reduced-motion).
  • Activity pulse: 8px accent dot with a soft expanding ring keyframe
    (1.8s loop). Reduced-motion turns it off.
  • Demo hero: subtle radial gradient halo via .section--glow::before
    using color-mix(--fg 6%) to stay neutral. Disabled when the user
    prefers reduced transparency.
  • Topbar Join pill: inset top highlight + accentuated drop shadow
    on hover for that glossy CTA look.
  • Two-column board grid on desktop (leaderboard + Live activity),
    stacks on <=880px.
  • OpenGraph + Twitter card metadata on the demo for proper social
    previews when someone tweets the page URL.
2026-06-10 13:32:08 -07:00

418 lines
8.4 KiB
CSS

/* Demo site — Hanzo monochrome (Radix gray). */
:root {
--bg: #fcfcfc;
--bg-2: #f9f9f9;
--bg-3: #f0f0f0;
--fg: #202020;
--muted: #646464;
--border: #e8e8e8;
--border-strong: #d9d9d9;
--font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, sans-serif;
--mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
--container: 1100px;
--pad-x: clamp(20px, 4vw, 32px);
--pad-y: clamp(56px, 8vw, 96px);
--radius: 14px;
--radius-sm: 8px;
color-scheme: light dark;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111111;
--bg-2: #191919;
--bg-3: #222222;
--fg: #eeeeee;
--muted: #b4b4b4;
--border: #2a2a2a;
--border-strong: #3a3a3a;
}
}
*,
*::before,
*::after { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--fg);
font-family: var(--font);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
::selection { background: var(--fg); color: var(--bg); }
a { color: inherit; text-decoration: none; }
a:hover { opacity: 0.72; }
button:focus-visible,
a:focus-visible,
[role="tab"]:focus-visible {
outline: 2px solid var(--fg);
outline-offset: 2px;
border-radius: 8px;
}
/* Container */
.container {
max-width: var(--container);
margin: 0 auto;
padding-left: var(--pad-x);
padding-right: var(--pad-x);
}
/* Sticky topbar with primary Join CTA */
.topbar {
position: sticky;
top: 0;
z-index: 50;
background: color-mix(in srgb, var(--bg) 92%, transparent);
backdrop-filter: saturate(180%) blur(10px);
-webkit-backdrop-filter: saturate(180%) blur(10px);
border-bottom: 1px solid var(--border);
color: var(--fg);
}
.topbar__inner {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding-top: 14px;
padding-bottom: 14px;
}
.topbar__brand {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--fg);
font-weight: 600;
letter-spacing: -0.01em;
}
.topbar__wordmark {
font-size: 15px;
}
.topbar__brand:hover { opacity: 1; }
.topbar__cta { flex-shrink: 0; }
/* Primary CTA pill — used in topbar + FAB. */
.cta {
appearance: none;
cursor: pointer;
font: inherit;
font-size: 14px;
font-weight: 600;
letter-spacing: -0.005em;
padding: 10px 18px;
border-radius: 999px;
border: 1px solid transparent;
display: inline-flex;
align-items: center;
gap: 8px;
transition: transform 120ms ease, background 120ms ease, color 120ms ease, box-shadow 200ms ease;
white-space: nowrap;
}
.cta:hover { transform: translateY(-1px); }
.cta:active { transform: none; }
.cta--solid {
background: var(--hw-accent, var(--fg));
color: var(--hw-accent-fg, var(--bg));
/* Subtle inner highlight on the top edge for that glossy feel. */
box-shadow:
inset 0 1px 0 0 color-mix(in srgb, var(--hw-accent-fg, var(--bg)) 18%, transparent),
0 1px 2px 0 color-mix(in srgb, var(--fg) 12%, transparent);
}
.cta--solid:hover {
box-shadow:
inset 0 1px 0 0 color-mix(in srgb, var(--hw-accent-fg, var(--bg)) 22%, transparent),
0 8px 22px 0 color-mix(in srgb, var(--fg) 22%, transparent);
}
.cta__rank {
font-variant-numeric: tabular-nums;
font-weight: 700;
}
.cta__sep {
opacity: 0.45;
margin: 0 -2px;
}
@media (prefers-reduced-motion: reduce) {
.cta { transition: none; }
.cta:hover { transform: none; }
}
/* Sections */
.section {
padding-top: clamp(40px, 6vw, 72px);
padding-bottom: clamp(40px, 6vw, 72px);
}
.section--first {
/* compensate for sticky topbar so the headline isn't tucked under it */
padding-top: clamp(32px, 5vw, 56px);
}
/* Glossy radial halo behind the hero — purely additive, monochrome,
* disabled when the user prefers reduced motion or low contrast. */
.section--glow {
position: relative;
isolation: isolate;
}
.section--glow::before {
content: '';
position: absolute;
inset: 0 0 auto 0;
height: 520px;
background:
radial-gradient(120% 80% at 50% 0%,
color-mix(in srgb, var(--fg) 6%, transparent) 0%,
color-mix(in srgb, var(--fg) 0%, transparent) 60%);
pointer-events: none;
z-index: -1;
mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
-webkit-mask-image: linear-gradient(180deg, #000 0%, #000 60%, transparent 100%);
}
@media (prefers-reduced-transparency: reduce) {
.section--glow::before { display: none; }
}
/* Board grid: leaderboard left, live activity right, stack on mobile. */
.board-grid {
display: grid;
grid-template-columns: minmax(0, 1.55fr) minmax(280px, 1fr);
gap: clamp(16px, 2.4vw, 28px);
align-items: start;
}
@media (max-width: 880px) {
.board-grid {
grid-template-columns: minmax(0, 1fr);
}
}
.section--alt {
background: var(--bg-2);
border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border);
max-width: none;
padding-left: 0;
padding-right: 0;
}
.section--alt > * {
max-width: var(--container);
margin-left: auto;
margin-right: auto;
padding-left: var(--pad-x);
padding-right: var(--pad-x);
}
/* Section heads */
.section-head {
margin-bottom: clamp(24px, 4vw, 40px);
}
.section-head--center {
text-align: center;
}
.section-head--center .h1,
.section-head--center .h2 { margin-left: auto; margin-right: auto; }
.h1 {
font-size: clamp(36px, 6vw, 56px);
font-weight: 700;
letter-spacing: -0.035em;
line-height: 1.02;
margin: 0 0 14px;
}
.h1--lg {
font-size: clamp(40px, 7vw, 64px);
}
.h2 {
font-size: clamp(24px, 3vw, 36px);
font-weight: 600;
letter-spacing: -0.025em;
margin: 0 0 12px;
}
.h2--lg {
font-size: clamp(28px, 4vw, 44px);
letter-spacing: -0.03em;
}
.lede {
font-size: clamp(15px, 1.5vw, 17px);
color: var(--muted);
line-height: 1.55;
margin: 0;
max-width: 56ch;
}
.lede--center {
margin: 0 auto;
text-align: center;
}
/* Join section: 2-col on desktop (intro | widget), stacked on mobile */
.join-wrap {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(360px, 440px);
gap: clamp(24px, 5vw, 64px);
align-items: start;
}
@media (max-width: 880px) {
.join-wrap {
grid-template-columns: minmax(0, 1fr);
gap: 28px;
}
.join-intro { text-align: center; }
.join-intro .join-bullets { display: inline-block; text-align: left; }
}
.join-intro {
display: flex;
flex-direction: column;
gap: 16px;
padding-top: 6px;
}
.join-lede {
font-size: clamp(15px, 1.4vw, 16px);
color: var(--muted);
line-height: 1.55;
margin: 0;
max-width: 50ch;
}
.join-bullets {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: 10px;
}
.join-bullets li {
font-size: 14px;
color: var(--muted);
padding-left: 22px;
position: relative;
line-height: 1.45;
}
.join-bullets li::before {
content: '';
position: absolute;
left: 0;
top: 10px;
width: 12px;
height: 1px;
background: var(--border-strong);
}
.join-bullets li b {
color: var(--fg);
font-weight: 600;
font-variant-numeric: tabular-nums;
}
.join-widget {
display: flex;
justify-content: center;
}
/* Footer */
.footer {
padding-top: clamp(32px, 5vw, 56px);
padding-bottom: clamp(32px, 5vw, 56px);
border-top: 1px solid var(--border);
}
.footer__row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
font-size: 13px;
color: var(--muted);
}
.footer__brand {
display: inline-flex;
align-items: center;
gap: 10px;
color: var(--fg);
font-weight: 500;
}
.footer__meta { color: var(--muted); }
@media (max-width: 540px) {
.footer__row { flex-direction: column; text-align: center; gap: 10px; }
}
/* Make the leaderboard truly full-width inside the section container.
* Inside the join-widget column we want the natural ~440px widget width. */
.section .hanzo-waitlist { max-width: none; }
.join-widget .hanzo-waitlist { max-width: 440px; }
/* Smooth anchor scroll respecting the sticky topbar. */
html { scroll-padding-top: 76px; }
/* Brand presets — override --hw-* directly on the wrapper. Activated by
* pressing `b` to cycle through; primarily exists to demonstrate the
* brand-neutral CSS variable contract. */
.preset-a {
--hw-accent: #0a0a0a;
--hw-accent-fg: #fafafa;
--hw-radius: 12px;
}
.preset-b {
--hw-accent: #c9a253;
--hw-accent-fg: #0a0a0a;
--hw-radius: 4px;
}
.preset-c {
--hw-accent: #2a8a3a;
--hw-accent-fg: #ffffff;
--hw-radius: 24px;
}
.preset-d {
--hw-accent: #6a3d8f;
--hw-accent-fg: #ffffff;
--hw-radius: 6px;
}