Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).

### Added

- Web UI redesign: a "survey sheet" visual language with a basalt/chalk/iron palette, Bricolage Grotesque display type with IBM Plex body and mono, square corners and hairline rules
- Strata column: every wizard step shows the project as layered rock, one layer per feature with thickness proportional to its share of files, updating live as features are toggled

- Machine-readable error codes on every ProblemDetails response: `ErrorMessages` entries are now `Error` records (stable snake_case `code` + message), `Result.Failure()` takes an `Error`, and `ProblemFactory` writes the code to the `code` extension (framework-generated bodies get `validation_failed` or a snake_case reason phrase such as `not_found`). New `ProblemDetailsSchemaTransformer` documents the extension in OpenAPI, and `ErrorMessagesTests` enforce code naming and uniqueness
- Hangfire-backed email delivery: with both `email` and `jobs` enabled, transactional emails are queued as `EmailDeliveryJob` via `BackgroundEmailService` and retried automatically (5 attempts, 30s to 1h backoff) instead of being lost on SMTP failure. Without `jobs`, `SmtpEmailService` still sends inline
- New API tests (`ProblemFactoryTests`, `ProblemDetailsCodeTests`, `ProblemDetailsAssert` fixture) and component tests for the email job pipeline
- Frontend: `getErrorCode()` and `getErrorMessage(error, fallback, messagesByCode)` translate by backend code; login form and OAuth callback page map on codes instead of English `detail` text

### Changed

- Header is a title block with a strata mark and revision number; the wizard progress is a numbered sheet index (01 Name, 02 Stack, 03 Download)
- Landing page leads with a headline and the project name field beside the strata column; FAQ, Why, and Changelog pages restyled to match
- Dependency graph, presets, architecture selector, feature cards, review stats, and file tree restyled; glow filters, traveling particles, and the typewriter effect removed
- Changelog page renders inline code in entries

### Fixed

- Frontend API proxy buffers request bodies so backend 401 responses pass through instead of surfacing as 502 on Node 24 (undici streaming issue)
Expand Down
9 changes: 6 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<html>` 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

Expand Down
163 changes: 125 additions & 38 deletions packages/web/src/app.css
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 */
Expand All @@ -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;
}
}
6 changes: 3 additions & 3 deletions packages/web/src/app.html
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<!doctype html>
<html lang="en" style="background: #08080d; color: #ececf1">
<html lang="en" style="background: #121416; color: #ebe6da">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
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."
/>
<meta name="theme-color" content="#08080d" />
<meta name="theme-color" content="#121416" />
<meta
name="keywords"
content=".NET, .NET 10, API generator, Clean Architecture, project generator, SvelteKit, PostgreSQL, Aspire, C#, dotnet"
Expand Down Expand Up @@ -64,7 +64,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wdth,wght@12..96,75..100,400..800&family=IBM+Plex+Mono:wght@400;500;600&family=IBM+Plex+Sans:wght@400;500;600&display=swap"
rel="stylesheet"
/>

Expand Down
Loading