From 028983097296e5f75ff72cb3a3e6eee17002a6c0 Mon Sep 17 00:00:00 2001 From: FIlip Dorian Pindej Date: Mon, 17 Aug 2026 22:21:39 +0200 Subject: [PATCH 1/2] feat(web): redesign UI as a geological survey sheet Replace the near-black/cyan look with a basalt, chalk, and iron palette, Bricolage Grotesque display type with IBM Plex body and mono, square corners and hairline rules. Add StrataColumn, a live column of the project's layers sized by file share, shown on every wizard step. Restyle header (title block), wizard progress (sheet index), landing, stack, review, dependency graph, feature cards, file tree, and the FAQ, Why, and Changelog pages; drop glow filters, particles, and the typewriter effect. Changelog page now renders inline code. --- CLAUDE.md | 9 +- packages/web/src/app.css | 163 ++++++++++++++---- packages/web/src/app.html | 6 +- .../src/lib/components/DependencyGraph.svelte | 65 ++----- .../web/src/lib/components/FeatureCard.svelte | 38 ++-- .../web/src/lib/components/FileTree.svelte | 8 +- packages/web/src/lib/components/Header.svelte | 73 +++----- .../web/src/lib/components/NameStep.svelte | 51 +++--- .../web/src/lib/components/ReviewStep.svelte | 89 ++++------ .../web/src/lib/components/StackStep.svelte | 135 +++++++-------- .../src/lib/components/StrataColumn.svelte | 153 ++++++++++++++++ .../src/lib/components/WizardProgress.svelte | 61 +++---- packages/web/src/routes/+page.svelte | 143 ++++++--------- .../web/src/routes/changelog/+page.svelte | 35 ++-- packages/web/src/routes/faq/+page.svelte | 24 +-- packages/web/src/routes/why/+page.svelte | 32 ++-- 16 files changed, 599 insertions(+), 486 deletions(-) create mode 100644 packages/web/src/lib/components/StrataColumn.svelte diff --git a/CLAUDE.md b/CLAUDE.md index 44609e5..d46a50f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -91,12 +91,15 @@ All changes to `packages/web` must follow these rules. Do not ask - just enforce ### Visual Standards -- Dark theme only - all colors from `app.css` theme variables +- Visual language is a geological survey sheet: basalt background, chalk text, one warm iron accent, square corners (no `rounded-*`), hairline borders, no glow/blur/gradient blobs +- Dark theme only - all colors from `app.css` theme variables; strata tones (`--color-strata-*`) are reserved for the `StrataColumn` legend and architecture diagrams +- Type: `.display` / `.display-md` (Bricolage Grotesque) for headlines and big numbers, IBM Plex Sans for body, `font-mono` (IBM Plex Mono) and `.label` for labels, stats, and technical text +- The `StrataColumn` (project layers by file share) is the signature element - it appears on every wizard step and must stay in sync with the resolved features - No white flash - inline `background` on `` for instant dark paint -- No browser default focus rings - use custom `focus-visible` styles with accent color -- Monospace font (`font-mono`) for labels, stats, and technical text +- No browser default focus rings - global `focus-visible` outline in accent color - Touch-friendly spacing on mobile, compact on desktop - Tooltips only on hover-capable devices (`@media (hover: hover)`) +- Respect `prefers-reduced-motion` (global rule in `app.css`) ### Anime.js diff --git a/packages/web/src/app.css b/packages/web/src/app.css index 97e33d3..49d2924 100644 --- a/packages/web/src/app.css +++ b/packages/web/src/app.css @@ -1,28 +1,41 @@ @import 'tailwindcss'; @theme { - --font-sans: 'Inter', system-ui, sans-serif; - --font-mono: 'JetBrains Mono', 'Fira Code', ui-monospace, monospace; + --font-sans: 'IBM Plex Sans', system-ui, sans-serif; + --font-mono: 'IBM Plex Mono', ui-monospace, monospace; + --font-display: 'Bricolage Grotesque', 'IBM Plex Sans', system-ui, sans-serif; - --color-bg: #08080d; - --color-surface: #111118; - --color-surface-raised: #191922; - --color-border-subtle: #222233; - --color-border-active: #334155; + /* Basalt, shale, chalk - a survey sheet read at night */ + --color-bg: #121416; + --color-surface: #191c1f; + --color-surface-raised: #212529; + --color-border-subtle: #2a2f35; + --color-border-active: #454c55; - --color-text-primary: #ececf1; - --color-text-secondary: #8e8ea0; - --color-text-muted: #555566; + --color-text-primary: #ebe6da; + --color-text-secondary: #a39d90; + --color-text-muted: #6d6860; - --color-accent: #06b6d4; - --color-accent-light: #22d3ee; - --color-accent-dim: #06b6d415; + /* Iron oxide - the one warm accent */ + --color-accent: #e08a3c; + --color-accent-light: #f0a55c; + --color-accent-dim: #e08a3c1f; - --color-emerald: #10b981; - --color-emerald-dim: #10b98115; + /* Lichen - success */ + --color-emerald: #8fb573; + --color-emerald-dim: #8fb5731a; - --color-amber: #f59e0b; - --color-amber-dim: #f59e0b14; + /* Sulfur - caution */ + --color-amber: #d9b44a; + --color-amber-dim: #d9b44a17; + + /* Strata tones - one per feature group, used only in the column and legend */ + --color-strata-core: #7d8590; + --color-strata-auth: #c39a5b; + --color-strata-infra: #6f8ea3; + --color-strata-addon: #b3746a; + --color-strata-tooling: #8fa37a; + --color-strata-frontend: #d8d1c2; } html { @@ -38,38 +51,102 @@ body { } ::selection { - background-color: #06b6d459; + background-color: #e08a3c66; } -/* Typewriter animation */ -@keyframes typewriter { - from { - max-width: 0; +@layer components { + /* Display type: condensed, heavy, tight */ + .display { + font-family: var(--font-display); + font-variation-settings: + 'opsz' 96, + 'wdth' 85; + font-weight: 800; + letter-spacing: -0.02em; + line-height: 0.95; + } + + .display-md { + font-family: var(--font-display); + font-variation-settings: + 'opsz' 48, + 'wdth' 90; + font-weight: 700; + letter-spacing: -0.015em; + line-height: 1.05; } - to { - max-width: 8ch; + + /* Survey-sheet label: small caps mono */ + .label { + font-family: var(--font-mono); + font-size: 10px; + letter-spacing: 0.14em; + text-transform: uppercase; + color: var(--color-text-muted); } } -@keyframes blink { - 0%, - 100% { - opacity: 1; +/* Hatch fills for strata layers */ +.hatch-solid { + background-color: var(--strata); +} +.hatch-diag { + background-color: color-mix(in srgb, var(--strata) 55%, var(--color-bg)); + background-image: repeating-linear-gradient(135deg, var(--strata) 0 1.5px, transparent 1.5px 7px); +} +.hatch-cross { + background-color: color-mix(in srgb, var(--strata) 45%, var(--color-bg)); + background-image: + repeating-linear-gradient(45deg, var(--strata) 0 1px, transparent 1px 8px), + repeating-linear-gradient(135deg, var(--strata) 0 1px, transparent 1px 8px); +} +.hatch-dots { + background-color: color-mix(in srgb, var(--strata) 40%, var(--color-bg)); + background-image: radial-gradient(var(--strata) 1.1px, transparent 1.2px); + background-size: 7px 7px; +} +.hatch-brick { + background-color: color-mix(in srgb, var(--strata) 50%, var(--color-bg)); + background-image: + repeating-linear-gradient(0deg, var(--strata) 0 1px, transparent 1px 8px), + repeating-linear-gradient(90deg, var(--strata) 0 1px, transparent 1px 16px); +} +.hatch-wave { + background-color: color-mix(in srgb, var(--strata) 60%, var(--color-bg)); + background-image: repeating-linear-gradient(0deg, var(--strata) 0 1px, transparent 1px 5px); +} + +@layer components { + .btn-primary { + display: inline-flex; + min-height: 44px; + align-items: center; + gap: 0.5rem; + background: var(--color-accent); + color: var(--color-bg); + padding: 0.625rem 1.5rem; + font-family: var(--font-mono); + font-size: 0.875rem; + font-weight: 500; + transition: + background-color 0.15s, + opacity 0.15s; + } + .btn-primary:hover:not(:disabled) { + background: var(--color-accent-light); } - 50% { - opacity: 0; + .btn-primary:disabled { + opacity: 0.4; } } -.typewriter { - display: inline-flex; - overflow: hidden; - white-space: nowrap; - animation: typewriter 1s steps(7) 0.3s both; +/* Focus ring - iron, offset, no browser default */ +:where(button, a, summary, [tabindex]):focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; } - -.cursor-blink { - animation: blink 1s step-end infinite; +:where(button, a, summary, [tabindex]):focus:not(:focus-visible) { + outline: none; } /* Hide scrollbar for horizontal nav strips */ @@ -92,3 +169,13 @@ body { background: var(--color-border-subtle); border-radius: 3px; } + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} diff --git a/packages/web/src/app.html b/packages/web/src/app.html index 67d7717..d19df5e 100644 --- a/packages/web/src/app.html +++ b/packages/web/src/app.html @@ -1,5 +1,5 @@ - + @@ -7,7 +7,7 @@ name="description" content="Generate .NET 10 API projects with auth, email, jobs, file storage, and more. Pick features, download a complete solution that builds and tests pass." /> - + diff --git a/packages/web/src/lib/components/DependencyGraph.svelte b/packages/web/src/lib/components/DependencyGraph.svelte index fb4357b..2fafe85 100644 --- a/packages/web/src/lib/components/DependencyGraph.svelte +++ b/packages/web/src/lib/components/DependencyGraph.svelte @@ -6,7 +6,6 @@ const W = 88; const H = 30; - const R = 6; const HIT_PAD = 16; interface GNode { @@ -192,11 +191,9 @@ }); -
+
- - {generator.isFrontendEnabled ? 'Full stack' : 'API only'} - backend dependencies - + Backend dependencies click nodes to toggle
- + - - - - - - - - - - + - + required - + features - + extensions @@ -241,19 +229,11 @@ class="gedge" d={d} fill="none" - stroke={active ? '#06b6d4' : '#222233'} + stroke={active ? 'var(--color-accent)' : 'var(--color-border-active)'} stroke-width={active ? 1.2 : 0.6} - opacity={active ? 0.7 : 0.25} + opacity={active ? 0.8 : 0.4} style="transition: stroke 0.3s, stroke-width 0.3s, opacity 0.3s;" /> - {#if active} - - - - - - - {/if} {/each} @@ -277,7 +257,6 @@ y={node.cy - H / 2 - HIT_PAD} width={W + HIT_PAD * 2} height={H + HIT_PAD * 2} - rx={R + 2} fill="transparent" /> {/if} @@ -287,11 +266,10 @@ y={node.cy - H / 2} width={W} height={H} - rx={R} - fill={on ? '#06b6d415' : '#191922'} - stroke={on ? '#06b6d4' : '#222233'} - stroke-width={on ? 1.5 : 0.8} - filter={on ? 'url(#glow)' : 'none'} + rx="1" + fill={on ? 'var(--color-accent-dim)' : 'var(--color-surface)'} + stroke={on ? 'var(--color-accent)' : 'var(--color-border-subtle)'} + stroke-width={on ? 1.2 : 0.8} style="transition: fill 0.3s, stroke 0.3s, stroke-width 0.3s;" /> {#if isRequired} - - - + {/if} {/each} @@ -325,10 +296,10 @@ {#if hoveredNode && tips[hoveredNode]}
- + {nodes.find((n) => n.id === hoveredNode)?.label} - @@ -338,7 +309,7 @@