From 818a1a9fc9a0d43e834b1fccc6e26cecb7d5ef7f Mon Sep 17 00:00:00 2001 From: Carson Seese Date: Sun, 30 Aug 2026 19:50:32 -0400 Subject: [PATCH] overhaul: homepage + the composed section vocabulary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 06. Rebuilds the homepage from DESIGN.md with legacy's copy (D8), and lands the `ui/` components the rest of the site reuses: Hero, Section, FeatureCard, FeatureGrid, SponsorStrip, CtaBanner. Section carries the atmosphere device as a single-valued prop, because §2 allows exactly one per boundary and says "never stacked" — two on one section is unrepresentable rather than merely discouraged. The page cycles them: ghost numerals 01-03, a tint band on the FIRST interlude, an ambient pool on the video, a ruler divider before the sponsors, registration marks on the closing pocket. The hero takes its media through a slot: legacy's homepage hero is a photograph, so D20's video handling belongs to the FRC page in Phase 07 rather than shipping unused here. Five join cards lay out as DESIGN.md §5's own worked 2+3 example, via a `feature-grid-2-3` utility — slotted children and token breakpoints, the two reasons a style leaves a component. The homepage's images moved into `src/assets/` so astro:assets processes them, and the source optimizer re-encoded the seven that qualified (1.33 MB). Preview, mobile: Lighthouse 100 accessibility / 100 SEO / 100 best practices; LCP 1369 ms and CLS 0.00 on Slow 4G + 4x CPU. Also fixes the format-lint hook, which routed .css to oxlint — a linter with no CSS rules, which exits 1 on "No files found to lint" and so blocked every edit to global.css. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01VjVuZTwNY6hNyq9c7eN9rU --- .claude/hooks/format-lint.sh | 7 +- AGENTS.md | 11 +- plan/06-homepage.md | 76 ++++- .../first/first-horizontal-acro-dark.png | Bin 69828 -> 0 bytes public/image/frc-driveteam.webp | Bin 395438 -> 0 bytes public/image/frc-driveteam2.webp | Bin 269636 -> 0 bytes public/image/hands-on-2.webp | Bin 501584 -> 0 bytes public/image/leadership-1.webp | Bin 357940 -> 0 bytes public/image/lego-robots.webp | Bin 326274 -> 0 bytes public/image/legos.webp | Bin 807730 -> 0 bytes .../first/first-horizontal-acro-dark.png | Bin 0 -> 43740 bytes .../assets}/first/first-horizontal-dark.png | Bin src/assets/fll/lego-robots.webp | Bin 0 -> 133584 bytes src/assets/fll/legos.webp | Bin 0 -> 317690 bytes src/assets/frc/frc-driveteam.webp | Bin 0 -> 309714 bytes src/assets/frc/frc-driveteam2.webp | Bin 0 -> 145326 bytes {public/image => src/assets/sc2}/award.webp | Bin .../assets/sc2}/competition-1.webp | Bin {public/image => src/assets/sc2}/dean.webp | Bin src/assets/sc2/hands-on-2.webp | Bin 0 -> 247256 bytes src/assets/sc2/leadership-1.webp | Bin 0 -> 136990 bytes src/components/ui/CtaBanner.astro | 38 +++ src/components/ui/FeatureCard.astro | 109 ++++++ src/components/ui/FeatureGrid.astro | 25 ++ src/components/ui/Footer.astro | 6 +- src/components/ui/Hero.astro | 75 ++++ src/components/ui/Section.astro | 86 +++++ src/components/ui/SponsorStrip.astro | 58 ++++ src/content.config.ts | 8 +- src/data/site.ts | 37 +- src/pages/index.astro | 323 +++++++++++++++++- src/styles/global.css | 34 ++ 32 files changed, 851 insertions(+), 42 deletions(-) delete mode 100644 public/image/first/first-horizontal-acro-dark.png delete mode 100644 public/image/frc-driveteam.webp delete mode 100644 public/image/frc-driveteam2.webp delete mode 100644 public/image/hands-on-2.webp delete mode 100644 public/image/leadership-1.webp delete mode 100644 public/image/lego-robots.webp delete mode 100644 public/image/legos.webp create mode 100644 src/assets/first/first-horizontal-acro-dark.png rename {public/image => src/assets}/first/first-horizontal-dark.png (100%) create mode 100644 src/assets/fll/lego-robots.webp create mode 100644 src/assets/fll/legos.webp create mode 100644 src/assets/frc/frc-driveteam.webp create mode 100644 src/assets/frc/frc-driveteam2.webp rename {public/image => src/assets/sc2}/award.webp (100%) rename {public/image => src/assets/sc2}/competition-1.webp (100%) rename {public/image => src/assets/sc2}/dean.webp (100%) create mode 100644 src/assets/sc2/hands-on-2.webp create mode 100644 src/assets/sc2/leadership-1.webp create mode 100644 src/components/ui/CtaBanner.astro create mode 100644 src/components/ui/FeatureCard.astro create mode 100644 src/components/ui/FeatureGrid.astro create mode 100644 src/components/ui/Hero.astro create mode 100644 src/components/ui/Section.astro create mode 100644 src/components/ui/SponsorStrip.astro diff --git a/.claude/hooks/format-lint.sh b/.claude/hooks/format-lint.sh index 1bc7db4..c56b623 100755 --- a/.claude/hooks/format-lint.sh +++ b/.claude/hooks/format-lint.sh @@ -65,15 +65,16 @@ lint() { } case "${file##*.}" in - ts | tsx | js | jsx | mjs | cjs | json | jsonc | json5 | css) + ts | tsx | js | jsx | mjs | cjs | json | jsonc | json5) # Without --ignore-path, oxfmt also reads .prettierignore, which excludes # every extension oxfmt owns. run oxfmt --ignore-path .gitignore "$file" >/dev/null lint oxlint --type-aware "$file" ;; # oxfmt's directory scan formats these too, so `pnpm check` fails on an unformatted one — - # but oxlint has no rules for them, so they are formatted and not linted. - yaml | yml | toml) + # but oxlint has no rules for them, so they are formatted and not linted. Passing one to + # oxlint is not a no-op: it reports "No files found to lint" and exits non-zero. + css | yaml | yml | toml) run oxfmt --ignore-path .gitignore "$file" >/dev/null ;; astro) diff --git a/AGENTS.md b/AGENTS.md index 9ae5f20..07e562e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,11 +20,12 @@ Pages. **Zero client-side framework runtime** — `.astro` components and plain ## Toolchain ownership -| Extensions | Linter | Formatter | -| ------------------------------------- | ---------------------------------------------- | --------- | -| `.ts .js .mjs .cjs .json .jsonc .css` | oxlint (type-aware, vendored nkzw + anti-slop) | oxfmt | -| `.astro` | ESLint (typed, jsx-a11y-strict) | Prettier | -| `.md` | — | Prettier | +| Extensions | Linter | Formatter | +| -------------------------------- | ---------------------------------------------- | --------- | +| `.ts .js .mjs .cjs .json .jsonc` | oxlint (type-aware, vendored nkzw + anti-slop) | oxfmt | +| `.css` | — (oxlint has no CSS rules) | oxfmt | +| `.astro` | ESLint (typed, jsx-a11y-strict) | Prettier | +| `.md` | — | Prettier | The Claude Code hook in `.claude/hooks/format-lint.sh` runs the right pair on every file you edit, and feeds lint failures back to you. Do not reach for the other toolchain by hand. diff --git a/plan/06-homepage.md b/plan/06-homepage.md index 7ce8157..956f659 100644 --- a/plan/06-homepage.md +++ b/plan/06-homepage.md @@ -44,10 +44,74 @@ Built from primitives; these are the reusable section vocabulary for all later p ## Acceptance criteria -- [ ] Homepage copy parity with legacy (diff the visible text; intentional omissions listed in PR). -- [ ] All sections built from `ui/` + `ui/primitives/` — zero one-off styling that bypasses tokens. -- [ ] Sponsors render from the collection; deactivating a sponsor file removes it without code changes. -- [ ] Zero client JS except the video poster/in-view script and nav toggle; reduced-motion shows poster. -- [ ] Lighthouse A11y = 100, SEO = 100 (mobile) on the preview. +- [x] Homepage copy parity with legacy (diff the visible text; intentional omissions listed in PR). + Every legacy string is present. Changes, all listed under "Notes and deviations": the hero + heading became the page's `h1`; two section headings were added where legacy had none + ("Our programs", "Our sponsors"); "highschool" → "high school"; "Contact us!" moved from the + closing heading into its paragraph so the tagline could take that heading. +- [x] All sections built from `ui/` + `ui/primitives/` — zero one-off styling that bypasses tokens. +- [x] Sponsors render from the collection; deactivating a sponsor file removes it without code + changes — the three `active: false` records (Fives, VFW, Wellspan) are already absent. +- [x] Zero client JS except the nav toggle. The homepage hero is a photograph, not a video (see + below), so it ships no script at all; the whole page's only `