v4
This commit is contained in:
+252
@@ -0,0 +1,252 @@
|
||||
# Design Critique — Goodwalk
|
||||
|
||||
Scope: read `variables.css` end-to-end, `ValuesSection.svelte` (open in IDE), `buttons.css`, and surveyed `HeroSection.svelte`. Citing concrete drift, not generalities.
|
||||
|
||||
---
|
||||
|
||||
## Issue list
|
||||
|
||||
### 1. Token system has decayed into noise — the design system is the problem
|
||||
|
||||
**Inconsistency.** `variables.css` defines **7 near-identical grays** (`--gray #59606d`, `--text-muted #4c5056`, `--text-muted-strong #4a4f55`, `--text-subtle #5f6369`, `--text-soft #666`, `--text-softest #6a6d72`, `--text-heading-soft #34363a`) and **6 near-identical off-whites** (`--surface-panel-soft`, `--surface-panel-muted`, `--surface-panel-warm`, `--surface-panel-cream`, `--surface-light`, `--off-white`). They differ by 1–3 hex points.
|
||||
|
||||
**Why it hurts.** A design system with this many close-but-different values is functionally no system at all. Every component author picks a slightly different one. This is the engine behind every other inconsistency in the codebase.
|
||||
|
||||
**Recommendation.** Collapse to: 3 grays (heading / body / muted), 2 off-whites (page / panel), 2 brand greens (primary / hover). Run a codemod replacing the deprecated tokens. Lock the file with a comment header forbidding additions without review.
|
||||
|
||||
**Good looks like.** Linear's full text scale is 4 grays. Stripe's surface scale is 3.
|
||||
|
||||
**Severity: Critical.**
|
||||
|
||||
---
|
||||
|
||||
### 2. No radius scale exists — `variables.css` has zero `--radius-*` tokens
|
||||
|
||||
**Inconsistency.** Components hand-pick: `40px` (button), `28px` (photo card), `22px` (photo card mobile), `18px` (bento + caption), `16px` (bento mobile + caption mobile), `11px` (point icon), `999px` (eyebrow). 7 different radii on one page.
|
||||
|
||||
**Why it hurts.** The eye notices radius mismatches more than almost any other inconsistency — it's the silhouette of every element. A 28px card next to an 18px card next to a 22px card reads as "ported from different templates."
|
||||
|
||||
**Recommendation.** Add tokens: `--radius-sm 8`, `--radius-md 14`, `--radius-lg 20`, `--radius-pill 999`. Pick **one** card radius (recommend 20). Buttons stay pill. Eyebrow stays pill. Everything else uses the scale.
|
||||
|
||||
**Severity: Critical.**
|
||||
|
||||
---
|
||||
|
||||
### 3. `ValuesSection.svelte` bypasses the token system almost entirely
|
||||
|
||||
**Inconsistency.** In ~480 lines of CSS in this single component:
|
||||
- Hardcoded colors not in tokens: `#000` (×3), `#0d1a0d` (×2), `#102010`, `#5a605f`, `#3f4348`, `#fff` (×3), `#ede4d2`, `#fcfbf6`.
|
||||
- Hardcoded shadows: `0 18px 34px rgba(17,20,24,0.08)` (line 227), `0 12px 24px rgba(...)` (line 262), `0 14px 34px rgba(...)` (line 295), `0 6px 16px rgba(33,48,33,0.18)` (line 505) — when `--shadow-card`, `--shadow-panel-strong`, `--shadow-badge` already exist for these.
|
||||
- Spacing values not on the 4px scale: `padding: 0 50px`, `padding: 38px 36px`, `padding: 32px 30px`, `padding: 13px 0`, `margin-top: 32px / 36px / 52px / 26px`.
|
||||
- One shadow token (`--shadow-panel-elevated`, line 479) — out of ~6 shadows. The other 5 are inline.
|
||||
|
||||
**Why it hurts.** This is design system drift in action. The tokens exist; they're being ignored. Every other component will do the same and the system becomes a museum.
|
||||
|
||||
**Recommendation.** Replace every literal in this file with a token. If a token doesn't exist, add it and use it everywhere it applies. Then add a stylelint rule blocking raw hex and raw shadow tuples in component CSS.
|
||||
|
||||
**Severity: Critical.**
|
||||
|
||||
---
|
||||
|
||||
### 4. Two `<h3>`s in the same section at totally different scales
|
||||
|
||||
**Inconsistency.** `.values-contrast-cell h3` is `clamp(20px, 1.9vw, 25px)`. `.values-points-title` (also `<h3>`) is `clamp(24px, 2.4vw, 32px)`. They sit in the same section, 200px apart vertically.
|
||||
|
||||
**Why it hurts.** Visual hierarchy breaks. The reader's brain expects the second H3 to be peer-level with the first. Instead it's larger than the first, then `.values-point h3` (also h3) drops back to 17px. Three H3s, three scales.
|
||||
|
||||
**Recommendation.** Adopt a real type scale token set: `--text-display`, `--text-h2`, `--text-h3`, `--text-h4`, `--text-body`, `--text-small`. Map every heading to a token. The "values points" introduction is functionally a sub-section header — use the same scale as the contrast cell titles, or promote it to h2-tier visually with proper hierarchy.
|
||||
|
||||
**Severity: Major.**
|
||||
|
||||
---
|
||||
|
||||
### 5. Photo caption layout breaks when `detail` is empty
|
||||
|
||||
**Inconsistency.** `.values-photo-caption` uses `justify-content: space-between` with two spans inside. Three of five photos have `detail: ''`. The result: an empty right-side gap and a left-pinned name in a half-empty pill. On mobile, the caption switches to `display: grid; justify-content: start` (line 569) which fixes it — but the desktop is broken.
|
||||
|
||||
**Why it hurts.** Looks accidental. A premium product never ships pills that are half-empty for half their content.
|
||||
|
||||
**Recommendation.** Either (a) make `detail` required, or (b) when detail is empty, render only the name and center it. Conditional class in Svelte: `class:values-photo-caption-solo={!photo.detail}`.
|
||||
|
||||
**Severity: Major.**
|
||||
|
||||
---
|
||||
|
||||
### 6. `.btn-yellow` uses pure black text
|
||||
|
||||
**Inconsistency.** `buttons.css:65` — `.btn-yellow { color: var(--text-strong); }` where `--text-strong: #000`. Every other text surface in the app uses `--gw-green #213021` or `--text-heading #1f2421`.
|
||||
|
||||
**Why it hurts.** Pure black on the brand yellow is the loudest color combo on the page. It pulls the eye like a warning sign and competes with the actual content. It's also the only place pure black appears outside `.values-inner .section-heading`.
|
||||
|
||||
**Recommendation.** Use `var(--gw-green)` on `.btn-yellow`. Contrast is still well above AA (≈8:1 on `#ffd100`) and the button immediately feels intentional and on-brand.
|
||||
|
||||
**Severity: Major.**
|
||||
|
||||
---
|
||||
|
||||
### 7. No `:focus-visible` states defined for `.btn`
|
||||
|
||||
**Inconsistency.** `buttons.css` defines `:hover` and `:active` only. No `:focus-visible`. Keyboard users get the browser default outline, which on a custom pill button with `border-radius: 40px` looks broken.
|
||||
|
||||
**Why it hurts.** Accessibility failure + perceived quality drop for power users. Tab through the page once and the button outlines clip the radius.
|
||||
|
||||
**Recommendation.**
|
||||
```css
|
||||
.btn:focus-visible {
|
||||
outline: 2px solid var(--gw-green);
|
||||
outline-offset: 3px;
|
||||
}
|
||||
.btn-green:focus-visible { outline-color: var(--yellow); }
|
||||
```
|
||||
|
||||
**Severity: Major.**
|
||||
|
||||
---
|
||||
|
||||
### 8. `.values-inner` uses hardcoded `padding: 0 50px`
|
||||
|
||||
**Inconsistency.** Line 187. The codebase already defines `--space-container-x: clamp(24px, 4vw, 48px)`. The 50px here doesn't match any other section's container padding and breaks the visual gutter rhythm down the page.
|
||||
|
||||
**Why it hurts.** When sections scroll past, the left/right gutters jitter by a few pixels between sections. Reads as cheap on a calibrated monitor.
|
||||
|
||||
**Recommendation.** Replace with `padding: 0 var(--space-container-x)`. Audit every section component and do the same — almost certainly this is repeated elsewhere.
|
||||
|
||||
**Severity: Major.**
|
||||
|
||||
---
|
||||
|
||||
### 9. Mobile eyebrow font-size drops to 11px
|
||||
|
||||
**Inconsistency.** Line 549 — `.values-eyebrow { font-size: 11px; }` on mobile. Combined with `letter-spacing: 0.08em` and uppercase, this is functionally close to unreadable on a real phone.
|
||||
|
||||
**Why it hurts.** Mobile readability and accessibility. Apple HIG and Material both recommend 12px minimum for body-adjacent text; uppercase tracked text should be 12–13px+ for legibility.
|
||||
|
||||
**Recommendation.** Mobile eyebrow: 12px, tracking ≤ 0.06em. Or drop uppercase on mobile entirely.
|
||||
|
||||
**Severity: Major.**
|
||||
|
||||
---
|
||||
|
||||
### 10. Featured photo card loses its prominence on mobile
|
||||
|
||||
**Inconsistency.** Lines 558–560 — `.values-photo-card-featured { grid-row: auto; min-height: 178px; }` on mobile. The whole point of the "featured" card is its larger size; on mobile it becomes a peer to the rest, and the layout decision evaporates.
|
||||
|
||||
**Why it hurts.** The featured card is the strongest brand image. On mobile (where the majority of traffic comes from for local services), it's neutered.
|
||||
|
||||
**Recommendation.** On mobile, make the featured card span both columns of the first row (`grid-column: 1 / -1`) with a larger min-height (~240px). Visual lead image, not a peer.
|
||||
|
||||
**Severity: Major.**
|
||||
|
||||
---
|
||||
|
||||
### 11. Reveal animation is slow
|
||||
|
||||
**Inconsistency.** Lines 645–647 — `opacity 0.55s, transform 0.7s` with a `translateY(24px)` reveal. The cubic-bezier is fine but the durations are dated.
|
||||
|
||||
**Why it hurts.** Premium products (Linear, Apple, Vercel) settle reveal animations at 0.25–0.4s. 0.7s feels like a 2015 marketing site.
|
||||
|
||||
**Recommendation.** `opacity 0.3s ease, transform 0.45s cubic-bezier(0.2, 0.8, 0.2, 1)`. Reduce `--reveal-distance` to 16px.
|
||||
|
||||
**Severity: Minor.**
|
||||
|
||||
---
|
||||
|
||||
### 12. The "with/without" contrast cell is the page's strongest moment but ends in a quiet brand voice mismatch
|
||||
|
||||
**Inconsistency.** The "good" cell ends with: *"That is what people are really buying: peace of mind, routine, and a dog who feels cared for."* — yellow text on green, font-size 13px. The footer is the punchline but it's the smallest type in the cell.
|
||||
|
||||
**Why it hurts.** Hierarchy inversion. The most quotable line is treated like a footnote.
|
||||
|
||||
**Recommendation.** Promote the footer to 15–16px, maintain the yellow color and family. Add a small top divider only on the negative cell so the positive cell's footer reads as continuous emphasis.
|
||||
|
||||
**Severity: Minor.**
|
||||
|
||||
---
|
||||
|
||||
### 13. `.values-bento` uses a 1px gap + ink-tinted background trick for hairlines
|
||||
|
||||
**Inconsistency.** Line 290 — `gap: 1px; background: rgba(17, 20, 24, 0.1);` to simulate dividers. This is fine technique but the resulting line color (`rgba(ink, 0.1)`) is darker than the actual borders elsewhere in the system (`--border-soft 0.05`, `--border-muted 0.08`).
|
||||
|
||||
**Why it hurts.** Bento dividers read heavier than every other border on the page. The card looks "boxier" than its neighbors.
|
||||
|
||||
**Recommendation.** Drop the gap-background trick to `rgba(var(--ink-rgb), 0.06)` or a real `--border-soft-strong` token. Match the rest of the system.
|
||||
|
||||
**Severity: Polish.**
|
||||
|
||||
---
|
||||
|
||||
### 14. `.values-contrast-num` ("01", "02") in `rgba(17, 20, 24, 0.22)` is barely visible
|
||||
|
||||
**Inconsistency.** Decorative numbering at ~3:1 contrast.
|
||||
|
||||
**Why it hurts.** If it's decorative, that's fine. But it's currently big enough to look like it's *supposed* to be read. Either commit to it being a design element (larger, more confident) or make it functional (numbered as a real step indicator).
|
||||
|
||||
**Recommendation.** Either bump to 32px+ with the same low opacity (treats it as a visual mark, like editorial step numbering) or remove entirely. Half-measures are the worst outcome.
|
||||
|
||||
**Severity: Polish.**
|
||||
|
||||
---
|
||||
|
||||
## Global Design System Drift
|
||||
|
||||
1. **Tokens written but not consumed.** `--shadow-card`, `--shadow-panel-strong`, `--surface-panel-warm`, `--text-muted`, `--border-soft` exist, but `ValuesSection.svelte` re-implements all of them inline. Likely repeated in every section component.
|
||||
2. **Spacing scale (`--space-1` … `--space-14`) is ignored.** Every padding/margin in `ValuesSection` is a literal pixel value off the 4px grid (`50px`, `38px`, `36px`, `30px`, `26px`, `22px`, `18px`, `13px`, `9px`).
|
||||
3. **No radius scale at all.** Every component invents its own.
|
||||
4. **No type scale.** Headings use `clamp()` with different formulas everywhere.
|
||||
5. **Seven grays, six off-whites, three greens** in the token file — the surface area of the system exceeds what any human can use consistently.
|
||||
6. **Hardcoded shadows.** The shadow tokens are a *complete* shadow system that nobody is calling.
|
||||
7. **Hover states use `translateY(-2px) scale(1.012)` everywhere on `.btn`** — but card hovers in `ValuesSection` use `transform: scale(1.06)`. No shared motion language.
|
||||
|
||||
---
|
||||
|
||||
## Fast Wins
|
||||
|
||||
1. **Find/replace every raw hex color in `ValuesSection.svelte`** with a token. ~30 minutes. Immediate consistency lift.
|
||||
2. **Replace `.values-inner { padding: 0 50px }` with `padding: 0 var(--space-container-x)`** and do the same in every section component. ~20 minutes. Fixes the section-to-section gutter jitter.
|
||||
3. **Add `--radius-sm/md/lg/pill` tokens and codemod components to use them.** Pick 20px as the universal card radius. ~1 hour.
|
||||
4. **Switch `.btn-yellow` text to `var(--gw-green)`.** 5 minutes. Big perceived-quality lift — the page stops shouting.
|
||||
5. **Add `:focus-visible` to `.btn`.** 5 minutes. Accessibility + polish.
|
||||
6. **Fix the photo caption layout when `detail` is empty.** 10 minutes. Removes the only broken-looking element on the page.
|
||||
7. **Bump mobile featured photo to `grid-column: 1 / -1`.** 2 minutes. Mobile hero moment.
|
||||
|
||||
---
|
||||
|
||||
## Premiumisation Opportunities
|
||||
|
||||
1. **Collapse the gray palette to 3 values** and the off-white palette to 2. Premium brands feel calm because their surface palette is small.
|
||||
2. **Speed up all reveal/hover transitions to 0.25–0.4s.** Slower motion now reads as a slow site.
|
||||
3. **Replace pure-black text (`#000`) with `var(--gw-green)` or `var(--text-heading)` everywhere.** True black on warm off-white feels harsh; the dark green keeps it editorial.
|
||||
4. **Tighten letter-spacing on display headings** to `-0.03em`. Mid-2020s premium look. Bonus: add `text-wrap: balance` to all h2s.
|
||||
5. **Replace the bento dividers** with `--border-soft` and add a barely-visible inner highlight (`inset 0 1px 0 rgba(255,255,255,0.5)`) for the elevated feel Linear uses on dark surfaces.
|
||||
6. **Promote the "punchline" copy** (the contrast-footer lines) to body-lead size. Stop hiding the best writing in 13px.
|
||||
7. **Inline social proof under the contrast cells.** "Joined by 200+ Auckland owners" or a row of three small testimonial avatars with rating. The section currently asserts emotional benefits without proof — easy E-E-A-T win.
|
||||
|
||||
---
|
||||
|
||||
## Mobile Audit
|
||||
|
||||
- **Eyebrow text is 11px** with uppercase + 0.08em tracking — sub-legible. Bump to 12px, drop tracking to 0.06em.
|
||||
- **Featured photo loses its purpose** — collapses to peer-card. Fix with `grid-column: 1 / -1; min-height: 240px`.
|
||||
- **Photo caption switches to vertical stack** (good) but `.values-photo-detail` runs through `-webkit-line-clamp: 2` even when detail is empty — leaves a weird invisible vertical reserve. Conditionally hide the element when empty.
|
||||
- **Container padding** uses `--space-container-x-mobile` (24px). Other components on the site may use the desktop 50px hardcode plus naive scaling — verify all sections collapse to a consistent 24px gutter.
|
||||
- **Tap targets** — the photo cards are large (~178px tall) and tappable hover scales aren't useful on touch. Hover scale lifts to `1.06` on tap-through devices look glitchy on iOS Safari. Wrap in `@media (hover: hover)` (already done — good) but verify the photo `:hover` rule is also gated (line 242 — good, it is).
|
||||
- **No visible scroll affordance** between the contrast cells and the values-points header — 30px gap on mobile (line 617). Reads as cramped.
|
||||
- **`.btn` padding `13px 28px`** on mobile yields ~44px tall tap targets only if line-height holds. Hero CTA stack untested in this audit but worth verifying tap area ≥48px on the primary booking CTA.
|
||||
|
||||
---
|
||||
|
||||
## Final Verdict
|
||||
|
||||
**What makes it feel inconsistent.** A real design system exists in `variables.css` but the components don't use it. Components ship with raw hex, raw pixels, raw shadow tuples, and inconsistent radii. The token file has too many near-duplicate values, which is what enabled the drift in the first place.
|
||||
|
||||
**What is preventing it from feeling premium.** Loud black-on-yellow CTA, slow reveal animations, seven different gray tones in the same viewport, radius drift between cards, missing focus states, and one or two broken elements (empty captions, mobile featured card collapse) that read as accidental rather than intentional.
|
||||
|
||||
**Top 5 highest-ROI changes:**
|
||||
|
||||
1. **Collapse the token palette** (3 grays, 2 off-whites, add `--radius-*` scale) — this is the precondition for everything else holding.
|
||||
2. **Codemod `ValuesSection.svelte` (and peer components) to consume tokens exclusively** — kills 80% of the visual inconsistency in one pass.
|
||||
3. **Switch `.btn-yellow` text to `--gw-green` and add `:focus-visible`** — instant perceived-quality lift on the most-clicked element.
|
||||
4. **Speed up reveal/hover transitions to 0.25–0.4s** — the single biggest "feels modern" lever.
|
||||
5. **Fix the photo caption (empty-detail case) and the mobile featured card** — two small fixes that remove the only "homemade" moments on the section.
|
||||
|
||||
These are systems-level fixes, not redesigns. The CLAUDE.md mandate to preserve the WordPress visual design is respected — none of this changes layout, color brand, or hierarchy. It tightens the existing system into something that holds together.
|
||||
Reference in New Issue
Block a user