From fd987542f040e96959a31775f157f543238eaa6f Mon Sep 17 00:00:00 2001 From: heyradcode Date: Sun, 9 Aug 2026 10:52:59 -0400 Subject: [PATCH 01/45] fix(website): scroll the document instead of a nested container --- website/src/styles/globals.css | 12 +++++++++++- website/src/styles/landing-page.css | 7 ------- website/src/styles/layout.css | 7 +++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/website/src/styles/globals.css b/website/src/styles/globals.css index 2d402bec..25f898db 100644 --- a/website/src/styles/globals.css +++ b/website/src/styles/globals.css @@ -2,6 +2,13 @@ @import './layout.css'; @import './landing-page.css'; +html { + /* Anchor targets clear the fixed `.main-header`. Do not also set + `scroll-margin-top` on the targets — the two offsets add up. */ + scroll-padding-top: var(--main-header-offset); + scroll-behavior: smooth; +} + :root { font-family: var(--font-body), 'Inter', system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; @@ -30,7 +37,10 @@ a:hover { body { margin: 0; min-width: 320px; - min-height: 100vh; + min-height: 100dvh; + /* `clip`, not `hidden`: `hidden` would make this a scroll container and + re-break `position: sticky` for everything inside it. */ + overflow-x: clip; background: inherit; color: inherit; } diff --git a/website/src/styles/landing-page.css b/website/src/styles/landing-page.css index 6cb5a51d..fb508924 100644 --- a/website/src/styles/landing-page.css +++ b/website/src/styles/landing-page.css @@ -6,11 +6,6 @@ backdrop-filter: blur(10px); } -html, -.landing-layout .main-content { - scroll-behavior: smooth; -} - .landing { --landing-gutter: clamp(44px, 7vw, 108px); --section-pad-block: clamp(80px, 9vh, 120px); @@ -22,14 +17,12 @@ html, repeating-linear-gradient(0deg, rgb(255 255 255 / 0.03) 0 1px, transparent 1px 48px), repeating-linear-gradient(90deg, rgb(255 255 255 / 0.03) 0 1px, transparent 1px 48px), linear-gradient(180deg, #060912 0%, #070a16 100%); - scroll-behavior: smooth; } .landing-section { position: relative; padding: var(--section-pad-block) var(--landing-gutter); background: transparent; - scroll-margin-top: var(--main-header-offset); } /* Alternating mood washes — every .landing-section cycles cyan / violet / magenta */ diff --git a/website/src/styles/layout.css b/website/src/styles/layout.css index bed532c9..8b656261 100644 --- a/website/src/styles/layout.css +++ b/website/src/styles/layout.css @@ -4,10 +4,9 @@ --title-font: var(--font-display), 'Orbitron', 'Eurostile', 'Bank Gothic', system-ui, sans-serif; --content-font: var(--font-body), 'Inter', 'Segoe UI', system-ui, sans-serif; width: 100%; - height: 100vh; + min-height: 100dvh; font-family: var(--content-font); position: relative; - overflow: hidden; display: flex; flex-direction: column; background: @@ -92,14 +91,14 @@ flex-wrap: wrap; } +/* The document scrolls, not this element — a nested scroll container would break + `position: sticky`, scroll-linked animation, and mobile URL-bar collapse. */ .main-content { flex: 1; margin-block-start: var(--main-header-offset); width: 100%; box-sizing: border-box; margin-inline: 0; - overflow-y: auto; - overflow-x: hidden; } /* Landing: gap under fixed header can be painted by hero section. */ From c52e699a7bd194160c7695c052cf6d2b72b5e916 Mon Sep 17 00:00:00 2001 From: heyradcode Date: Sun, 9 Aug 2026 11:03:19 -0400 Subject: [PATCH 02/45] refactor(website): extract a real design token layer --- .../common/NeonButton/NeonButton.css | 47 ++-- .../components/common/NeonCard/NeonCard.css | 26 +- website/src/styles/globals.css | 17 +- website/src/styles/landing-page.css | 33 +-- website/src/styles/layout.css | 19 +- website/src/styles/variables.css | 262 ++++++++++++++++-- 6 files changed, 303 insertions(+), 101 deletions(-) diff --git a/website/src/components/common/NeonButton/NeonButton.css b/website/src/components/common/NeonButton/NeonButton.css index dc724ae9..9ae221ad 100644 --- a/website/src/components/common/NeonButton/NeonButton.css +++ b/website/src/components/common/NeonButton/NeonButton.css @@ -1,16 +1,18 @@ -/* Base palette = azure (default tone; no .tone-azure rule needed) */ +/* Base palette = azure (default tone; no .tone-azure rule needed). + Per-tone alphas differ on purpose — the more saturated hues need less of + themselves to read at the same weight — so each tone restates all three. */ .neon-btn { - --btn-text: #8ed1ff; - --btn-border: rgb(30 157 255 / 50%); - --btn-glow: rgb(30 157 255 / 24%); - --btn-inner: rgb(30 157 255 / 14%); - --btn-highlight: rgb(255 255 255 / 3%); + --btn-text: var(--tone-azure-text); + --btn-border: rgb(var(--tone-azure-rgb) / 50%); + --btn-glow: rgb(var(--tone-azure-rgb) / 24%); + --btn-inner: rgb(var(--tone-azure-rgb) / 14%); + --btn-highlight: var(--surface-veil-1); position: relative; isolation: isolate; border: 1px solid var(--btn-border); - border-radius: 2px; - background: linear-gradient(180deg, rgb(5 13 30 / 92%), rgb(4 8 22 / 95%)); + border-radius: var(--radius-xs); + background: var(--surface-control); color: var(--btn-text); font-weight: 700; letter-spacing: 0.95px; @@ -20,7 +22,10 @@ inset 0 0 12px var(--btn-inner), inset 0 0 24px var(--btn-highlight), 0 0 12px var(--btn-glow); - transition: border-color 180ms ease, box-shadow 180ms ease, transform 180ms ease; + transition: + border-color var(--dur-base) var(--ease-out), + box-shadow var(--dur-base) var(--ease-out), + transform var(--dur-base) var(--ease-out); &::before { content: ''; @@ -78,24 +83,24 @@ } &.tone-emerald { - --btn-text: #9effd4; - --btn-border: rgb(0 255 157 / 48%); - --btn-glow: rgb(0 255 157 / 24%); - --btn-inner: rgb(0 255 157 / 14%); + --btn-text: var(--tone-emerald-text); + --btn-border: rgb(var(--tone-emerald-rgb) / 48%); + --btn-glow: rgb(var(--tone-emerald-rgb) / 24%); + --btn-inner: rgb(var(--tone-emerald-rgb) / 14%); } &.tone-amber { - --btn-text: #ffd07b; - --btn-border: rgb(255 181 67 / 44%); - --btn-glow: rgb(255 181 67 / 22%); - --btn-inner: rgb(255 181 67 / 12%); + --btn-text: var(--tone-amber-text); + --btn-border: rgb(var(--tone-amber-rgb) / 44%); + --btn-glow: rgb(var(--tone-amber-rgb) / 22%); + --btn-inner: rgb(var(--tone-amber-rgb) / 12%); } &.tone-cyan { - --btn-text: #86ebff; - --btn-border: rgb(0 247 255 / 36%); - --btn-glow: rgb(0 247 255 / 18%); - --btn-inner: rgb(0 247 255 / 10%); + --btn-text: var(--tone-cyan-text); + --btn-border: rgb(var(--tone-cyan-rgb) / 36%); + --btn-glow: rgb(var(--tone-cyan-rgb) / 18%); + --btn-inner: rgb(var(--tone-cyan-rgb) / 10%); } @media (prefers-reduced-motion: reduce) { diff --git a/website/src/components/common/NeonCard/NeonCard.css b/website/src/components/common/NeonCard/NeonCard.css index dedc27fa..fc44423c 100644 --- a/website/src/components/common/NeonCard/NeonCard.css +++ b/website/src/components/common/NeonCard/NeonCard.css @@ -1,12 +1,12 @@ .neon-card { position: relative; - border-radius: var(--border-radius, 12px); + border-radius: var(--radius-lg); overflow: hidden; transition: - transform 180ms ease, - box-shadow 180ms ease, - border-color 180ms ease, - filter 180ms ease; + transform var(--dur-base) var(--ease-out), + box-shadow var(--dur-base) var(--ease-out), + border-color var(--dur-base) var(--ease-out), + filter var(--dur-base) var(--ease-out); &::after { content: ''; @@ -16,16 +16,18 @@ pointer-events: none; opacity: 0; box-shadow: - inset 0 0 0 1px rgb(126 170 255 / 24%), - inset 0 0 22px rgb(86 136 255 / 12%); - transition: opacity 180ms ease, box-shadow 180ms ease; + inset 0 0 0 1px rgb(var(--border-rgb) / 24%), + inset 0 0 22px rgb(var(--border-rgb) / 12%); + transition: + opacity var(--dur-base) var(--ease-out), + box-shadow var(--dur-base) var(--ease-out); } &:hover::after { opacity: 1; box-shadow: - inset 0 0 0 1px rgb(148 191 255 / 42%), - inset 0 0 30px rgb(100 152 255 / 24%); + inset 0 0 0 1px rgb(var(--border-rgb) / 42%), + inset 0 0 30px rgb(var(--border-rgb) / 24%); } &:hover { @@ -41,8 +43,8 @@ &:active::after { opacity: 1; box-shadow: - inset 0 0 0 1px rgb(120 170 255 / 36%), - inset 0 0 18px rgb(84 136 255 / 16%); + inset 0 0 0 1px rgb(var(--border-rgb) / 36%), + inset 0 0 18px rgb(var(--border-rgb) / 16%); } @media (prefers-reduced-motion: reduce) { diff --git a/website/src/styles/globals.css b/website/src/styles/globals.css index 25f898db..2358d9ba 100644 --- a/website/src/styles/globals.css +++ b/website/src/styles/globals.css @@ -15,8 +15,8 @@ html { font-weight: 400; color-scheme: dark; - color: var(--cp-body-text-dark); - background-color: var(--cp-body-bg-dark); + color: var(--text-bright); + background-color: var(--surface-void); font-synthesis: none; text-rendering: optimizeLegibility; @@ -26,12 +26,13 @@ html { a { font-weight: 500; - color: var(--cp-link); + color: var(--neon-azure); text-decoration: inherit; + transition: color var(--dur-base) var(--ease-out); } a:hover { - color: var(--cp-link-hover); + color: var(--neon-cyan); } body { @@ -51,19 +52,19 @@ h1 { } button { - border-radius: 8px; + border-radius: var(--radius-sm); border: 1px solid transparent; padding: 0.6em 1.2em; font-size: 1em; font-weight: 500; font-family: inherit; - background-color: #1a1a1a; + background-color: var(--surface-void); cursor: pointer; - transition: border-color 0.25s; + transition: border-color var(--dur-slow) var(--ease-out); } button:hover { - border-color: var(--cp-link); + border-color: var(--neon-azure); } button:focus, diff --git a/website/src/styles/landing-page.css b/website/src/styles/landing-page.css index fb508924..9689341c 100644 --- a/website/src/styles/landing-page.css +++ b/website/src/styles/landing-page.css @@ -1,22 +1,20 @@ /* Landing-only: lighter header chrome so the hero shows through */ .landing-layout .main-header { - background: rgb(5 8 18 / 42%); - border-block-end-color: rgb(101 131 255 / 16%); - box-shadow: 0 8px 20px rgb(0 0 0 / 22%); + background: rgb(var(--surface-void-rgb) / 42%); + border-block-end-color: rgb(var(--border-rgb) / 16%); + box-shadow: var(--elev-1); backdrop-filter: blur(10px); } +/* Section rhythm tokens live at :root — see variables.css §9. */ .landing { - --landing-gutter: clamp(44px, 7vw, 108px); - --section-pad-block: clamp(80px, 9vh, 120px); - --section-pad-block-band: clamp(56px, 6vh, 80px); display: flex; flex-direction: column; min-height: 0; background: - repeating-linear-gradient(0deg, rgb(255 255 255 / 0.03) 0 1px, transparent 1px 48px), - repeating-linear-gradient(90deg, rgb(255 255 255 / 0.03) 0 1px, transparent 1px 48px), - linear-gradient(180deg, #060912 0%, #070a16 100%); + repeating-linear-gradient(0deg, var(--surface-veil-1) 0 1px, transparent 1px 48px), + repeating-linear-gradient(90deg, var(--surface-veil-1) 0 1px, transparent 1px 48px), + linear-gradient(180deg, var(--surface-deep-top) 0%, var(--surface-deep-bottom) 100%); } .landing-section { @@ -60,12 +58,12 @@ margin: 0 0 10px; text-align: left; padding-inline-start: 0; - color: #f6f3ff; + color: var(--text-title); font-size: clamp(1.65rem, 3vw, 2.3rem); font-weight: 700; text-transform: uppercase; letter-spacing: 0.8px; - text-shadow: 0 0 10px rgb(174 150 255 / 35%); + text-shadow: var(--glow-text-sm); position: relative; display: inline-block; } @@ -77,15 +75,16 @@ bottom: -8px; width: 56px; height: 2px; - background: linear-gradient(90deg, #7dd6ff 0%, #b58cff 60%, #ff7bcb 100%); - border-radius: 2px; - box-shadow: 0 0 12px rgb(170 140 255 / 65%); + --spectrum-angle: 90deg; + background: var(--gradient-spectrum); + border-radius: var(--radius-xs); + box-shadow: 0 0 12px rgb(var(--glow-text-rgb) / 65%); } .section-subtitle { font-family: var(--content-font); margin: 22px 0 28px; - color: rgb(195 210 255 / 70%); + color: var(--text-muted); font-size: 1rem; max-width: 640px; line-height: 1.55; @@ -102,10 +101,6 @@ } @media (max-width: 768px) { - .landing { - --section-pad-block: 64px; - --section-pad-block-band: 44px; - } .landing-section { padding-inline: 18px; } diff --git a/website/src/styles/layout.css b/website/src/styles/layout.css index 8b656261..62c169a0 100644 --- a/website/src/styles/layout.css +++ b/website/src/styles/layout.css @@ -9,11 +9,8 @@ position: relative; display: flex; flex-direction: column; - background: - radial-gradient(circle at 20% 0%, rgb(87 57 230 / 30%) 0%, transparent 38%), - radial-gradient(circle at 80% 20%, rgb(32 163 255 / 14%) 0%, transparent 44%), - #050812; - color: var(--color-text); + background: var(--surface-shell); + color: var(--text-bright); } .main-header { @@ -21,10 +18,10 @@ inset-block-start: 0; inset-inline: 0; width: 100%; - background: rgb(5 8 18 / 68%); - border-block-end: 1px solid rgb(101 131 255 / 22%); + background: rgb(var(--surface-void-rgb) / 68%); + border-block-end: var(--border-subtle); z-index: var(--z-header); - box-shadow: 0 10px 26px rgb(0 0 0 / 32%); + box-shadow: var(--elev-2); box-sizing: border-box; min-height: var(--main-header-offset); padding-inline: clamp(20px, 5vw, 42px); @@ -41,11 +38,11 @@ font-size: clamp(1.8rem, 3vw, 2.5rem); line-height: 1.2; margin: 0; - color: #f4f2ff; + color: var(--text-title); letter-spacing: 1.2px; font-weight: 700; text-transform: uppercase; - text-shadow: 0 0 12px rgb(181 152 255 / 40%); + text-shadow: var(--glow-text-md); } } @@ -104,7 +101,7 @@ /* Landing: gap under fixed header can be painted by hero section. */ .main-content:has(.landing) { margin-block-start: 0; - background-color: #070a14; + background-color: var(--surface-base); } @media (max-width: 768px) { diff --git a/website/src/styles/variables.css b/website/src/styles/variables.css index 0cba1ce5..bce8c4a2 100644 --- a/website/src/styles/variables.css +++ b/website/src/styles/variables.css @@ -1,55 +1,257 @@ /* ============================================================================= - Palette primitives — landing-only subset of frontend tokens. + Design tokens. + + Every value here was extracted from colours that were previously hardcoded and + repeated across the landing section stylesheets. Near-identical variants were + collapsed to one canonical value — the four border blues (96/100/101/102 x + 123-132 x 255) became `--border-rgb`, and the two emeralds (108 246 197 and + 108 244 197) became `--neon-emerald`. + + The `-rgb` triplets exist so alpha is composed at the use site with + `rgb(var(--x-rgb) / )` rather than hand-written rgba literals. That is + what makes a glow scale possible at all: one hue, many strengths. + + Section stylesheets still carry their own literals. They adopt these tokens as + each section is rewritten, rather than in one churn-heavy pass that would + rewrite the same lines twice. + ============================================================================= */ + +/* ============================================================================= + 1. Palette primitives ============================================================================= */ :root { - --cp-white: #ffffff; - --cp-text-body-dark: #f3f4f6; - --cp-text-muted-dark: #c7ccd4; - --cp-text-light-dark: #9ca3af; - --cp-border-dark: #2b3240; - --cp-bg-dark: #141923; - --cp-bg-muted-dark: #1d2430; - --cp-bg-hover-dark: #273244; + /* Display accents — headings, rails, gradient text, section washes. */ + --neon-cyan: #7dd6ff; + --neon-cyan-rgb: 125 214 255; + + --neon-azure: #1e9dff; + --neon-azure-rgb: 30 157 255; + + --neon-violet: #b58cff; + --neon-violet-rgb: 181 140 255; + + --neon-magenta: #ff7bcb; + --neon-magenta-rgb: 255 123 203; - --cp-brand-primary: #667eea; - --cp-brand-secondary: #764ba2; + --neon-emerald: #6cf6c5; + --neon-emerald-rgb: 108 246 197; - --cp-body-text-dark: #e5e7eb; - --cp-body-bg-dark: #0f1117; - --cp-link: #646cff; - --cp-link-hover: #535bf2; + --neon-gold: #ffd76b; + --neon-gold-rgb: 255 215 107; + + /* Rarity tones — Pets rarity track, legend dots, card edges, tags. */ + --rarity-common: #b2c8eb; + --rarity-common-rgb: 178 200 235; + --rarity-rare: #50b8ff; + --rarity-rare-rgb: 80 184 255; + --rarity-epic: #b878ff; + --rarity-epic-rgb: 184 120 255; + --rarity-legendary: #ffcf5c; + --rarity-legendary-rgb: 255 207 92; + + /* Status tones — roadmap pins, rail segments, badges. */ + --status-shipped: var(--neon-emerald); + --status-shipped-rgb: var(--neon-emerald-rgb); + --status-active: var(--neon-gold); + --status-active-rgb: var(--neon-gold-rgb); + --status-planned: var(--rarity-epic); + --status-planned-rgb: var(--rarity-epic-rgb); + + /* Control tones — NeonButton. Deliberately more saturated than the display + accents: buttons are small and have to hold their colour against dark glass. + Kept separate rather than unified so the two roles can diverge on purpose. */ + --tone-azure-rgb: 30 157 255; + --tone-azure-text: #8ed1ff; + --tone-emerald-rgb: 0 255 157; + --tone-emerald-text: #9effd4; + --tone-amber-rgb: 255 181 67; + --tone-amber-text: #ffd07b; + --tone-cyan-rgb: 0 247 255; + --tone-cyan-text: #86ebff; } /* ============================================================================= - Semantic tokens + 2. Surfaces ============================================================================= */ :root { - --color-primary: var(--cp-brand-primary); - --color-secondary: var(--cp-brand-secondary); - --color-text: var(--cp-text-body-dark); - --color-text-muted: var(--cp-text-muted-dark); - --color-text-light: var(--cp-text-light-dark); - --color-border: var(--cp-border-dark); - --color-background: var(--cp-bg-dark); - --color-background-light: var(--cp-bg-muted-dark); - --color-background-hover: var(--cp-bg-hover-dark); + --surface-void: #050812; + --surface-void-rgb: 5 8 18; + --surface-base: #070a14; + --surface-deep-top: #060912; + --surface-deep-bottom: #070a16; + + /* Shell aura behind the app chrome. */ + --surface-shell: + radial-gradient(circle at 20% 0%, rgb(87 57 230 / 30%) 0%, transparent 38%), + radial-gradient(circle at 80% 20%, rgb(32 163 255 / 14%) 0%, transparent 44%), + var(--surface-void); + + /* Card body — the two-stop vertical gradient repeated on nearly every card. */ + --surface-card-top: rgb(10 14 28 / 95%); + --surface-card-bottom: rgb(7 11 24 / 95%); + --surface-card: linear-gradient(180deg, var(--surface-card-top) 0%, var(--surface-card-bottom) 100%); + + /* Control body — NeonButton. */ + --surface-control: linear-gradient(180deg, rgb(5 13 30 / 92%), rgb(4 8 22 / 95%)); + + /* Translucent fills that sit over any surface. */ + --surface-veil-1: rgb(255 255 255 / 3%); + --surface-veil-2: rgb(255 255 255 / 5%); + + /* Interior wash that gives cards depth without a visible edge. */ + --surface-inset-rgb: 55 68 148; } /* ============================================================================= - Layout scale used by landing sections and the fixed header. + 3. Text ============================================================================= */ :root { - --border-radius: 16px; - --border-radius-sm: 8px; + --text-title: #f6f3ff; + --text-bright: #eaf0ff; + --text-body: rgb(195 210 255 / 80%); + --text-muted: rgb(195 210 255 / 70%); + --text-dim: rgb(170 188 230 / 65%); + --text-accent: #b6e2ff; + --text-on-accent: #ffffff; +} - --z-header: 50; +/* ============================================================================= + 4. Borders + ============================================================================= */ +:root { + --border-rgb: 100 130 255; + --border-hairline: 1px solid rgb(var(--border-rgb) / 16%); + --border-subtle: 1px solid rgb(var(--border-rgb) / 22%); + --border-strong: 1px solid rgb(var(--border-rgb) / 38%); +} + +/* ============================================================================= + 5. Glow scale + + `--glow-rgb` is the hue; components override it and the steps below compose + against whatever it currently is. This is the piece the old stylesheets were + missing — every glow was a bespoke box-shadow, so nothing shared a ramp and + depth never read as consistent. + ============================================================================= */ +:root { + --glow-rgb: var(--border-rgb); + + --glow-xs: 0 0 8px rgb(var(--glow-rgb) / 22%); + --glow-sm: 0 0 12px rgb(var(--glow-rgb) / 26%); + --glow-md: 0 0 18px rgb(var(--glow-rgb) / 30%); + --glow-lg: 0 0 30px rgb(var(--glow-rgb) / 34%); + --glow-xl: 0 0 48px rgb(var(--glow-rgb) / 38%); + + --glow-inset-sm: inset 0 0 12px rgb(var(--glow-rgb) / 14%); + --glow-inset-md: inset 0 0 22px rgb(var(--glow-rgb) / 18%); + --glow-inset-lg: inset 0 0 32px rgb(var(--glow-rgb) / 22%); + + /* Text glow — the violet cast carried by every uppercase display heading. */ + --glow-text-rgb: 174 150 255; + --glow-text-sm: 0 0 10px rgb(var(--glow-text-rgb) / 35%); + --glow-text-md: 0 0 14px rgb(var(--glow-text-rgb) / 40%); + --glow-text-lg: 0 0 18px rgb(var(--glow-text-rgb) / 28%), 0 0 38px rgb(108 72 255 / 18%); +} + +/* ============================================================================= + 6. Elevation — plain drop shadows, no hue. Pairs with the glow scale above. + ============================================================================= */ +:root { + --elev-1: 0 3px 8px rgb(0 0 0 / 22%); + --elev-2: 0 8px 22px rgb(0 0 0 / 32%); + --elev-3: 0 14px 34px rgb(0 0 0 / 38%); + --elev-4: 0 24px 60px rgb(0 0 0 / 46%); +} + +/* ============================================================================= + 7. Signature gradients + + `--spectrum-angle` is read at the use site, so one token serves both the + 92deg text fills and the 90deg horizontal rails. + ============================================================================= */ +:root { + --gradient-spectrum: linear-gradient( + var(--spectrum-angle, 92deg), + var(--neon-cyan) 0%, + var(--neon-violet) 60%, + var(--neon-magenta) 100% + ); + + --gradient-rail: linear-gradient( + 90deg, + transparent, + var(--neon-cyan) 12%, + var(--neon-violet) 50%, + var(--neon-magenta) 88%, + transparent + ); +} + +/* ============================================================================= + 8. Motion + ============================================================================= */ +:root { + --dur-fast: 140ms; + --dur-base: 180ms; + --dur-slow: 320ms; + --dur-reveal: 640ms; + + --ease-out: cubic-bezier(0.22, 1, 0.36, 1); + --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1); + --ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1); +} - /* Space reserved for fixed `.main-header` */ +/* ============================================================================= + 9. Space and layout scale + ============================================================================= */ +:root { + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 24px; + --space-6: 36px; + --space-7: 56px; + --space-8: 80px; + + /* Section rhythm. Lives at :root rather than on `.landing` so the fixed + header can align its own padding to the same gutter. */ + --landing-gutter: clamp(44px, 7vw, 108px); + --section-pad-block: clamp(80px, 9vh, 120px); + --section-pad-block-band: clamp(56px, 6vh, 80px); + + /* Space reserved for fixed `.main-header`. */ --main-header-offset: 100px; } +/* ============================================================================= + 10. Radii + ============================================================================= */ +:root { + --radius-xs: 2px; + --radius-sm: 8px; + --radius-md: 10px; + --radius-lg: 16px; + --radius-xl: 18px; + --radius-pill: 999px; +} + +/* ============================================================================= + 11. Stacking order + ============================================================================= */ +:root { + --z-base: 0; + --z-raised: 1; + --z-sticky: 20; + --z-header: 50; + --z-overlay: 80; + --z-modal: 100; +} + @media (max-width: 768px) { :root { --main-header-offset: 128px; + --section-pad-block: 64px; + --section-pad-block-band: 44px; } } From 492f90882dbff89f35607e703f2a6822e1eed990 Mon Sep 17 00:00:00 2001 From: heyradcode Date: Sun, 9 Aug 2026 11:12:10 -0400 Subject: [PATCH 03/45] feat(website): add scroll-reveal motion primitives --- website/src/app/layout.tsx | 8 ++ .../src/components/landing/Backers/index.tsx | 4 +- .../components/landing/Community/index.tsx | 6 +- website/src/components/landing/Cta/index.tsx | 2 +- website/src/components/landing/Faq/index.tsx | 8 +- .../src/components/landing/Features/index.tsx | 8 +- .../src/components/landing/Footer/index.tsx | 8 +- website/src/components/landing/Hero/index.tsx | 10 +- .../components/landing/HowItWorks/index.tsx | 8 +- website/src/components/landing/Pets/index.tsx | 10 +- .../src/components/landing/Roadmap/index.tsx | 12 ++- .../src/components/landing/Stats/index.tsx | 4 +- .../components/landing/Testimonials/index.tsx | 8 +- website/src/components/layout/Layout.tsx | 3 + website/src/hooks/useRevealObserver.ts | 57 ++++++++++++ website/src/styles/globals.css | 1 + website/src/styles/motion.css | 93 +++++++++++++++++++ 17 files changed, 208 insertions(+), 42 deletions(-) create mode 100644 website/src/hooks/useRevealObserver.ts create mode 100644 website/src/styles/motion.css diff --git a/website/src/app/layout.tsx b/website/src/app/layout.tsx index dd9a2e25..58752308 100644 --- a/website/src/app/layout.tsx +++ b/website/src/app/layout.tsx @@ -22,9 +22,17 @@ export const metadata: Metadata = { description: 'Collect, battle and breed unique on-chain pets in the ultimate Crypto Pets adventure.', }; +/* Runs before first paint. Reveal targets start hidden only once this class is + present, so a blocked or failed bundle degrades to a fully visible page + instead of a blank one below the fold. */ +const REVEAL_GUARD = "document.documentElement.classList.add('js-reveal')"; + export default function RootLayout({ children }: { children: ReactNode }) { return ( + +