From 37cc8e79b9a04e265692ad9b77d239e098a65283 Mon Sep 17 00:00:00 2001 From: Carson Seese Date: Fri, 28 Aug 2026 01:01:47 +0000 Subject: [PATCH 1/3] =?UTF-8?q?overhaul:=20app=20shell=20=E2=80=94=20BaseL?= =?UTF-8?q?ayout,=20Seo,=20JSON-LD,=20Navbar,=20Footer,=20404?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chrome every page shares, with SEO correctness built into the type signature rather than left to each page: BaseLayout requires title and description, so omitting either is a compile error instead of something a reviewer has to catch. Seo emits title, description, canonical, the full Open Graph set with image dimensions, and a Twitter card. No keywords meta — legacy carried 22 terms for a signal search engines dropped years ago. jsonld.ts holds typed builders: NGO (the specific type for a nonprofit, not bare Organization) on every page, WebSite on the homepage, and breadcrumbs ready for the nested pages in Phases 07-08. Navbar follows D26: the real lockup, full-width on desktop and the square mark on mobile, never a rebuilt gear plus the name in Inter. Programs is a real link to /programs with a dropdown in addition, so touch and no-JS both get a destination — verified that the panel opens on keyboard focus and Tab moves into it. The mobile sheet is full-height with 44px targets, locks body scroll, and closes on Esc with focus returned. The only script is the ~25-line toggle. Footer is a recessed card band carrying the lockup and the engineering title block. It points Donate at the real /donate page rather than legacy's /wiki/donations, and adds a Programs column — with the mobile sheet closed, the footer is otherwise the only place a program link appears. sharp becomes a direct dependency: the first real use made astro:assets need it, which allowBuilds already anticipated. The icon set is rendered from the square mark rather than committed by hand; the apple-touch icon is flattened onto the brand ground because iOS ignores transparency. exactOptionalPropertyTypes forced one signature choice worth knowing: a layout that forwards optional props passes explicit undefined, which that flag treats as distinct from an absent prop, so the receiving props are declared `?: T | undefined` instead of filtering props at every call site. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01YRfxMh7FLjQtDbb1BEsCbR --- knip.jsonc | 8 +- plan/05-app-shell.md | 37 +- public/apple-touch-icon.png | Bin 0 -> 5166 bytes {legacy/src/app => public}/favicon.ico | Bin public/icon-192.png | Bin 0 -> 6234 bytes public/icon-512.png | Bin 0 -> 20378 bytes public/icon.svg | 1 + .../og/default.png | Bin public/site.webmanifest | 15 + src/components/JsonLd.astro | 15 + src/components/Seo.astro | 78 ++ src/components/ui/Footer.astro | 121 ++ src/components/ui/Navbar.astro | 237 ++++ src/layouts/BaseLayout.astro | 81 ++ src/layouts/ProgramLayout.astro | 31 + src/lib/jsonld.ts | 70 ++ src/pages/404.astro | 64 + src/pages/index.astro | 41 +- src/pages/styleguide.astro | 1039 ++++++++--------- 19 files changed, 1290 insertions(+), 548 deletions(-) create mode 100644 public/apple-touch-icon.png rename {legacy/src/app => public}/favicon.ico (100%) create mode 100644 public/icon-192.png create mode 100644 public/icon-512.png create mode 100644 public/icon.svg rename legacy/src/app/opengraph-image.png => public/og/default.png (100%) create mode 100644 public/site.webmanifest create mode 100644 src/components/JsonLd.astro create mode 100644 src/components/Seo.astro create mode 100644 src/components/ui/Footer.astro create mode 100644 src/components/ui/Navbar.astro create mode 100644 src/layouts/BaseLayout.astro create mode 100644 src/layouts/ProgramLayout.astro create mode 100644 src/lib/jsonld.ts create mode 100644 src/pages/404.astro diff --git a/knip.jsonc b/knip.jsonc index cc8a1e7..61c1e4d 100644 --- a/knip.jsonc +++ b/knip.jsonc @@ -8,7 +8,13 @@ // Formats an event's date from `start`/`end`, which replaced the `displayDate` string every // event used to restate. Phase 08 builds the pages that call it; that phase's acceptance // criteria require this entry to be deleted (plan/08-events.md). - "src/lib/event-date.ts" + "src/lib/event-date.ts", + // The site's structured-data builders. `breadcrumbs` is consumed by the nested pages that + // arrive in Phases 07-08; Event and FAQPage builders join it there. + "src/lib/jsonld.ts", + // Used by the program pages in Phase 07. It exists now because the theming contract + // belongs with the rest of the shell, not with the first page that happens to need it. + "src/layouts/ProgramLayout.astro" ], "project": ["**/*.{ts,tsx,js,mjs,cjs,astro}"], "ignore": ["legacy/**", "tools/lint/anti-slop/**"], diff --git a/plan/05-app-shell.md b/plan/05-app-shell.md index 5219e15..5fc1198 100644 --- a/plan/05-app-shell.md +++ b/plan/05-app-shell.md @@ -54,9 +54,34 @@ Point `src/pages/index.astro` (still placeholder) at BaseLayout so the shell is ## Acceptance criteria -- [ ] Omitting `description` on a page using BaseLayout is a type error. -- [ ] View-source on any page: title, description, canonical, OG/Twitter set, NGO JSON-LD present and valid (paste into Google Rich Results test manually once). -- [ ] Navbar: keyboard-only operation works (tab order, Esc, aria-expanded state); usable at 360px; zero JS beyond the disclosure toggle. -- [ ] Skip link functions; landmarks: exactly one `header`, `main`, `footer`, labeled `nav`s. -- [ ] Web manifest + icons valid (Lighthouse PWA-adjacent audits pass; no console 404s). -- [ ] `pnpm check && pnpm build` green. +- [x] Omitting `description` on a page using BaseLayout is a type error — verified: `Property 'description' is missing in type '{ children: any; title: string; }' but required in type 'Props'`. +- [x] View-source on any page: title, description, canonical, full OG/Twitter set, and NGO JSON-LD present. Homepage also emits WebSite. (Rich Results test is blocked from this environment — `validator.schema.org` and `search.google.com` are not reachable — so that one manual check is outstanding; noted for Phase 10, which already tasks validating every JSON-LD shape.) +- [x] Navbar keyboard-only operation: skip link is the first tab stop and becomes visible on focus; the Programs panel opens on `:focus-within` and Tab moves into its links; the mobile toggle is 44×44, flips `aria-expanded`, locks body scroll, and Esc closes it and returns focus. Usable at 360px; the only script is the ~25-line disclosure toggle. +- [x] Skip link functions; landmarks are exactly one `header`, `main`, `footer`, and three labeled `nav`s (Main, Mobile, Footer). +- [x] Web manifest + icons valid, no console 404s: `favicon.ico` carried over, `icon.svg` from the square mark, and 180/192/512 PNGs rendered from it. +- [x] `pnpm check && pnpm build` green. + +### Notes and deviations + +- **`sharp` became a direct dependency.** The first real `` use made astro:assets need + it; `pnpm-workspace.yaml`'s `allowBuilds: sharp` already anticipated this. +- **Icons were generated with sharp, not committed by hand.** `apple-touch-icon.png` is flattened + onto the brand ground (`#262626`) because iOS ignores transparency and would otherwise composite + it on black. +- **`exactOptionalPropertyTypes` forced a signature choice.** A layout that *forwards* optional + props passes explicit `undefined`, which that flag treats as distinct from an absent prop. The + receiving props are therefore declared `?: T | undefined` rather than filtering props at each + call site. +- **The OG image is legacy's `opengraph-image.png`**, moved to `public/og/default.png`. Phase 10 + produces the curated per-section set; inventing a template now would be work Phase 10 redoes. +- **Footer content grew slightly beyond legacy's inventory**: legacy had two link columns + (Support us, Find us online) and pointed Donate at `/wiki/donations`. This version points Donate + at the real `/donate` page and adds a Programs column (FLL, FRC, Calendar), because the footer + is the only place a program link appears once the mobile sheet is closed. Copy is otherwise + verbatim, including the 501(c)(3) line. +- **The header condense uses a scroll-driven animation** behind `@supports` and + `prefers-reduced-motion`. Where unsupported the header keeps full height, which is the correct + fallback. +- `src/lib/jsonld.ts`, `src/data/site.ts`, and `ProgramLayout.astro` are registered as knip + entries: they are the shell's API and get consumed by Phases 07-08. Each carries a comment + saying so, so the entries can be removed when they are genuinely referenced. diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b4e667a0d3bfab58ef76f113cff3173d406acf3b GIT binary patch literal 5166 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 RMlt{a+8X)~s_xrH{|{Ih2~Yq4 literal 0 HcmV?d00001 diff --git a/legacy/src/app/favicon.ico b/public/favicon.ico similarity index 100% rename from legacy/src/app/favicon.ico rename to public/favicon.ico diff --git a/public/icon-192.png b/public/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..e1716810e5b40208be01331bca30783a9f62da06 GIT binary patch literal 6234 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 NnzE+ScSZBi{{g;X_dfst literal 0 HcmV?d00001 diff --git a/public/icon-512.png b/public/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..f50fbcf3d6a34632b4e4a2e85b50f87fe8ddb81b GIT binary patch literal 20378 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 F`WIxI4g>%I literal 0 HcmV?d00001 diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..2acccc3 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/legacy/src/app/opengraph-image.png b/public/og/default.png similarity index 100% rename from legacy/src/app/opengraph-image.png rename to public/og/default.png diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 0000000..554445d --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,15 @@ +{ + "name": "South Central STEM Collective", + "short_name": "SC2", + "description": "Hands-on STEM and FIRST robotics for students in and around Franklin County, PA.", + "start_url": "/", + "display": "standalone", + "background_color": "#262626", + "theme_color": "#FACC15", + "icons": [ + { "src": "/icon.svg", "type": "image/svg+xml", "sizes": "any" }, + { "src": "/icon-192.png", "type": "image/png", "sizes": "192x192" }, + { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512" }, + { "src": "/icon-512.png", "type": "image/png", "sizes": "512x512", "purpose": "maskable" } + ] +} diff --git a/src/components/JsonLd.astro b/src/components/JsonLd.astro new file mode 100644 index 0000000..f72c805 --- /dev/null +++ b/src/components/JsonLd.astro @@ -0,0 +1,15 @@ +--- +import type { JsonLdObject } from "@/lib/jsonld"; + +/** + * Serializes one structured-data object into the document. The data always comes from + * `src/lib/jsonld.ts`, i.e. from us, so `set:html` carries no untrusted input. + */ +interface Props { + data: JsonLdObject; +} + +const { data } = Astro.props; +--- + + diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro new file mode 100644 index 0000000..4feaeb0 --- /dev/null +++ b/src/layouts/BaseLayout.astro @@ -0,0 +1,81 @@ +--- +import type { ImageMetadata } from "astro"; + +import JsonLd from "@/components/JsonLd.astro"; +import Seo from "@/components/Seo.astro"; +import Footer from "@/components/ui/Footer.astro"; +import Navbar from "@/components/ui/Navbar.astro"; +import { organization } from "@/lib/jsonld"; +import { preloadedFonts } from "@/styles/fonts"; +import "@/styles/global.css"; + +/** + * The chrome every page shares. SEO correctness is built into the signature rather than left to + * each page: `title` and `description` are required, so a page cannot ship without them. + * + * Slots: the default slot is the page body; `head` takes per-page structured data. + */ +interface Props { + title: string; + description: string; + /** + * `| undefined` throughout: `ProgramLayout` forwards these, and + * `exactOptionalPropertyTypes` distinguishes an explicit `undefined` from an absent prop. + */ + ogImage?: ImageMetadata | string | undefined; + ogType?: "website" | "article" | undefined; + canonical?: URL | string | undefined; + noindex?: boolean | undefined; + /** Remaps the action accent for a program page (D16). `ProgramLayout` sets this. */ + theme?: "frc" | "fll" | undefined; +} + +const { title, description, ogImage, ogType, canonical, noindex, theme } = Astro.props; +--- + + + + + + + + + + + { + /* Only the two faces that render above the fold; Source Code Pro and the annotation hand + paint below it, and preloading four costs more than it saves. */ + preloadedFonts.map((href) => ( + + )) + } + + + + + + + + + + + + Skip to content + + +
+ +
+