|
1 | | -# code-tomato.github.io |
| 1 | +# Nathan Lemma |
2 | 2 |
|
3 | | -Nathan Lemma's portfolio. ECE at UT Austin — GPU inference serving in the |
4 | | -systems-for-ML group, firmware and data infrastructure at Caterpillar. |
5 | | -Third author on EnerTune (SOSP '26). |
| 3 | +My personal portfolio, featuring my work in computer engineering, research, and software. |
6 | 4 |
|
7 | | -**Live:** [code-tomato.github.io](https://code-tomato.github.io) |
8 | | - |
9 | | -## Résumé |
10 | | - |
11 | | -`public/NathanLemmaPublicResume.pdf` is the public variant of the one-page |
12 | | -résumé (no phone or email), built in the private résumé repo by |
13 | | -`./build.sh public`. To update it, rebuild there, check the PDF (one page, no |
14 | | -contact details), and copy only `NathanLemmaPublicResume.pdf` into `public/`. |
15 | | -Never copy the private PDFs or any `.typ` sources here. |
16 | | - |
17 | | -## Design |
18 | | - |
19 | | -The site is one screen in a physical set (a bezel, a chin, and a power button |
20 | | -in that chin) and the page scrolls inside it. Two designs, both available in |
21 | | -every browser: a CRT (blue-black tube, cream and amber phosphor, one tomato |
22 | | -accent) and an iPad (cool light canvas, white cards, dot grid, no glow). The |
23 | | -CRT has effects that can be turned off separately: scanlines, a refresh sweep, |
24 | | -heading flicker, and a live barrel warp on Chromium at ≥900px (the only part |
25 | | -tied to a browser engine). Behind both is a field of hand-drawn doodles tiled |
26 | | -from a single SVG. Type is IBM Plex Mono and Reddit Sans, self-hosted; nothing |
27 | | -loads at runtime. |
28 | | - |
29 | | -Built with Astro. `npm run dev` to work on it, `npm run build` to ship. The |
30 | | -build also writes `sitemap-index.xml` (every page but the 404), which |
31 | | -`public/robots.txt` points to. |
32 | | - |
33 | | -## Checks |
34 | | - |
35 | | -Node 22.12 or newer, as in CI. |
36 | | - |
37 | | -```bash |
38 | | -npm ci |
39 | | -npm run check # astro check: TypeScript and templates |
40 | | -npm run build |
41 | | -npm run test:static # the built site: metadata, links, fragments, sitemap, robots |
42 | | -npx playwright install chromium firefox webkit # once per machine |
43 | | -npm run test:e2e # browser regression tests against dist/: three engines + cached Chromium |
44 | | -npm test # all of the above in order (after the browser install) |
45 | | -``` |
46 | | - |
47 | | -- `test:static` (`scripts/check-dist.mjs`) reads every page the build |
48 | | - produced. Each needs a title naming Nathan Lemma, one h1, one |
49 | | - `<main id="main">`, a canonical and `og:url`/`twitter:url` equal to its own |
50 | | - published URL, matching share titles and descriptions, and a share image |
51 | | - that exists. Ids must be unique and ARIA id references must resolve. Every |
52 | | - on-site `href`/`src` has to resolve to a built file the way GitHub Pages |
53 | | - serves it, every `#fragment` to an id on its page, and any linked PDF has to |
54 | | - be a PDF. The 404 page's links must be absolute or root-relative, since it |
55 | | - is served at whatever address was missed. The sitemap must list exactly the |
56 | | - published pages, and not the 404 page. Off-site links are counted, never |
57 | | - fetched, and so are links into the other repositories' Pages sites on this |
58 | | - origin (`/CHT_Radix/`, `/YASH/`, the `OTHER_DEPLOYS` list); anything else on |
59 | | - the origin must be in the build. Failures name the page and the link. |
60 | | -- `test:e2e` (`tests/e2e`, Playwright) serves `dist/` on port 4329 (set |
61 | | - `E2E_PORT` to change it) with `tests/serve-dist.mjs`, which behaves like |
62 | | - GitHub Pages (`/x` redirects to `/x/`, unknown paths get `404.html`). It |
63 | | - always starts its own server, never reusing whatever is on the port. Nothing |
64 | | - leaves 127.0.0.1. Build first. It covers: |
65 | | - - scrolling inside `.tube`; |
66 | | - - no sideways overflow at 320 and 390px in both designs: every published |
67 | | - page at 100% text, and at 150% text the Experience page and every project |
68 | | - page (the dates and periods that used to run off-screen); |
69 | | - - print output; |
70 | | - - the current-section marking and the no-JavaScript phone navigation; |
71 | | - - the phone menu: Tab/Shift+Tab loop, Escape, inert background, the quick |
72 | | - close-and-reopen race, and resizing past 720px; |
73 | | - - per-engine default designs, the design and effects controls, persistence, |
74 | | - storage that throws, reduced motion, and the power-button hand-off at 721px; |
75 | | - - keyboard scrolling from a fresh page, the skip link, and scroll memory |
76 | | - across `#hash` and cross-page back/forward. |
77 | | - The two timing races (menu reopen, Back inside the scroll-save pause) record |
78 | | - their timing in the browser and fail if a run was too slow to hit the race. |
79 | | -- Projects: `chromium`, `firefox` and `webkit` run everything except |
80 | | - `bfcache.spec.ts`. Playwright starts Chromium with the back/forward cache |
81 | | - off, so there these cover the reload path. `chromium-bfcache` runs only |
82 | | - `bfcache.spec.ts`, in full Chromium (`channel: 'chromium'`) with the cache |
83 | | - on, and asserts every return came from the cache (`pageshow.persisted`) with |
84 | | - its place and the latest display choice. |
85 | | -- Pick projects with `--project`, e.g. |
86 | | - `npx playwright test --project=webkit --project=chromium-bfcache`. CI runs |
87 | | - all four on Ubuntu. If a browser won't launch locally, reinstall it with |
88 | | - `npx playwright install <browser>` and use `--project` to run the others |
89 | | - meanwhile; CI remains the full run. |
90 | | -- A failed CI run uploads the HTML report and traces as `playwright-report`. |
91 | | - Open one locally with `npx playwright show-trace <trace.zip>`. |
92 | | - |
93 | | -The `Validate` workflow (`.github/workflows/validate.yml`) runs all of this |
94 | | -on every push and pull request, read-only. Deployment is only |
95 | | -`deploy.yml`, on `main`. |
96 | | - |
97 | | -## Display settings |
98 | | - |
99 | | -The power button (on the chin at ≥721px, in the header on phones) switches the |
100 | | -design. The footer's *Design* and *Effects* rows set each choice directly. |
101 | | -Everything lives in `BaseLayout.astro`: the head script decides the state |
102 | | -before first paint, and the end-of-body script wires up the controls. |
103 | | - |
104 | | -| `localStorage` key | Values | Nothing stored | |
105 | | -| --- | --- | --- | |
106 | | -| `display-design` | `crt`, `tablet` | `crt` on Chromium, `tablet` elsewhere (unchanged from before) | |
107 | | -| `display-effects` | `on`, `off` | `off` under `prefers-reduced-motion: reduce`, else `on`; follows the OS setting live until chosen | |
108 | | - |
109 | | -- The old key `crt-warp` (`1` CRT, `0` tablet) is moved to `display-design` |
110 | | - once and then removed. |
111 | | -- If storage is unavailable, a choice still applies to the current page and is |
112 | | - forgotten on the next one. |
113 | | -- A page restored from the back/forward cache re-reads the stored choices, so |
114 | | - it matches whatever was chosen on the pages visited since. |
115 | | -- Classes on `<html>`: `flat` (tablet), `calm` (CRT with effects off), `warp` |
116 | | - (warp filter active: CRT, effects on, Chromium; the CSS adds ≥900px), `js` |
117 | | - (the script ran). With no JavaScript the page is the CRT with effects, as |
118 | | - before, and the phone header shows plain links instead of the menu button. |
119 | | -- Reduced motion also stops the sweep and flicker whatever is chosen; turning |
120 | | - effects on there brings back only the static parts (scanlines, warp). The |
121 | | - Effects group's screen-reader description is rewritten to list only what |
122 | | - actually runs in the current browser, width and motion setting. |
123 | | - |
124 | | -## Keyboard and scrolling |
125 | | - |
126 | | -- The skip link is the first Tab stop and lands on `<main>` (`tabindex="-1"`). |
127 | | -- The page scrolls inside `.tube`, not the document. With nothing focused, |
128 | | - PageUp/PageDown, Space/Shift+Space, the arrow keys, Home and End are |
129 | | - forwarded to the tube, so they work on a fresh load without clicking first. |
130 | | -- The tube's scroll offset is kept per history entry (a key in `history.state`, |
131 | | - the offset in `sessionStorage` under `tube-y:<key>`) and restored on |
132 | | - back/forward and reload. New visits and `#hash` links scroll as normal; a |
133 | | - back/forward cache hit keeps the live page. |
134 | | -- Phone menu: Tab and Shift+Tab stay inside the open drawer; Escape, the close |
135 | | - button, the backdrop or a link close it and return focus to the menu button. |
136 | | - Everything else, the skip link and display controls included, is inert while |
137 | | - it is open. Resizing past 720px closes it and moves focus to the matching tab. |
| 5 | +[Visit the website](https://code-tomato.github.io) |
0 commit comments