Skip to content
Merged
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
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,39 @@ jobs:
env:
CHROMATIC_ARCHIVE_LOCATION: ./test-results

lighthouse:
name: Lighthouse CI
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up pnpm
uses: pnpm/action-setup@v4

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build
run: pnpm build

# Tests the static dist/ output directly, so /api/geo and the Vercel
# Analytics script (both Vercel-runtime-only) 404 here even though
# they work in production — accounted for in lighthouserc.json's
# best-practices threshold.
- name: Run Lighthouse CI
uses: treosh/lighthouse-ci-action@v11
with:
configPath: ./lighthouserc.json
uploadArtifacts: true
temporaryPublicStorage: true

lint:
name: Lint + format
runs-on: ubuntu-latest
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ dist/
# playwright output
test-results/
playwright-report/
# lighthouse ci output
.lighthouseci/
# generated types
.astro/

Expand Down
20 changes: 18 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# youthphase.dev

![Lighthouse Performance](https://img.shields.io/badge/Lighthouse%20Performance-100-brightgreen)
![Lighthouse Accessibility](https://img.shields.io/badge/Lighthouse%20Accessibility-100-brightgreen)
![Lighthouse Best Practices](https://img.shields.io/badge/Lighthouse%20Best%20Practices-100-brightgreen)
![Lighthouse SEO](https://img.shields.io/badge/Lighthouse%20SEO-100-brightgreen)

Personal site of Amy Lam. Built with [Astro](https://astro.build) and
[Tailwind CSS](https://tailwindcss.com), package-managed with
[pnpm](https://pnpm.io), linted with Prettier/ESLint/Stylelint. This
Expand Down Expand Up @@ -117,6 +122,17 @@ Key files:
every sun altitude and fails if any ink/background pairing drops below
WCAG AAA — see [the sky section](#the-sky) above.

[Lighthouse CI](https://github.com/GoogleChrome/lighthouse-ci) runs
against every page in the built `dist/` output and fails the build if
Performance drops below 90, Best Practices drops below 95, or
Accessibility or SEO drop below 100 — thresholds set in
[lighthouserc.json](lighthouserc.json). Performance gets a lower floor
because build-to-build timing variance on CI runners is real; Best
Practices gets one because testing the static `dist/` directly (rather
than a deployed Vercel environment) makes `/api/geo` and the Vercel
Analytics script 404, which they don't in production. The badges above
reflect an actual production run, which hits neither issue.

The same Playwright run also feeds [Chromatic](https://www.chromatic.com)
visual regression testing via
[`@chromatic-com/playwright`](https://www.chromatic.com/docs/playwright/) —
Expand Down Expand Up @@ -148,8 +164,8 @@ to actually publish.

[GitHub Actions](https://github.com/features/actions)
(`.github/workflows/ci.yml`) runs the build, `check:contrast`, the full
Playwright suite, and all three linters on every push to `main` and on
every pull request.
Playwright suite, Lighthouse CI, and all three linters on every push to
`main` and on every pull request.

## Deploying (Vercel)

Expand Down
19 changes: 19 additions & 0 deletions lighthouserc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"ci": {
"collect": {
"staticDistDir": "./dist",
"numberOfRuns": 3
},
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"categories:accessibility": ["error", { "minScore": 1 }],
"categories:best-practices": ["error", { "minScore": 0.95 }],
"categories:seo": ["error", { "minScore": 1 }]
}
},
"upload": {
"target": "temporary-public-storage"
}
}
}
35 changes: 30 additions & 5 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const ogImage = new URL('/og.png', Astro.site);
<div
id="glow"
aria-hidden="true"
data-chromatic="ignore"
class="pointer-events-none fixed size-[60vw] -translate-1/2 rounded-full opacity-0 blur-[55px] sm:size-[45vmin] sm:blur-[90px]"
>
</div>
Expand All @@ -59,6 +60,7 @@ const ogImage = new URL('/og.png', Astro.site);
<div
id="moon-core"
aria-hidden="true"
data-chromatic="ignore"
class="pointer-events-none fixed size-[7px] -translate-1/2 rounded-full opacity-0"
>
</div>
Expand All @@ -73,7 +75,12 @@ const ogImage = new URL('/og.png', Astro.site);
sliding off another circle. sky.ts positions it with the moon and
rewrites the shadow path each tick; visibility and the bloom
transition live in global.css under html.moon-cycle. -->
<div id="moon-face" aria-hidden="true" class="pointer-events-none fixed size-14 -translate-1/2">
<div
id="moon-face"
aria-hidden="true"
data-chromatic="ignore"
class="pointer-events-none fixed size-14 -translate-1/2"
>
<svg viewBox="0 0 100 100" class="size-full">
<defs>
<radialGradient id="moon-sphere" cx="42%" cy="38%" r="72%">
Expand Down Expand Up @@ -111,18 +118,29 @@ const ogImage = new URL('/og.png', Astro.site);
moonlight rises softly from the stage and recedes (sky.ts).
Sits below the page content in DOM order so the flood stays
behind the text. -->
<div id="moon-finale" aria-hidden="true" class="pointer-events-none fixed inset-0 opacity-0">
<div
id="moon-finale"
aria-hidden="true"
data-chromatic="ignore"
class="pointer-events-none fixed inset-0 opacity-0"
>
</div>

<!-- Day-show shower cloud: a faint gray dim over the sky while the
rain falls, lifting when the rainbow arrives (sky.ts). -->
<div id="cloud-dim" aria-hidden="true" class="pointer-events-none fixed inset-0 opacity-0">
<div
id="cloud-dim"
aria-hidden="true"
data-chromatic="ignore"
class="pointer-events-none fixed inset-0 opacity-0"
>
</div>

<!-- Day-show rainbow: the left leg of a bow whose arch is far off
the upper right — mostly horizontal across the page, sweeping
up as it exits (sky.ts triggers it; geometry in global.css). -->
<div id="rainbow" aria-hidden="true" class="pointer-events-none fixed"></div>
<div id="rainbow" aria-hidden="true" data-chromatic="ignore" class="pointer-events-none fixed">
</div>

<!-- The easter egg's trigger is a triple-tap anywhere on the sky
(sky.ts) — no dedicated touch target needed, so nothing ever
Expand All @@ -132,13 +150,20 @@ const ogImage = new URL('/og.png', Astro.site);

<!-- The real night sky: sky.ts positions the brightest stars for the
visitor's location and time. -->
<div id="stars" aria-hidden="true" class="pointer-events-none fixed inset-0 opacity-0"></div>
<div
id="stars"
aria-hidden="true"
data-chromatic="ignore"
class="pointer-events-none fixed inset-0 opacity-0"
>
</div>

<!-- Contrast scrim: sky.ts raises its opacity when the gradient behind
the center content can't meet AAA on its own (golden hour). -->
<div
id="scrim"
aria-hidden="true"
data-chromatic="ignore"
class="pointer-events-none fixed top-[48%] left-1/2 h-[44vh] w-[min(96vw,64rem)] -translate-1/2 rounded-[50%] opacity-0 blur-3xl"
>
</div>
Expand Down
Loading