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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

# production
/build
/.prerender-*

# misc
.DS_Store
Expand Down
84 changes: 57 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# exploretech.la

The website for [exploretech.la](https://www.exploretech.la/): a React single-page app written in TypeScript, built with Vite and Tailwind, and hosted on GitHub Pages.
The website for [exploretech.la](https://www.exploretech.la/), built with React, TypeScript, Vite, and Tailwind. GitHub Pages serves generated HTML for each published route; React hydrates it for navigation and interactive controls.

## Development

Expand All @@ -23,18 +23,20 @@ The development server runs at `http://127.0.0.1:3000` and fails clearly if that
| `src/content/teams.ts` | Ordered team sections and the role each person holds in them | A roster or job title changes |
| `src/content/speakers.ts` | Ordered historical speaking roles referencing the people registry | A past speaker's displayed title or order changes |
| `src/content/events/` | One module per event year, plus `index.ts` listing them | A schedule, workshop, map, waiver or FAQ changes |
| `src/content/participation.ts` | Current program notices, school/volunteer/partner inquiry links, UCLA updates link | Verified program information or a contact destination changes |
| `src/content/pages.ts` | Published routes and their titles, descriptions, and canonical URLs | Page metadata changes |
| `src/content/sections.ts` | Home page section ids used by header hash links | A home page anchor is added or renamed |
| `src/features/` | Page rendering and feature-specific home or Ignite copy | A page layout or its local copy changes |
| `src/components/` | Shared UI and media only | Something is genuinely used by more than one feature |
| `src/styles/` | `theme.css` entry, tokens, reset, and per-area layers | The design changes |
| `src/constants/optimizedImages.ts` | Generated image map — never edit by hand | Never; run `npm run images` |
| `src/constants/optimizedImages.ts` | Generated image map, never edit by hand | Never; run `npm run images` |
| `src/static/` | Original photographs and documents, and generated WebP output | You add a source image or document |
| `scripts/` | Image pipeline, content check, browser smoke, test server, parity tools | Tooling changes |
| `src/app/` | Entry, routes, analytics and scroll effects | A route is added or removed |

Content is central on purpose: authors change `src/content` without knowing which component renders it, and features read content without owning it.

Home carousel and sponsor records live in `src/features/home/content/`. Team and speaker identities always live in the shared people registry.
Home gallery and sponsor records live in `src/features/home/content/`. Team and speaker identities always live in the shared people registry.

## Contributor tasks

Expand All @@ -50,30 +52,44 @@ Edit the one entry in `src/content/people.ts`. Every team that lists them picks
},
```

A person with no portrait simply omits `image`; their card still renders with name and role.
A person with no portrait omits `image`; their card shows initials, name, and role.

### Change a roster or a role

Edit `src/content/teams.ts`. Each member is a reference plus the title held *in that section*, so the same person can appear in several teams with different titles. Card order follows the array; tab order follows the section order.
Edit `src/content/teams.ts`. Each member is a reference plus the title held *in that section*, so the same person can appear in several teams with different titles. Card order follows the array. The department links and phone selector follow the section order.

```ts
{ personId: "benjamin-garcia", title: "Web Dev Lead" },
```

`personId` is checked against the registry at compile time and again by `npm run content:check`.

### Change an event
### Publish verified program information

Edit the year module under `src/content/events/` (`2022.ts` is served at `/resources`, `2023.ts` at `/resources2023`, `2026.ts` at `/resources2026`, and `2021.ts` is the registration page at `/register`). Schedules, workshop cards, maps, waivers, FAQ entries and the feedback link are all data there. Archived workshop videos hold a bare YouTube id, never a watch URL, and always render behind the click-to-load facade.
Edit `src/content/participation.ts` for notices and inquiry actions shared by the home page, `/events`, `/get-involved`, and Ignite. Until the organization confirms new details, keep the explicit unpublished-details notice and the working email inquiry.

Do not infer that registration is open or closed from an old form. Confirm dates, eligibility, transportation, meals, deadlines, and application destinations with the organization before publishing them. Keep school participation, UCLA volunteering, and partnership inquiries separate. The existing UCLA mailing list is for updates, not a school application or a volunteer application.

Use the audience sections at `/get-involved#schools`, `#volunteer`, and `#partners` as the public entry points. Their mailto links use the shared contact address and a topic-specific subject. Check the destination without sending a message.

### Change an archived event

Edit the year module under `src/content/events/`. `/register` is the 2021 archive, `/resources` is the 2022 archive, and `/resources2023` and `/resources2026` serve their named years. Keep year labels consistent in headings and archive links.

Schedules, workshop cards, maps, waivers, and historical FAQ entries live in these modules. Do not restore past registration, Zoom attendance, or feedback collection actions. Archived workshop videos use a bare YouTube id and their actual workshop title, and load only after activation.

Local downloads use `DocumentLink` records with `name`, imported `src`, and `file: { format, bytes }`. Use the file's actual byte size, not a rounded display value. The shared component displays the format, decimal MB or kB size, and new-tab notice. Keep old PDFs and maps reachable at their existing URLs.

The 2026 workshop session, time, and room fields come from the archived program. Change them only against that source. Workshop details use native disclosures, and filtering must leave a visible result count and a way to clear the query.

### Add an event year

1. Add `src/content/events/<year>.ts` exporting an `EventContent`.
2. Register it in `src/content/events/index.ts`: the module in `EVENTS`, and its path, year and menu label in `EVENT_ROUTES`.

That is the whole change. The router and the Resources menu are generated from `EVENT_ROUTES`, which is also why the menu label lives there: `/resources` serves the 2022 content under the label "exploretech 2021" it shipped with.
2. Register the module in `EVENTS` and its path, year, and matching archive label in `EVENT_ROUTES`.
3. Add the route's metadata in `src/content/pages.ts` if it is not derived from the event route list.
4. Run the content, build, and browser checks below. Confirm the generated route contains a heading and metadata with JavaScript disabled.

Old years stay published. They are history, not dead code.
`EVENT_ROUTES` supplies the archived event routes and archive links. `/events` is the program hub; a newer archive is not evidence of an upcoming event.

### Add or replace an image

Expand Down Expand Up @@ -117,11 +133,11 @@ npm run format:check # Prettier (npm run format writes)
npm test # Vitest unit and component regressions
npm run content:check # References between people, teams, events and their assets
npm run images:check # Generated image integrity and budgets
npm run build # Checks images, then builds into build/
npm run build # Check images, build client assets, generate route HTML
npm run preview # Serve the production build locally
```

`npm run content:check` loads the content modules through Vite, so imported images and documents are resolved the way the app resolves them. It reports missing people, duplicate profiles or roles, shared profile links, missing assets, malformed URLs and video ids, and duplicate routes or workshop titles. It makes no network requests: a syntactically valid but dead external link still passes.
`npm run content:check` loads the content modules through Vite, so imported images and documents resolve as they do in the app. It reports missing people, duplicate profiles or roles, shared profile links, missing assets, malformed URLs and video ids, duplicate routes or workshop titles, mismatched archive years, incorrect download sizes, and broken participation inquiry destinations. TypeScript owns data shapes; this check owns cross-record and filesystem facts. It makes no network requests, so a syntactically valid but dead external link still passes.

### Browser smoke

Expand All @@ -131,19 +147,33 @@ npm run build
npm run test:browser
```

The suite (`scripts/browser-smoke.spec.ts`, configured in `playwright.config.ts`) runs Chromium against the **built** site served by `scripts/serve-built-site.cjs`, which answers unknown paths with `404.html` exactly as GitHub Pages does. Point it at another build with `SMOKE_ROOT=/path/to/build`, or move it off port 4390 with `SMOKE_PORT`.
For a full cross-engine pass:

```sh
npx playwright install chromium firefox webkit
npm run test:browser:all
```

CI keeps the faster Chromium gate. On macOS, the WebKit keyboard cases use the native Option+Tab link-navigation shortcut; the tests do not change your system or Safari settings. Geometry checks round to hundredths of a CSS pixel to avoid floating-point reporting noise.

The suite in `scripts/browser-smoke.spec.ts`, configured in `playwright.config.ts`, runs Chromium against the built site served by `scripts/serve-built-site.cjs`. Known directory routes redirect to a trailing slash with their query preserved and return generated HTML with status 200. Unknown paths and the `/our_team` alias return `404.html` and exercise the Pages restore script. Missing assets return a plain 404, not the app shell.

Point the suite at another generated build with `SMOKE_ROOT=/path/to/build`, or change port 4390 with `SMOKE_PORT`. The server starts fresh for each run to avoid accidentally checking a different build.

Every cross-origin request is aborted and recorded. The suite does not contact analytics or video vendors, send email, or submit forms. It covers:

Every cross-origin request is aborted and recorded, so the suite is offline and reaches no analytics or video vendor. It covers:
- published routes, route-specific HTML and metadata without JavaScript, hydration errors, aliases, and the not-found page;
- query and hash preservation, first-tab skip navigation, phone menu dismissal, pushed-route focus, repeated hash links, and Back scroll restoration;
- the visible phone mission and overflow at 320, 390, 768, 1024, and 1440px;
- school, volunteer, and partner journeys ending at real inquiry links, shared program notices, explicit archives, and obsolete collection actions;
- all seven team grids, deferred portraits, and initials for missing photos;
- keyboard FAQ expansion with named panels, workshop disclosures and filtering, and event shortcuts;
- specific footer and video names, keyboard player activation, focus transfer, and reserved video geometry;
- 44px core controls, primary-action text contrast, and visible keyboard focus;
- download format and byte-size labels against actual served documents;
- manual gallery buttons and status, no autoplay with reduced motion or keyboard focus, slow images, failed images, and stale image loads.

- all 13 routes, the `/our_team` alias, its trailing slash, and the not-found page and title;
- deep links surviving the Pages 404-to-root restore with their query and hash intact;
- the phone menu opening, navigating, closing, and still landing on a hash section;
- a repeated same-hash click scrolling back;
- a pushed route starting at the top while Back leaves scrolling to the browser;
- all seven team sections, eager-then-lazy portraits, and cards with no portrait;
- archived videos requesting nothing before activation, keyboard activation, and unchanged geometry after it;
- every shared PDF and map link answering 200;
- the carousel holding the current slide until a slow one loads, and skipping a broken one without locking up.
These checks do not replace a visual review or a screen-reader pass. Before requesting review, inspect the changed pages at the listed widths, tab through each task, and check new external destinations without submitting anything.

Add a case when you fix a bug a user could see; keep it in this one file.

Expand All @@ -157,7 +187,7 @@ SITE_URL=http://127.0.0.1:4392 OUTPUT_DIR=/tmp/site-after BASELINE_DIR=/tmp/site
node scripts/asset-parity.cjs /path/to/baseline/build build
```

`WIDTHS=390,768,1440` narrows a capture. The default includes every Bootstrap-era breakpoint and its adjacent pixels. Read [the UI canon](docs/ui-canon.md) before changing shared controls or styling.
`WIDTHS=390,768,1440` narrows a capture. The default includes the historical responsive breakpoints and their adjacent pixels. Parity against an old design is not an acceptance gate for an intentional UX change. Read [the UI canon](docs/ui-canon.md) before changing shared controls or styling.

## CI and deployment

Expand All @@ -171,11 +201,11 @@ node scripts/asset-parity.cjs /path/to/baseline/build build

## Runtime and hosting conventions

- Root `index.html` is the Vite entry and keeps the GitHub Pages query-to-route restoration script; `public/404.html` and `public/CNAME` are copied unchanged.
- Root `index.html` is the Vite entry and keeps the GitHub Pages query-to-route restoration script. `npm run build` runs the client build and `scripts/prerender.mjs`, using `src/app/prerender.tsx` and `src/content/pages.ts` to write each published route's `index.html`. Canonical URLs end with a slash except the root. `public/404.html` and `public/CNAME` remain the hosting fallback and domain configuration.
- Public media keep their `static/media/<name>.<content-hash>.<extension>` URLs so shared PDF, map and image links survive a rebuild. JavaScript and CSS filenames may change.
- Styling is Tailwind utilities plus the layered stylesheets in `src/styles`, entered from `theme.css`. Tailwind's Preflight is deliberately not imported: the site's geometry depends on the reset it shipped with, which `src/styles/reset.css` carries explicitly. Do not add `@import "tailwindcss"` back.
- Fonts load from a stylesheet link in the HTML rather than a nested CSS import, which prevents an unstyled startup flash in WebKit.
- Initially visible images stay eager; offscreen content is lazy with reserved dimensions. The carousel waits for a selected image and skips failures. Archived videos load only after activation.
- Initially visible images stay eager; offscreen content is lazy with reserved dimensions. The gallery is manual, keeps the last loaded selection visible while another loads, and skips failures. Archived videos load only after activation.
- `VITE_GOOGLE_ANALYTICS_TRACKING_ID` is optional and used only in production builds. Vite-prefixed variables are public client configuration, not a place for credentials. Analytics keeps the existing tracker, queues early commands, and defers vendor loading until idle, interaction, or its deadline.
- GitHub Pages' cache headers and hosting configuration are unchanged. Longer immutable caching would need a separate hosting decision.
- npm install-script decisions are version-scoped in `package.json`; the optional watcher source builds are denied and supported platforms use prebuilt packages. Review `npm install-scripts ls` before changing them.
4 changes: 3 additions & 1 deletion docs/refactor-plan.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Contributor refactor plan and acceptance ledger
# PR #108 refactor verification, historical record

This records the original parity refactor against `f435aca`. PR #108 subsequently merged as `b57ae7d` and was deployed. The tables below retain that effort's original scope and measurements; they are not a current UI specification or working-tree status. For current controls, layouts, participation workflows, and checks, use the [README](../README.md) and [UI canon](ui-canon.md). The later UX pass intentionally replaces several preserved legacy behaviors.

## Mission

Expand Down
Loading
Loading