# Design Audit — Goodwalk (Codebase-wide)
Re-audited beyond ValuesSection. Read `sections.css` (1130 lines), `typography.css`, `responsive.css`, `variables.css`, and surveyed each major surface (Hero, Intro, PageHeader, Footer, FAQ, Instagram, Mobile Menu, Testimonial card). Findings below cite actual files/lines.
---
## Critical issues
### 1. Three different "eyebrow" components exist, sharing no DNA
**Problem.** The same UX element — small uppercase intro label above a heading — is rendered three incompatible ways:
- `.eyebrow` (typography.css:100): font-body, 12px, 700, `0.09em`, green text, no background.
- `.hero-kicker` (sections.css:69): font-body, 12px, 700, `0.08em`, yellow text in a *yellow pill with yellow border*.
- `.intro-kicker` (sections.css:408): font-body, 12px, 600 (weight differs), `0.18em` (tracking differs — over 2× the others), white-58% text with a *yellow rule prefix*.
Plus `.values-eyebrow`, `.booking-eyebrow`, `.footer-col-label`, `.intro-meta` are all variants.
**Root cause.** No eyebrow primitive in the system. Each section author re-implemented from scratch.
**Perception.** Reader senses different sections were designed at different times. Brand voice fragments.
**Fix.** One `` component. Three variants only. Migrate all six existing implementations.
**Class: System debt.**
---
### 2. Six different primary heading scales
**Problem.**
| Selector | Scale | Weight | Tracking |
|---|---|---|---|
| `.section-heading` | clamp(30, 4.6vw, 44) | 800 | -0.035em |
| `.hero-text h1` | clamp(34, 4vw, 56) | 800 | -0.045em |
| `.intro-headline` | clamp(30, 4.4vw, 54) | **500** | -0.02em |
| `.ph-title` | clamp(34, 4vw, 56) | 800 | -0.04em |
| `.info-block h2` | clamp(28, 2.4vw, 32) | 700 | -0.02em |
| `#instagram h2` | clamp(30, 3vw, 36) | 700 | -0.02em |
Six clamp formulas. Five tracking values. Weights ranging 500→800. The base `h2 { font-weight: 700 }` (typography.css:58) conflicts with `.section-heading { font-weight: 800 }` (typography.css:79) — actual rendered weight depends on whether the heading author remembered to apply the class.
**Root cause.** No `--text-display`, `--text-h1`, `--text-h2` tokens. Every author authors a fresh clamp.
**Perception.** Headings jump in size and weight as you scroll. The 500-weight `.intro-headline` reads as a different brand from the 800-weight `.hero h1` directly above it.
**Fix.** Define type tokens: `--text-display` (52/800/-0.04), `--text-h1` (44/800/-0.035), `--text-h2` (32/700/-0.02), `--text-h3` (22/700/-0.02), `--text-body-lead` (17/500/0), `--text-body` (16/400/0). Map every heading. Delete the per-section clamps.
**Class: Design debt + System debt.**
---
### 3. The Instagram section is a chromatic anomaly
**Problem.** Section background sequence on the homepage:
`#hero` (green) → `#intro` (green) → `#promise/#values` (off-white) → `#services` (white) → `#testimonials` (white) → `#info` (white) → `#newlead` (white) → `#instagram` (**solid #ffd100 full-bleed**) → `footer` (green).
The Instagram block is the only place brand yellow is used as a *page section*. It's also where pure black-on-yellow body text appears at `rgba(0, 0, 0, 0.6)` (sections.css:738).
**Root cause.** Brand yellow was treated as both an accent (CTAs, highlights) AND a surface (this section). Premium products pick one.
**Perception.** Reads as advertising, not editorial. Breaks the calm green/off-white rhythm. The user scrolls into a marketing banner mid-page.
**Fix.** Either (a) demote to a green section with yellow accents inside, or (b) shrink to a narrow card-on-off-white block. Reserve `var(--yellow)` for accents only.
**Class: Design debt + Conversion debt** (it sits right before footer — possibly the last impression).
---
### 4. Hero animation system runs on a different clock than the rest of the page
**Problem.** Hero entry: image 1.6s, text rise 0.85s, underline draw 0.9s @ 900ms delay, star pop 0.45s starting at 820ms. Total animation completes ~1.7s after load.
Rest of page: reveal-block now 0.3s opacity / 0.45s transform.
Mobile menu: 180–220ms.
Button hover: 0.16–0.22s.
**Root cause.** Hero was authored before the reveal/motion system existed. Never reconciled.
**Perception.** The hero feels heavy and ceremonial; the rest of the page feels snappy. Two products bolted together.
**Fix.** Cap all hero animations at 0.5s. Reduce delays — first text element by 80ms, each subsequent +60ms. Drop the underline-draw delay from 900ms to 350ms. Keep the elegance, lose the lethargy.
**Class: Polish debt.**
---
### 5. `--space-container-x-tablet` breaks the gutter rhythm
**Problem.** variables.css:145 — `--space-container-x-tablet: 30px`. Desktop value is `clamp(24px, 4vw, 48px)`. At 1024px width (the tablet breakpoint), 4vw = 41px. The tablet override drops gutters to 30px, then back up to 41–48px on desktop. A user resizing a window or rotating an iPad sees the gutters *narrow* then *widen*.
**Root cause.** A patch fix applied at the tablet breakpoint that doesn't share the desktop formula.
**Perception.** Visible engineering. Anyone who notices the layout shift on resize loses trust.
**Fix.** Either delete `--space-container-x-tablet` and let the clamp handle the breakpoint, or make tablet = `clamp(20px, 3vw, 32px)` so the curve is continuous.
**Class: System debt.**
---
### 6. Card system doesn't exist — each section invents its own
**Problem.** Same UX primitive (an elevated content card), shipped four ways:
| Component | Radius | Padding | Background | Shadow |
|---|---|---|---|---|
| `.testimonial-card` | 28px | 36px 32px | gradient panel→panel-soft | inset + shadow-md |
| `.faq details` | 16px | 18px 22px | surface-page | shadow-lg on open only |
| `.values-photo-card` | 28px → md mobile | n/a | beige | inset + card |
| `.booking-field-card` | 24px | 24px 22px | (varies) | (varies) |
Plus `.values-point` (no radius, pure surface), `.mobile-menu-links a` (16px radius), `.ph-media` (28px), `.intro-google` (pill).
**Root cause.** No `` primitive. Every author re-decides.
**Perception.** Adjacent sections feel like they were copy-pasted from different Behance projects.
**Fix.** One Card system: `--radius-lg` (20px) for cards, `--space-7` (32px) padding, `--shadow-card` standard. Variants: `card`, `card-elevated`, `card-quiet`. Migrate all four.
**Class: System debt + Design debt.**
---
### 7. Footer typography is a four-size jumble
**Problem.** Within the footer alone: 14px (`.footer-brand p`, `.footer-nav a`), 13px (`.footer-bottom`, `.footer-contact-link`), 12px (`.footer-back-top`, `.footer-social-invite`), 10px (`.footer-col-label`), plus 14px (`footer h4`). Five sizes in a quiet auxiliary surface.
Add opacities: `.footer-brand p` 0.72, `.footer-nav a` 0.72, `.footer-contact-link` 0.7, `.footer-bottom` 0.6, `.footer-back-top` 0.5, `.footer-col-label` 0.5.
**Root cause.** Each footer column was sized independently to hit visual targets, not to scale.
**Perception.** The footer feels busy in a quiet way — many small elements clamoring for slightly different attention.
**Fix.** Two sizes: 14px (links/copy) + 12px (label/legal). Two opacities: 0.85 (active text) + 0.55 (labels). Delete the rest.
**Class: Polish debt + UX debt.**
---
### 8. `.section-heading` color is pure `#000`, used on warm off-white backgrounds
**Problem.** typography.css:82 — `.section-heading { color: var(--text-strong); }` where `--text-strong: #000`. Promise, Values, Testimonials, Services, Info all render their primary heading in pure black on a warm `--off-white #f8f7f2`. Same pattern as the `.btn-yellow` issue I already fixed: pure black against a warm surface reads as harsh and clinical.
**Root cause.** `--text-strong` is a "darkest-possible" token used reflexively for headings instead of a heading-specific token.
**Perception.** Headings shout. The rest of the type — body in `var(--text)` (#2e3031), muted in green-cast grays — is warm. Headings are not.
**Fix.** `.section-heading { color: var(--text-heading); }` (which is `#1f2421`, near-black with a green cast). Or `var(--gw-green)` for full editorial treatment. The dark-green-on-warm-cream pairing is the brand's most premium register.
**Class: Design debt.**
---
### 9. Two Google trust-mark components live as duplicates
**Problem.** `.hero-trust-mark` (sections.css:153) and `.intro-google-mark` (sections.css:491). Same Google "G" circle, same white background, same shadow recipe with slightly different parameters (`0 2px 8px rgba(0,0,0,0.25)` vs `0 4px 12px rgba(0,0,0,0.25)`). Different sizes (28px vs 36px). Different parent chip layouts (`.hero-trust-chip` vs `.intro-google`).
**Root cause.** Copy-paste authoring. The second one was built without abstracting the first.
**Perception.** A user who scrolls hero → intro sees the same trust signal twice in slightly different sizes. Either feels redundant or sloppy depending on attention level.
**Fix.** One `` component. Two clean sizes (24, 36). Single shadow token.
**Class: System debt + Conversion debt** (trust signals matter; redundant ones dilute).
---
### 10. Letter-spacing on headings has four values
**Problem.** `-0.02em` (h2/h3, intro-headline, info-block h2), `-0.035em` (section-heading), `-0.04em` (ph-title), `-0.045em` (hero h1). Four bespoke tracking values.
**Root cause.** Each heading was tuned visually without a tracking scale.
**Perception.** Headings at similar sizes (`.intro-headline` 54px at `-0.02em` vs `.hero h1` 56px at `-0.045em`) feel like they're set in *different fonts*, when they're actually both Unbounded.
**Fix.** Two tracking values: `-0.035em` for display (36px+), `-0.02em` for everything else. Lock in the type tokens.
**Class: Design debt.**
---
### 11. Hero text relies on hand-numbered nth-child animation delays
**Problem.** sections.css:272–278 — `.hero-text > :nth-child(1) { animation-delay: 160ms; }` through `:nth-child(7)`. Seven hardcoded children. If the hero content shape changes (e.g., remove the kicker, add a phone CTA), the choreography breaks silently.
**Root cause.** Animation was authored against current markup, not against generic children.
**Perception.** Probably invisible to most users *until* it breaks — at which point an element pops in without animation while others slide.
**Fix.** Use CSS counter or a Svelte action that applies `--reveal-delay` per child. Or accept staggered delays via `:nth-child(n)` formula: `animation-delay: calc(160ms + (var(--i, 0)) * 100ms)`.
**Class: System debt + Polish debt.**
---
### 12. Mobile hero CTA shrinks to 12px font, 9px horizontal padding under 480px
**Problem.** responsive.css:767–774 — primary `.btn-yellow` becomes `padding: 13px 9px; font-size: 12px; line-height: 1.1` on iPhone SE-class widths. This is the *primary booking CTA* — the conversion target.
**Root cause.** Two CTAs side-by-side in a row at very narrow widths. Author chose to squeeze both rather than stack.
**Perception.** On a 320px screen, the booking button reads as cramped, low-confidence. CTA hierarchy collapses.
**Fix.** Keep buttons stacked under 480px (already done at 768px — extend the rule). Primary CTA: full-width, 14px font, 14px vertical padding. Secondary link below.
**Class: Conversion debt.**
---
### 13. Hardcoded shadows everywhere despite a complete shadow token set
**Problem.** Beyond ValuesSection, the same pattern repeats:
- `.testimonial-card:hover` — `0 8px 40px rgba(0, 0, 0, 0.08)` (sections.css:620). Not a token.
- `.hero-trust-mark` — `0 2px 8px rgba(0, 0, 0, 0.25)` (line 161). Not a token.
- `.intro-google-mark` — `0 4px 12px rgba(0, 0, 0, 0.25)` (line 499). Not a token.
- `.ph-media` — `0 16px 40px rgba(var(--ink-rgb), 0.08)` (line 1027). Not a token.
- `.intro-google` — uses inset shadow tuples directly (lines 470, 482).
**Root cause.** Authors didn't know which token to grab from 18 shadow options.
**Perception.** Elevations feel inconsistent — some cards lift more, some less, even when visually they're the same depth.
**Fix.** Five tokens, not eighteen: `--shadow-flat`, `--shadow-card`, `--shadow-elevated`, `--shadow-floating`, `--shadow-modal`. Migrate everything.
**Class: System debt.**
---
### 14. The `.intro-kicker` heading-overline pattern is design-y; nothing else on the page uses it
**Problem.** `.intro-kicker` (sections.css:408) uses a horizontal rule + uppercase text pattern (line + word). It's stylish. But it appears nowhere else. Adjacent sections use pill eyebrows, naked uppercase, or nothing.
**Root cause.** Section author drew inspiration that didn't propagate.
**Perception.** The intro feels visually distinct in a way that disconnects it from the rest of the page — over-designed compared to its neighbors.
**Fix.** Pick one: either commit and use the rule pattern in 2-3 more sections (rhythm); or drop it and use the standard `.eyebrow`.
**Class: Design debt.**
---
### 15. FAQ details radius (16px) doesn't match testimonial card (28px), values bento (20px), or photo card (28px) — they all sit on the same page
**Problem.** Cumulative effect of issue #6. The Info section has FAQ cards next to other cards with completely different silhouettes.
**Perception.** Visual rhythm dissolves.
**Fix.** All cards at `--radius-lg` (20px). Period.
**Class: Polish debt.**
---
### 16. Footer container padding doesn't share the global gutter system
**Problem.** sections.css:600 — `footer { padding: 60px 50px 32px; }`. The 50px is hardcoded. The mobile override at responsive.css:678 uses `var(--space-container-x-mobile)` — correct. But desktop is a literal.
**Same problem** at `#instagram { padding: 60px 50px; }` (sections.css:732) and `.ph-inner { padding: 0 50px; }` (line 949).
**Perception.** Footer/instagram/page-header gutters jitter against section gutters by a few pixels — the same issue I documented for ValuesSection, repeated across the codebase.
**Fix.** Replace every `50px` desktop gutter with `var(--space-container-x)`.
**Class: System debt.**
---
### 17. Section vertical rhythm collapses on mobile to a single value
**Problem.** variables.css:172–178 — on mobile, all four section padding tiers (`--space-section-featured-y`, `--space-section-support-y`, `--space-section-form-y`, `--space-section-page-y`) collapse to `--space-section-mobile-y` (40px). On desktop, featured sections breathe more than supporting. On mobile, every section has identical vertical padding.
**Root cause.** Pragmatic — mobile space is scarce. But the *intent* (featured sections feel more important) disappears.
**Perception.** Mobile users get a "flat" page where every section has equal visual weight. Hero → Intro → Promise → Services all feel like peers.
**Fix.** Two mobile tiers: `--space-section-featured-y-mobile: 56px`, `--space-section-mobile-y: 40px`. Featured sections get 16px more breathing room. Still respects mobile constraints.
**Class: Design debt.**
---
### 18. `#hero::after` gradient mask has a 4-stop gradient with literal RGB
**Problem.** sections.css:32–45 — a four-stop gradient `transparent 18% → 26% brand → 78% brand → solid brand 86%`. Plus a mobile-only override at lines 293–303 with *different* stops (`22% → 45% → 88% → 78%`). The math doesn't read as deliberate; it reads as tuned by trial and error.
**Root cause.** Hero image overlay was fine-tuned to one image asset. Different images won't behave the same.
**Perception.** Probably invisible. But if the hero photo ever changes (winter scene, different dog), the overlay won't land right.
**Fix.** Reduce to 2 stops: `transparent 30% → solid brand 95%`. Test against multiple photos.
**Class: Polish debt.**
---
### 19. Hero kicker mobile color is `rgba(white, 0.48)` on dark green
**Problem.** responsive.css:358 — `color: rgba(var(--white-rgb), 0.48)` for the kicker on mobile. At 10px uppercase 0.13em on a dark green-tinted gradient, that's somewhere around 4–5:1 contrast against the gradient. Borderline AA.
**Root cause.** Designer wanted the kicker quiet against the loud headline. But mobile users get a small device + outdoor sunlight + decreased contrast.
**Perception.** Some users won't read the kicker. Those who do will strain.
**Fix.** `rgba(white, 0.72)` at 11px on mobile. Still quiet, properly accessible.
**Class: UX debt.**
---
### 20. Three styling paradigms cohabit
**Problem.** The codebase uses:
1. **Global utility classes** (`.btn`, `.eyebrow`, `.section-heading`, `.testimonial-card`, `.faq`) defined in `src/lib/styles/*.css`.
2. **Component-scoped styles** inside Svelte `