diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 10e8bb611e..db55506ee5 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -3,7 +3,7 @@ ## Reality Check - This repo is a root-static GitHub Pages site. -- The maintained pages are `index.html`, `photo.html`, `thank-you.html`, and `cv.html`. +- The maintained pages are `index.html`, `photo.html`, and `cv.html`. `404.html` is served by GitHub Pages for missing routes and must use root-absolute asset paths. - `dev` is the working/source branch convention. - Do not assume `main` is the source branch. - Do not assume Jekyll templates or GitHub Actions are part of the active runtime unless the repo is changed to make that true. @@ -13,7 +13,7 @@ - Shared shell and navigation logic: `assets/js/site-shell.js` - Homepage behavior: `assets/js/index-page.js` - Photography page behavior: `assets/js/photo-page.js` -- Legacy helper script: `assets/js/scripts.js` +- Lab loader and navigation: `assets/js/portfolio-labs.js` (lazy-imports `path-playground.js` and `lab-experiments.js`, which pull in three.js) ## CSS Layout @@ -28,4 +28,4 @@ ## Legacy Routes -`about.html`, `projects.html`, and `vision.html` are compatibility-only routes. Keep them low-risk and avoid treating them as primary product surfaces unless the repo is intentionally reoriented. Local harness pages belong under `tests/fixtures/`. +`about.html`, `projects.html`, `vision.html`, and `thank-you.html` are `noindex` meta-refresh redirects into homepage sections. Keep them that way. Local harness pages belong under `tests/fixtures/`. diff --git a/.github/workflows/site-checks.yml b/.github/workflows/site-checks.yml new file mode 100644 index 0000000000..a7cd6dd9e8 --- /dev/null +++ b/.github/workflows/site-checks.yml @@ -0,0 +1,42 @@ +name: Site checks + +on: + push: + branches: [dev] + pull_request: + +permissions: + contents: read + +jobs: + checks: + # Pinned: Playwright 1.59 cannot install browsers on ubuntu-latest (26.04). + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + # The Playwright web server runs scripts/serve.py. + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - run: npm ci + + - name: Committed CSS matches the Tailwind source + run: | + npm run build + git diff --exit-code assets/css/output.css + + - run: npx playwright install --with-deps chromium + + - run: npm run lint + + - run: npm run test:models + + - run: npm test -- --reporter=line diff --git a/404.html b/404.html new file mode 100644 index 0000000000..68d2adb78e --- /dev/null +++ b/404.html @@ -0,0 +1,63 @@ + + + + + + + Page not found | Yuval Grimberg + + + + + + + + + + + + + +
+

404 · no feasible path

+

+ The planner searched everywhere. This page is not on the map. +

+

+ Either the link is stale or the goose moved it. Both happen. Here are routes that do converge. +

+ + + + + + + + + + + START + GOAL: UNREACHABLE + + + +
+ + + diff --git a/README.md b/README.md index a276b59088..0d3ebcd86c 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ Static personal site for [grimgrimberg.github.io](https://grimgrimberg.github.io - Primary maintained pages: - `index.html` - `photo.html` - - `thank-you.html` - `cv.html` -- Legacy compatibility pages: - - `about.html` - - `projects.html` - - `vision.html` + - `fly101.html` (project notes; the Fly101 repo is private, so never link to it or copy private paths) + - `404.html` (served by GitHub Pages for any missing path, so it uses root-absolute URLs) +- Legacy URLs kept as `noindex` redirects into homepage sections: + - `about.html`, `projects.html`, `vision.html`, `thank-you.html` +- Crawl and sharing files: `robots.txt`, `sitemap.xml`, `apple-touch-icon.png`, `assets/images/og-card.png` This repo is not React and is not driven by Jekyll templates in its current maintained flow. @@ -30,12 +30,18 @@ This repo is not React and is not driven by Jekyll templates in its current main - Page-specific behavior lives in: - `assets/js/index-page.js` - `assets/js/photo-page.js` -- Legacy standalone pages still use `assets/js/scripts.js`. +- The interactive labs live in: + - `assets/js/portfolio-labs.js`: lab switching, plus a lazy loader that imports the scenes when a lab nears the viewport or the page goes idle + - `assets/js/path-playground.js` and `assets/js/lab-experiments.js`: the three.js scenes + - `assets/js/lab-models.mjs`: the pure simulation models, tested by `npm run test:models` + - `assets/js/fly-brain.js`: the connectome point cloud, shared by the `Connectome` lab and `fly101.html` +- Clippy (jQuery + clippy.js, about 1.5 MB with the sprite sheet) is injected on demand: after page load on desktop, on the first Guide click on phones. ## CSS Story - `assets/css/styles.css` is the Tailwind v4 source-of-truth stylesheet. -- `npm run build` produces the committed `assets/css/output.css` used by the maintained pages and legacy compatibility pages. +- `npm run build` produces the committed `assets/css/output.css` used by every page. CI fails if it is stale. +- `assets/css/original-plus-3d.css` holds hand-written overrides for the hero, nav, labs and goose, loaded after `output.css` on the homepage. - Primary pages no longer depend on the Tailwind CDN runtime. ## Local Development @@ -56,12 +62,13 @@ npm run serve - [http://127.0.0.1:8000/](http://127.0.0.1:8000/) - [http://127.0.0.1:8000/photo.html](http://127.0.0.1:8000/photo.html) -- [http://127.0.0.1:8000/thank-you.html](http://127.0.0.1:8000/thank-you.html) +- [http://127.0.0.1:8000/cv.html](http://127.0.0.1:8000/cv.html) ## Commands ```bash npm run lint +npm run test:models npm test npm run test:headed npm run build @@ -70,7 +77,9 @@ npm run serve ``` - `npm run lint` runs the repo-specific static and runtime hygiene checks. +- `npm run test:models` checks the lab simulation models in plain Node. - `npm test` runs the maintained Playwright smoke suite. +- `.github/workflows/site-checks.yml` runs the build check, lint, model tests and smoke suite on pushes to `dev` and on pull requests. - Use `npm run lint` and `npm test -- --reporter=line` as the default acceptance gates before pushing portfolio changes. - `npm run build` rebuilds the committed Tailwind CSS used by the site pages. - `npm run serve` launches the same local static server shape the smoke suite uses. @@ -82,11 +91,25 @@ assets/ css/ styles.css output.css + original-plus-3d.css + data/ + github-repos.json + images/ + fly101/ # renders of the connectome, fly and arm + projects/ # line-art schematics for the project cards + icons/ # inline-ready skill icons js/ index-page.js photo-page.js site-shell.js - scripts.js + portfolio-labs.js + path-playground.js + lab-experiments.js + lab-models.mjs + fly-brain.js + vendor/ + clippyjs/ + three/ scripts/ lint-site.mjs optimize-images.ps1 @@ -108,8 +131,11 @@ docs/ REFACTORING_SUMMARY.md index.html photo.html -thank-you.html cv.html +fly101.html +404.html +robots.txt +sitemap.xml ``` ## Notes diff --git a/about.html b/about.html index af68daa40e..b6dedece20 100644 --- a/about.html +++ b/about.html @@ -1,62 +1,22 @@ + - About | Yuval Grimberg - + About moved | Yuval Grimberg + + + + + - -
- -
-
-

- Legacy page: this standalone route is kept for compatibility. Start from - the homepage - for the maintained experience. -

-
- -
-
-

About Me

-

As a Mechanical Engineer specializing in control theory and autonomous vehicles, I bring together technical expertise and innovative thinking to solve complex engineering challenges.

-
-
-

Education

-

Mechanical Engineering with focus on Control Theory and Autonomous Systems

-
-
-

Skills

-

Control Systems, Robotics, Machine Learning, System Design

-
-
-

Interests

-

Autonomous Vehicles, AI in Engineering, Advanced Control Systems

-
-
- -
-
+ +
+

This page moved. Go to the About section.

+
+ - - - diff --git a/apple-touch-icon.png b/apple-touch-icon.png new file mode 100644 index 0000000000..910d51bfbd Binary files /dev/null and b/apple-touch-icon.png differ diff --git a/assets/css/output.css b/assets/css/output.css index 39f378bf2d..a7b0c74a79 100644 --- a/assets/css/output.css +++ b/assets/css/output.css @@ -1,2 +1,2 @@ /*! tailwindcss v4.1.17 | MIT License | https://tailwindcss.com */ -@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-blue-50:oklch(97% .014 254.604);--color-blue-400:oklch(70.7% .165 254.624);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-blue-700:oklch(48.8% .243 264.376);--color-gray-50:oklch(98.5% .002 247.839);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-extrabold:800;--font-weight-black:900;--leading-tight:1.25;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-2xl:1rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-tech-dark:#05070a;--color-tech-cyan:#58c7bc;--color-tech-purple:#d8aa4f;--color-tech-green:#9bd66b;--color-tech-pink:#ff766d;--font-display:"Bahnschrift","Aptos Display","Segoe UI Variable","Segoe UI",system-ui,sans-serif;--font-tech:"Cascadia Mono","JetBrains Mono",Consolas,Monaco,monospace;--font-roboto:"Segoe UI",system-ui,sans-serif;--font-playfair:"Bahnschrift","Aptos Display","Segoe UI Variable",system-ui,sans-serif;--animate-fade-in:fadeIn .6s ease-in-out;--animate-float:float 6s ease-in-out infinite}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.-top-10{top:calc(var(--spacing)*-10)}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.-right-10{right:calc(var(--spacing)*-10)}.right-0{right:calc(var(--spacing)*0)}.right-4{right:calc(var(--spacing)*4)}.-bottom-1{bottom:calc(var(--spacing)*-1)}.-bottom-10{bottom:calc(var(--spacing)*-10)}.bottom-4{bottom:calc(var(--spacing)*4)}.bottom-8{bottom:calc(var(--spacing)*8)}.-left-10{left:calc(var(--spacing)*-10)}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-5{left:calc(var(--spacing)*5)}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-14{margin-top:calc(var(--spacing)*14)}.mt-16{margin-top:calc(var(--spacing)*16)}.mt-20{margin-top:calc(var(--spacing)*20)}.mt-auto{margin-top:auto}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-5{margin-bottom:calc(var(--spacing)*5)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.mb-10{margin-bottom:calc(var(--spacing)*10)}.mb-12{margin-bottom:calc(var(--spacing)*12)}.mb-16{margin-bottom:calc(var(--spacing)*16)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0\.5{height:calc(var(--spacing)*.5)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-10{height:calc(var(--spacing)*10)}.h-11{height:calc(var(--spacing)*11)}.h-16{height:calc(var(--spacing)*16)}.h-24{height:calc(var(--spacing)*24)}.h-32{height:calc(var(--spacing)*32)}.h-72{height:calc(var(--spacing)*72)}.h-96{height:calc(var(--spacing)*96)}.h-full{height:100%}.max-h-56{max-height:calc(var(--spacing)*56)}.min-h-11{min-height:calc(var(--spacing)*11)}.min-h-\[20px\]{min-height:20px}.min-h-\[72svh\]{min-height:72svh}.min-h-\[84svh\]{min-height:84svh}.min-h-screen{min-height:100vh}.w-0{width:calc(var(--spacing)*0)}.w-1{width:calc(var(--spacing)*1)}.w-2\.5{width:calc(var(--spacing)*2.5)}.w-3{width:calc(var(--spacing)*3)}.w-6{width:calc(var(--spacing)*6)}.w-11{width:calc(var(--spacing)*11)}.w-16{width:calc(var(--spacing)*16)}.w-24{width:calc(var(--spacing)*24)}.w-72{width:calc(var(--spacing)*72)}.w-96{width:calc(var(--spacing)*96)}.w-\[calc\(100vw-4rem\)\]{width:calc(100vw - 4rem)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-fade-in{animation:var(--animate-fade-in)}.animate-float{animation:var(--animate-float)}.animate-pulse{animation:var(--animate-pulse)}.resize{resize:both}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-5{gap:calc(var(--spacing)*5)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}.gap-10{gap:calc(var(--spacing)*10)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*1)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*1)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-6>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*6)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*8)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-x-reverse)))}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.scroll-smooth{scroll-behavior:smooth}.rounded{border-radius:.25rem}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-gray-200{border-color:var(--color-gray-200)}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-tech-cyan\/20{border-color:#58c7bc33}@supports (color:color-mix(in lab, red, red)){.border-tech-cyan\/20{border-color:color-mix(in oklab,var(--color-tech-cyan)20%,transparent)}}.border-tech-cyan\/30{border-color:#58c7bc4d}@supports (color:color-mix(in lab, red, red)){.border-tech-cyan\/30{border-color:color-mix(in oklab,var(--color-tech-cyan)30%,transparent)}}.border-tech-cyan\/40{border-color:#58c7bc66}@supports (color:color-mix(in lab, red, red)){.border-tech-cyan\/40{border-color:color-mix(in oklab,var(--color-tech-cyan)40%,transparent)}}.border-tech-green{border-color:var(--color-tech-green)}.border-transparent{border-color:#0000}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.border-white\/15{border-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.border-white\/15{border-color:color-mix(in oklab,var(--color-white)15%,transparent)}}.border-white\/20{border-color:#fff3}@supports (color:color-mix(in lab, red, red)){.border-white\/20{border-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.border-white\/30{border-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.border-white\/30{border-color:color-mix(in oklab,var(--color-white)30%,transparent)}}.bg-black\/30{background-color:#0000004d}@supports (color:color-mix(in lab, red, red)){.bg-black\/30{background-color:color-mix(in oklab,var(--color-black)30%,transparent)}}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-blue-50{background-color:var(--color-blue-50)}.bg-blue-500{background-color:var(--color-blue-500)}.bg-blue-600{background-color:var(--color-blue-600)}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800{background-color:var(--color-gray-800)}.bg-gray-800\/60{background-color:#1e293999}@supports (color:color-mix(in lab, red, red)){.bg-gray-800\/60{background-color:color-mix(in oklab,var(--color-gray-800)60%,transparent)}}.bg-tech-cyan{background-color:var(--color-tech-cyan)}.bg-tech-cyan\/10{background-color:#58c7bc1a}@supports (color:color-mix(in lab, red, red)){.bg-tech-cyan\/10{background-color:color-mix(in oklab,var(--color-tech-cyan)10%,transparent)}}.bg-tech-dark{background-color:var(--color-tech-dark)}.bg-tech-dark\/60{background-color:#05070a99}@supports (color:color-mix(in lab, red, red)){.bg-tech-dark\/60{background-color:color-mix(in oklab,var(--color-tech-dark)60%,transparent)}}.bg-tech-dark\/95{background-color:#05070af2}@supports (color:color-mix(in lab, red, red)){.bg-tech-dark\/95{background-color:color-mix(in oklab,var(--color-tech-dark)95%,transparent)}}.bg-tech-green{background-color:var(--color-tech-green)}.bg-tech-green\/85{background-color:#9bd66bd9}@supports (color:color-mix(in lab, red, red)){.bg-tech-green\/85{background-color:color-mix(in oklab,var(--color-tech-green)85%,transparent)}}.bg-tech-purple{background-color:var(--color-tech-purple)}.bg-white{background-color:var(--color-white)}.bg-white\/60{background-color:#fff9}@supports (color:color-mix(in lab, red, red)){.bg-white\/60{background-color:color-mix(in oklab,var(--color-white)60%,transparent)}}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-gray-50{--tw-gradient-from:var(--color-gray-50);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-gray-800{--tw-gradient-from:var(--color-gray-800);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-gray-900{--tw-gradient-from:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-cyan{--tw-gradient-from:var(--color-tech-cyan);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-dark{--tw-gradient-from:var(--color-tech-dark);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-green{--tw-gradient-from:var(--color-tech-green);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-purple{--tw-gradient-from:var(--color-tech-purple);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-purple\/10{--tw-gradient-from:#d8aa4f1a}@supports (color:color-mix(in lab, red, red)){.from-tech-purple\/10{--tw-gradient-from:color-mix(in oklab,var(--color-tech-purple)10%,transparent)}}.from-tech-purple\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.via-tech-dark{--tw-gradient-via:var(--color-tech-dark);--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-gray-200{--tw-gradient-to:var(--color-gray-200);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-gray-500{--tw-gradient-to:var(--color-gray-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-cyan{--tw-gradient-to:var(--color-tech-cyan);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-cyan\/10{--tw-gradient-to:#58c7bc1a}@supports (color:color-mix(in lab, red, red)){.to-tech-cyan\/10{--tw-gradient-to:color-mix(in oklab,var(--color-tech-cyan)10%,transparent)}}.to-tech-cyan\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-dark{--tw-gradient-to:var(--color-tech-dark);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-green{--tw-gradient-to:var(--color-tech-green);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-pink{--tw-gradient-to:var(--color-tech-pink);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.object-cover{object-fit:cover}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.p-10{padding:calc(var(--spacing)*10)}.px-2{padding-inline:calc(var(--spacing)*2)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-7{padding-inline:calc(var(--spacing)*7)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-1{padding-block:calc(var(--spacing)*1)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-6{padding-block:calc(var(--spacing)*6)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-20{padding-block:calc(var(--spacing)*20)}.pt-8{padding-top:calc(var(--spacing)*8)}.pt-20{padding-top:calc(var(--spacing)*20)}.pt-24{padding-top:calc(var(--spacing)*24)}.pb-16{padding-bottom:calc(var(--spacing)*16)}.pb-24{padding-bottom:calc(var(--spacing)*24)}.text-center{text-align:center}.text-left{text-align:left}.font-\[\'Roboto\'\,\'sans-serif\'\]{font-family:Roboto,"sans-serif"}.font-display{font-family:var(--font-display)}.font-mono{font-family:var(--font-mono)}.font-playfair{font-family:var(--font-playfair)}.font-roboto{font-family:var(--font-roboto)}.font-tech{font-family:var(--font-tech)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-extrabold{--tw-font-weight:var(--font-weight-extrabold);font-weight:var(--font-weight-extrabold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.14em\]{--tw-tracking:.14em;letter-spacing:.14em}.tracking-\[0\.16em\]{--tw-tracking:.16em;letter-spacing:.16em}.tracking-\[0\.18em\]{--tw-tracking:.18em;letter-spacing:.18em}.break-words{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.text-blue-500{color:var(--color-blue-500)}.text-blue-600{color:var(--color-blue-600)}.text-blue-700{color:var(--color-blue-700)}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-gray-600{color:var(--color-gray-600)}.text-gray-700{color:var(--color-gray-700)}.text-gray-800{color:var(--color-gray-800)}.text-gray-900{color:var(--color-gray-900)}.text-tech-cyan{color:var(--color-tech-cyan)}.text-tech-dark{color:var(--color-tech-dark)}.text-tech-green{color:var(--color-tech-green)}.text-tech-pink{color:var(--color-tech-pink)}.text-tech-purple{color:var(--color-tech-purple)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.opacity-10{opacity:.1}.opacity-20{opacity:.2}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.mix-blend-multiply{mix-blend-mode:multiply}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a),0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-xl{--tw-shadow:0 20px 25px -5px var(--tw-shadow-color,#0000001a),0 8px 10px -6px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-red-500{--tw-ring-color:var(--color-red-500)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.blur-xl{--tw-blur:blur(var(--blur-xl));filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-shadow{transition-property:box-shadow;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media (hover:hover){.group-hover\:w-full:is(:where(.group):hover *){width:100%}.group-hover\:translate-y-1:is(:where(.group):hover *){--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.group-hover\:scale-105:is(:where(.group):hover *){--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.group-hover\:scale-110:is(:where(.group):hover *){--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:border-tech-cyan:hover{border-color:var(--color-tech-cyan)}.hover\:bg-blue-600:hover{background-color:var(--color-blue-600)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-tech-cyan:hover{background-color:var(--color-tech-cyan)}.hover\:bg-tech-cyan\/10:hover{background-color:#58c7bc1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-tech-cyan\/10:hover{background-color:color-mix(in oklab,var(--color-tech-cyan)10%,transparent)}}.hover\:bg-tech-cyan\/20:hover{background-color:#58c7bc33}@supports (color:color-mix(in lab, red, red)){.hover\:bg-tech-cyan\/20:hover{background-color:color-mix(in oklab,var(--color-tech-cyan)20%,transparent)}}.hover\:bg-tech-green\/20:hover{background-color:#9bd66b33}@supports (color:color-mix(in lab, red, red)){.hover\:bg-tech-green\/20:hover{background-color:color-mix(in oklab,var(--color-tech-green)20%,transparent)}}.hover\:bg-tech-purple\/20:hover{background-color:#d8aa4f33}@supports (color:color-mix(in lab, red, red)){.hover\:bg-tech-purple\/20:hover{background-color:color-mix(in oklab,var(--color-tech-purple)20%,transparent)}}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.hover\:bg-white\/20:hover{background-color:#fff3}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/20:hover{background-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.hover\:from-tech-cyan:hover{--tw-gradient-from:var(--color-tech-cyan);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hover\:from-tech-pink:hover{--tw-gradient-from:var(--color-tech-pink);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hover\:to-tech-green:hover{--tw-gradient-to:var(--color-tech-green);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hover\:to-tech-purple:hover{--tw-gradient-to:var(--color-tech-purple);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hover\:text-blue-400:hover{color:var(--color-blue-400)}.hover\:text-blue-600:hover{color:var(--color-blue-600)}.hover\:text-tech-cyan:hover{color:var(--color-tech-cyan)}.hover\:text-tech-green:hover{color:var(--color-tech-green)}.hover\:text-tech-purple:hover{color:var(--color-tech-purple)}.hover\:text-white:hover{color:var(--color-white)}.hover\:underline:hover{text-decoration-line:underline}.hover\:shadow-lg:hover{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a),0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}}@media (min-width:40rem){.sm\:mb-6{margin-bottom:calc(var(--spacing)*6)}.sm\:mb-8{margin-bottom:calc(var(--spacing)*8)}.sm\:mb-12{margin-bottom:calc(var(--spacing)*12)}.sm\:min-h-screen{min-height:100vh}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-4{padding-inline:calc(var(--spacing)*4)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:px-8{padding-inline:calc(var(--spacing)*8)}.sm\:py-4{padding-block:calc(var(--spacing)*4)}.sm\:py-16{padding-block:calc(var(--spacing)*16)}.sm\:pt-20{padding-top:calc(var(--spacing)*20)}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:flex-row{flex-direction:row}.md\:pt-28{padding-top:calc(var(--spacing)*28)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.md\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.md\:text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[0\.9fr_1\.1fr\]{grid-template-columns:.9fr 1.1fr}.lg\:grid-cols-\[1fr_auto\]{grid-template-columns:1fr auto}.lg\:gap-8{gap:calc(var(--spacing)*8)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.lg\:text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.lg\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}}.gradient-text{color:#d8aa4f;-webkit-text-fill-color:currentColor}.glass-effect{background:linear-gradient(#f6f1e811,#f6f1e809),#0b0f12c7;border:1px solid #f6f1e81f;box-shadow:0 18px 48px #02050847}.glass-effect.rounded-2xl,.project-card.rounded-2xl,.cv-gate.rounded-2xl,.glass-effect.rounded-xl{border-radius:.5rem}.skip-link{z-index:60;color:#fff;background:#05070af2;border:1px solid #38d8c873;border-radius:.75rem;padding:.75rem 1rem;font-weight:600;transition:transform .2s;position:absolute;top:1rem;left:1rem;transform:translateY(-200%)}.skip-link:focus{transform:translateY(0)}h1,h2,h3{letter-spacing:0;text-wrap:balance;font-family:Bahnschrift,Aptos Display,Segoe UI Variable,system-ui,sans-serif}.btn{color:#fff;background-color:#1a202c;border-radius:5px;padding:10px 20px;text-decoration:none;transition:background-color .3s,transform .3s}.btn:hover{background-color:#4a5568;transform:scale(1.05)}.gradient-bg{color:#fff;background:linear-gradient(135deg,#1a202c,#366ecd)}social-links a{color:#fff;margin:0 10px;transition:color .3s}social-links a:hover{color:gold}social-links{z-index:2000;position:relative}#goose-animation{z-index:1000}h1{color:#f6f1e8}body{color:#f6f1e8;background:linear-gradient(90deg,#f6f1e806 1px,#0000 1px) 0 0/40px 40px,linear-gradient(#f6f1e805 1px,#0000 1px) 0 0/40px 40px,linear-gradient(#05070a 0%,#0c1114 44%,#070908 100%)}html{color-scheme:dark;scroll-behavior:smooth}.touch-target{touch-action:manipulation;min-width:46px;min-height:46px}.anchor-section{scroll-margin-top:5.5rem}.mobile-menu-button-open{z-index:70;position:fixed;top:1rem;right:calc(min(24rem,100vw - 4rem) + .5rem);translate:none}#mobile-menu{z-index:60}.project-action-link{border-radius:.75rem;align-items:center;min-height:44px;padding:.5rem .75rem;display:inline-flex}.project-icon-link,.footer-social-link{touch-action:manipulation;border-radius:9999px;justify-content:center;align-items:center;min-width:44px;min-height:44px;display:inline-flex}.footer-link-list a{touch-action:manipulation;align-items:center;min-width:44px;min-height:44px;display:inline-flex}.drawer-scroll-region{overscroll-behavior:contain}.hero-photo-bg{background-position:50%;background-repeat:no-repeat;background-size:cover}.hero-signal-grid{opacity:.42;background-color:#0000;background-image:linear-gradient(#38d8c817 1px,#0000 1px),linear-gradient(90deg,#f2b84b12 1px,#0000 1px);background-position:0 0,0 0;background-repeat:repeat,repeat;background-size:72px 72px;background-attachment:scroll,scroll;background-origin:padding-box,padding-box;background-clip:border-box,border-box;-webkit-mask-image:linear-gradient(90deg,#000 0% 56%,#0000 100%);mask-image:linear-gradient(90deg,#000 0% 56%,#0000 100%)}.hire-command-center{background:linear-gradient(135deg,#05070af7,#0e1214ed)}.hire-grid-plane{opacity:.34;background-color:#0000;background-image:linear-gradient(#9bd66b14 1px,#0000 1px),linear-gradient(90deg,#38d8c814 1px,#0000 1px);background-position:0 0,0 0;background-repeat:repeat,repeat;background-size:44px 44px;background-attachment:scroll,scroll;background-origin:padding-box,padding-box;background-clip:border-box,border-box;position:absolute;inset:0;-webkit-mask-image:linear-gradient(#0000,#000 12% 86%,#0000);mask-image:linear-gradient(#0000,#000 12% 86%,#0000)}.hire-role-tabs{grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem;display:grid}.hire-role-tab,.hire-command-button,.hire-contact-link,.roast-button,.retro-link-button,.retro-game-action,.contact-primary-button,.contact-email-strip button,.cv-unlock-button,.cv-public-link,.cv-secondary-link{touch-action:manipulation;border-radius:9999px;justify-content:center;align-items:center;font-weight:900;display:inline-flex}.hire-role-tab{color:#d1d5db;background:#ffffff0e;border:1px solid #ffffff21;min-height:48px;transition:transform .18s,border-color .18s,background-color .18s,color .18s}.hire-role-tab:hover,.hire-role-tab:focus-visible,.hire-role-tab.is-active{color:#f6f1e8;background:#2dd4bf29;border-color:#2dd4bf94;transform:translateY(-1px)}.hire-console{background:#080b0deb;border:1px solid #58c7bc42;border-radius:.5rem;overflow:hidden;box-shadow:0 28px 80px #02050861}.hire-console-header,.hire-console-actions{border-bottom:1px solid #ffffff1a;justify-content:space-between;align-items:center;gap:1rem;padding:1rem 1.25rem;display:flex}.hire-console-actions{border-top:1px solid #ffffff1a;border-bottom:0;flex-wrap:wrap}.hire-status-dot{background:#84cc16;border-radius:9999px;width:.7rem;height:.7rem;box-shadow:0 0 20px #84cc16db}.hire-console-body{padding:clamp(1.25rem,3vw,2rem)}.hire-metric{background:#ffffff0e;border:1px solid #ffffff1a;border-radius:.5rem;min-height:6.5rem;padding:1rem}.hire-metric-label{color:#94a3b8;letter-spacing:.12em;text-transform:uppercase;margin-bottom:.45rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;display:block}.hire-metric strong{color:#f6f1e8;line-height:1.25}.hire-proof-list{color:#d1d5db;gap:.8rem;display:grid}.hire-proof-list li{padding-left:1.5rem;position:relative}.hire-proof-list li:before{content:">";color:#84cc16;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-weight:900;position:absolute;left:0}.hire-command-button{color:#2dd4bf;background:#2dd4bf29;border:1px solid #2dd4bf61;min-height:46px;padding:.75rem 1rem}.hire-contact-link{color:#05070a;background:#f2b84b;min-height:46px;padding:.75rem 1rem}.retro-archive-section{background:linear-gradient(90deg,#d8aa4f0e 1px,#0000 1px) 0 0/54px 54px,linear-gradient(#58c7bc0b 1px,#0000 1px) 0 0/54px 54px,linear-gradient(135deg,#05070afa,#0d1117f0)}.retro-archive-header{grid-template-columns:minmax(0,1fr) minmax(18rem,.42fr);align-items:end;gap:2rem;margin-bottom:2.5rem;display:grid}.retro-archive-note,.retro-request-card,.retro-policy-card,.retro-game-card{background:#f6f1e80d;border:1px solid #ffffff1c;border-radius:.5rem}.retro-archive-note{color:#d1d5db;padding:1.25rem;line-height:1.55}.retro-policy-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:.8rem;margin-bottom:3rem;display:grid}.retro-policy-card{min-height:5.5rem;padding:1rem}.retro-policy-value{color:#f2b84b;font-size:1.75rem;font-weight:900;line-height:1;display:block}.retro-policy-label{color:#9ca3af;letter-spacing:.08em;text-transform:uppercase;margin-top:.45rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;display:block}.retro-section-title{justify-content:space-between;align-items:end;gap:2rem;margin-bottom:1.25rem;display:flex}.retro-section-title h3{color:#f6f1e8;margin-top:.35rem;font-size:clamp(1.55rem,2.4vw,2.35rem);font-weight:900}.retro-section-title>p{color:#9ca3af;max-width:30rem}.retro-catalog-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem;display:grid}.retro-game-card{grid-template-rows:auto auto auto 1fr auto;gap:1rem;min-height:23rem;padding:1.25rem;transition:transform .18s,border-color .18s,background-color .18s;display:grid}.retro-game-card:hover,.retro-game-card:focus-within{background:#f6f1e812;border-color:#38d8c86b;transform:translateY(-3px)}.retro-card-topline{justify-content:space-between;align-items:center;gap:.75rem;display:flex}.retro-card-icon{background:#05070a75;border:1px solid #ffffff1f;border-radius:.5rem;place-items:center;width:3rem;height:3rem;font-size:1.65rem;display:inline-grid}.retro-source-pill{color:#38d8c8;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;background:#38d8c81a;border:1px solid #38d8c852;border-radius:9999px;align-items:center;min-height:1.75rem;padding:.28rem .62rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.68rem;font-weight:800;display:inline-flex}.retro-game-card h3{color:#f6f1e8;font-size:1.35rem;font-weight:900}.retro-game-card p{color:#d1d5db;font-size:.94rem;line-height:1.62}.retro-card-meta{gap:.55rem;display:grid}.retro-card-meta div{grid-template-columns:4.3rem minmax(0,1fr);align-items:start;gap:.75rem;display:grid}.retro-card-meta dt{color:#9ca3af;letter-spacing:.08em;text-transform:uppercase;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.68rem}.retro-card-meta dd{color:#f6f1e8;font-size:.84rem}.retro-link-button,.retro-game-action{color:#38d8c8;background:#38d8c829;border:1px solid #38d8c861;gap:.45rem;min-height:46px;padding:.75rem 1rem}.retro-link-button:hover,.retro-link-button:focus-visible,.retro-game-action:hover,.retro-game-action:focus-visible{color:#f6f1e8;background:#38d8c83d}.retro-game-action{width:100%}.retro-request-card{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:1.5rem;padding:1.25rem;display:grid}.retro-request-card h3{color:#f6f1e8;margin-bottom:.35rem;font-size:1.65rem;font-weight:900}.retro-request-card p{color:#d1d5db}.contact-command-section{background:linear-gradient(90deg,#f2b84b0d 1px,#0000 1px) 0 0/64px 64px,linear-gradient(#38d8c80f 1px,#0000 1px) 0 0/64px 64px,#05070a}.contact-hero-email{background:#05070ab8;border:1px solid #f2b84b61;border-radius:.5rem;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:1rem;max-width:58rem;margin:-1.25rem auto 2rem;padding:.9rem 1rem;display:grid}.contact-hero-email span,.contact-hero-email small{color:#9ca3af;letter-spacing:.1em;text-transform:uppercase;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;font-weight:900}.contact-hero-email a{color:#f6f1e8;overflow-wrap:anywhere;font-weight:900}.contact-hero-email a:hover,.contact-hero-email a:focus-visible{color:#f2b84b}.contact-command-grid{grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);align-items:start;gap:1rem;display:grid}.contact-direct-panel,.contact-draft-card,.contact-support-panel,.cv-gate{border-radius:.5rem;padding:clamp(1.25rem,2.6vw,2rem)}.contact-direct-panel{min-height:100%}.contact-direct-panel h3,.contact-draft-card h3,.contact-support-panel h3,.cv-gate h3{color:#f6f1e8;margin-bottom:.75rem;font-size:clamp(1.45rem,2vw,2rem);font-weight:900}.contact-direct-copy,.contact-card-header p,.contact-support-panel p,.cv-gate p{color:#d1d5db;line-height:1.65}.contact-email-strip{background:#f2b84b1a;border:1px solid #f2b84b6b;border-radius:.5rem;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:.85rem;margin:1.5rem 0 .4rem;padding:.85rem;display:grid}.contact-email-strip span:first-child{color:#f2b84b;letter-spacing:.1em;text-transform:uppercase;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;font-weight:900}.contact-email-strip strong{color:#f6f1e8;overflow-wrap:anywhere;min-width:0}.contact-email-strip button,.contact-primary-button{min-height:46px;padding:.72rem 1rem}.contact-email-strip button{color:#f6f1e8;background:#05070a94;border:1px solid #f6f1e82e;gap:.4rem}.contact-copy-status{color:#9bd66b;min-height:1.35rem;font-size:.9rem}.contact-action-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem;margin-top:1.2rem;display:grid}.contact-action-card{color:#f6f1e8;background:#ffffff0e;border:1px solid #ffffff24;border-radius:.5rem;gap:.25rem;min-height:7.4rem;padding:1rem;transition:transform .18s,border-color .18s,background-color .18s;display:grid}.contact-action-card:hover,.contact-action-card:focus-visible{background:#38d8c81a;border-color:#38d8c87a;transform:translateY(-2px)}.contact-action-card span{color:#38d8c8;font-weight:900}.contact-action-card small{color:#9ca3af}.contact-draft-card{gap:1rem;display:grid}.contact-card-header{margin-bottom:.25rem}.contact-field-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;display:grid}.contact-draft-card label,.cv-gate label{color:#d1d5db;margin-bottom:.45rem;font-size:.9rem;font-weight:800;display:block}.contact-draft-card input,.contact-draft-card select,.contact-draft-card textarea{color:#f6f1e8;background:#05070aad;border:1px solid #ffffff29;border-radius:.5rem;outline:0;width:100%;padding:.85rem .95rem}.contact-draft-card textarea{resize:vertical}.contact-draft-card input:focus,.contact-draft-card select:focus,.contact-draft-card textarea:focus{border-color:#38d8c89e;box-shadow:0 0 0 3px #38d8c821}.contact-primary-button{color:#05070a;background:#f2b84b;gap:.65rem}.contact-zero-backend-note{color:#9ca3af;font-size:.9rem}.contact-support-panel{border-color:#9bd66b3d}.contact-availability{color:#9bd66b;align-items:center;gap:.6rem;margin-bottom:1rem;display:flex}.contact-availability span{background:#9bd66b;border-radius:9999px;width:.8rem;height:.8rem;box-shadow:0 0 20px #9bd66bb8}.contact-support-panel ul{color:#d1d5db;gap:.45rem;margin-top:1rem;display:grid}.contact-support-panel li{padding-left:1.25rem;position:relative}.contact-support-panel li:before{content:">";color:#9bd66b;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-weight:900;position:absolute;left:0}.cv-gate{border-color:#f2b84b3d}.cv-gate-row{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.75rem;display:grid}.cv-gate-input{color:#f6f1e8;background:#05070a9e;border:1px solid #ffffff29;border-radius:.85rem;outline:0;width:100%;min-height:46px;padding:.75rem .95rem}.cv-gate-input:focus{border-color:#38d8c89e;box-shadow:0 0 0 3px #38d8c821}.cv-unlock-button,.cv-public-link{color:#05070a;background:#f2b84b;min-height:46px;padding:.75rem 1rem}.cv-secondary-link{color:#38d8c8;background:#38d8c817;border:1px solid #38d8c852;min-height:46px;padding:.75rem 1rem}.cv-unlocked-panel{flex-wrap:wrap;gap:.75rem;display:flex}.cv-unlocked-panel[hidden]{display:none}.portfolio-fab-stack{z-index:55;flex-direction:column;align-items:flex-end;gap:.5rem;display:flex;position:fixed;bottom:1rem;right:1rem}.portfolio-fab{color:#fff;touch-action:manipulation;border:1px solid #ffffff29;border-radius:9999px;justify-content:center;align-items:center;gap:.5rem;min-height:44px;padding:.65rem .9rem;font-weight:800;transition:transform .2s,background-color .2s,border-color .2s;display:inline-flex;box-shadow:0 14px 36px #02050857}.portfolio-fab:hover,.portfolio-fab:focus-visible{transform:translateY(-2px)}.portfolio-fab-primary{color:#05070a;background:#f2b84beb}.portfolio-fab-secondary{background:#080b11c7}.clippy,.clippy-balloon{pointer-events:none}.command-palette{z-index:1100;place-items:end center;padding:1rem;display:grid;position:fixed;inset:0}.command-palette.hidden{display:none}.command-palette-backdrop{background:#000000a3;position:absolute;inset:0}.command-shell{z-index:1;background:#080b11f5;border:1px solid #2dd4bf47;border-radius:.5rem;grid-template-rows:auto minmax(220px,1fr) auto auto;width:min(900px,100%);max-height:min(760px,100svh - 2rem);display:grid;position:relative;overflow:hidden;box-shadow:0 24px 80px #0000007a}.command-shell-header{border-bottom:1px solid #ffffff1a;justify-content:space-between;align-items:center;gap:1rem;padding:1rem 1.1rem;display:flex}.command-kicker{color:#2dd4bf;letter-spacing:.16em;text-transform:uppercase;margin:0 0 .25rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.74rem}.command-shell h2{color:#fff;margin:0;font-size:clamp(1.2rem,2vw,1.75rem)}.command-close{color:#fff;background:#ffffff14;border-radius:9999px;justify-content:center;align-items:center;font-size:1.7rem;line-height:1;display:inline-flex}.command-output{color:#d1d5db;padding:1rem 1.1rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.9rem;line-height:1.55;overflow-y:auto}.command-line{margin-bottom:1rem}.command-input-line{color:#f59e0b}.command-response{white-space:pre-wrap}.command-response a{color:#2dd4bf;text-underline-offset:3px;text-decoration:underline}.command-response strong{color:#fff}.command-form{background:#ffffff09;border-top:1px solid #ffffff1a;grid-template-columns:auto 1fr auto;align-items:center;gap:.7rem;padding:.85rem 1.1rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;display:grid}.command-form span{color:#84cc16;font-weight:800}.command-form input{color:#fff;background:0 0;border:0;outline:0;min-width:0}.command-form button,.command-suggestions button{color:#2dd4bf;background:#2dd4bf24;border-radius:9999px;padding:.55rem .85rem;font-weight:800}.command-suggestions{flex-wrap:wrap;gap:.5rem;padding:0 1.1rem 1rem;display:flex}.project-visual{isolation:isolate;place-items:center;min-height:12rem;display:grid;position:relative;overflow:hidden}.project-visual:before{content:"";opacity:.35;background-color:#0000;background-image:linear-gradient(#ffffff1f 1px,#0000 1px),linear-gradient(90deg,#ffffff1a 1px,#0000 1px);background-position:0 0,0 0;background-repeat:repeat,repeat;background-size:36px 36px;background-attachment:scroll,scroll;background-origin:padding-box,padding-box;background-clip:border-box,border-box;position:absolute;inset:0}.project-visual-road{background:linear-gradient(135deg,#f59e0bc7,#111827f2)}.project-visual-orbit{background:linear-gradient(135deg,#2dd4bfad,#2563ebb8)}.project-visual-tool{background:linear-gradient(135deg,#84cc16a3,#111827f0)}.project-visual-agent{background:linear-gradient(135deg,#f43f5ea8,#2dd4bf6b)}.project-visual-icon{z-index:1;font-size:4.5rem;transition:transform .3s;position:relative}.project-card:hover .project-visual-icon{transform:scale(1.08)rotate(-2deg)}.project-visual-label{z-index:1;color:#fff;letter-spacing:.12em;text-transform:uppercase;background:#080b11b8;border-radius:9999px;padding:.35rem .7rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;position:absolute;top:1rem;left:1rem}.repo-badge,.repo-chip{white-space:nowrap;border-radius:9999px;align-items:center;display:inline-flex}.repo-badge{color:#f59e0b;background:#f59e0b29;padding:.35rem .65rem;font-size:.72rem;font-weight:900}.repo-chip{color:#d1d5db;background:#ffffff12;padding:.28rem .6rem;font-size:.76rem}.reviews-section{border-block:1px solid #ffffff0f}.review-grid{grid-template-columns:repeat(6,minmax(0,1fr));gap:2rem;display:grid}.review-card{text-align:center;background:#ffffff0e;border:1px solid #ffffff1c;border-radius:.5rem;flex-direction:column;grid-column:span 2;justify-content:center;min-height:21.35rem;padding:2rem;transition:transform .18s,border-color .18s,background-color .18s;display:flex}.review-card:hover{background:#ffffff13;border-color:#f59e0b6b;transform:translateY(-4px)}.review-card-accent{background:linear-gradient(#f59e0b29,#ffffff0e)}.review-card-wide{grid-column:span 3;min-height:18.35rem}.review-mark{margin-bottom:1rem;font-size:2.2rem}.review-card h3{color:#f6f1e8;margin-bottom:.85rem;font-size:1.25rem}.review-card p{color:#d1d5db;line-height:1.65}.review-author{color:#9ca3af;margin-top:1rem;font-size:.9rem}.review-note{color:#6b7280;margin-top:.25rem;font-size:.78rem;font-style:italic}.review-roast-teaser{background:#080b119e;border:1px solid #2dd4bf38;border-radius:.5rem;grid-template-columns:1fr auto;align-items:center;gap:1.5rem;padding:1.5rem;display:grid}.roast-button{color:#fb7185;background:#f43f5e29;border:1px solid #f43f5e70;min-height:46px;padding:.75rem 1rem}.goose-desk-scene{isolation:isolate;width:148px;height:148px}.goose-frame-fallback,.goose-desk-goose{object-fit:contain;filter:drop-shadow(0 18px 28px #00000057);width:100%;height:100%}.goose-desk-goose{z-index:2;transform-origin:50% 88%;animation:3.8s ease-in-out infinite goose-waddle;position:relative}.goose-desk-bubble{z-index:3;color:#f2b84b;background:#05070ac7;border:1px solid #f6f1e838;border-radius:9999px;padding:.25rem .55rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;font-weight:900;position:absolute;top:.2rem;right:-.45rem;transform:rotate(7deg)}.goose-desk-shadow{z-index:1;filter:blur(4px);background:#00000057;border-radius:9999px;height:.72rem;position:absolute;bottom:.18rem;left:1.1rem;right:1.1rem}.goose-terror-trigger{color:#f2b84b;background:#f2b84b29;border:1px solid #f2b84b7a;border-radius:9999px;justify-content:center;align-items:center;min-height:46px;padding:.65rem 1rem;font-weight:900;display:inline-flex}.goose-terror-trigger:hover,.goose-terror-trigger:focus-visible,.goose-terror-trigger[aria-pressed=true]{color:#f6f1e8;background:#f2b84b47}.goose-terror-layer{z-index:1200;pointer-events:none;position:fixed;inset:0;overflow:hidden}.goose-terror-toolbar{top:calc(4.8rem + env(safe-area-inset-top));color:#f6f1e8;pointer-events:auto;background:#05070adb;border:1px solid #f2b84b85;border-radius:9999px;align-items:center;gap:.75rem;padding:.45rem .55rem .45rem .9rem;display:inline-flex;position:fixed;right:1rem;box-shadow:0 16px 46px #0000005c}.goose-terror-toolbar span{color:#f2b84b;letter-spacing:.1em;text-transform:uppercase;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;font-weight:900}.goose-terror-toolbar button{color:#05070a;background:#f2b84b;border-radius:9999px;min-height:40px;padding:.55rem .8rem;font-weight:900}.goose-terror-goose{filter:drop-shadow(0 20px 26px #0000006b);pointer-events:none;width:clamp(82px,9vw,132px);height:auto;transition:left .85s ease-in-out,top .85s ease-in-out,transform .2s;animation:.72s steps(2,end) infinite goose-terror-bob;position:fixed}.goose-terror-goose.goose-terror-flipped{transform:scaleX(-1)}.goose-terror-note{color:#10151a;width:min(210px,100vw - 2rem);min-height:7rem;transform:rotate(var(--note-rotate,-2deg));pointer-events:none;background:#f2e7a8;border:1px solid #10151a2e;border-radius:.35rem;padding:1.05rem .85rem .85rem;position:fixed;box-shadow:0 18px 42px #00000057}.goose-terror-note:nth-of-type(2n){--note-rotate:2deg}.goose-terror-note strong{letter-spacing:.08em;text-transform:uppercase;margin-bottom:.45rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.68rem;display:block}.goose-terror-note span{font-weight:900;line-height:1.35;display:block}.goose-terror-note button{color:#10151a;pointer-events:auto;font-size:1.25rem;font-weight:900;position:absolute;top:.25rem;right:.35rem}.goose-terror-footprint{background:#f2b84b75;border-radius:9999px;width:.72rem;height:.42rem;animation:5.2s forwards goose-footprint-fade;position:fixed;transform:rotate(-18deg)}@keyframes goose-terror-bob{0%,to{translate:0}50%{translate:0 -6px}}@keyframes goose-footprint-fade{0%{opacity:.75}to{opacity:0;transform:translateY(10px)rotate(-18deg)}}@keyframes goose-waddle{0%,to{transform:translate(0)translateY(0)rotate(-2deg)}25%{transform:translate(-3px)translateY(-2px)rotate(2deg)}50%{transform:translate(2px)translateY(0)rotate(-1deg)}75%{transform:translate(4px)translateY(-3px)rotate(2deg)}}@media (prefers-reduced-motion:reduce){.goose-desk-goose,.goose-terror-goose,.goose-terror-footprint{animation:none}.goose-terror-goose{transition:none}}@media (max-width:640px){.anchor-section{padding-bottom:8rem}.contact-command-section.anchor-section{padding-top:3rem}.contact-command-hero{margin-bottom:2rem}.contact-hero-email{text-align:center;grid-template-columns:1fr;gap:.35rem;margin-top:-.75rem}.contact-direct-panel,.contact-draft-card,.contact-support-panel,.cv-gate{padding:1.25rem}.portfolio-fab-stack{left:.75rem;right:.75rem;bottom:calc(.75rem + env(safe-area-inset-bottom));grid-template-columns:1fr 1fr 1.2fr;align-items:stretch;display:grid}#hero [data-command-open],.hero-scroll-cue{display:none}.portfolio-fab{width:100%;padding-inline:.55rem;font-size:.82rem}.command-palette{place-items:end center;padding:0}.command-shell{border-radius:18px 18px 0 0;max-height:92svh}.command-output{font-size:.82rem}.goose-terror-toolbar{top:calc(4.5rem + env(safe-area-inset-top));justify-content:space-between;left:.75rem;right:.75rem}.goose-terror-toolbar span{font-size:.64rem}.goose-terror-note{width:min(190px,100vw - 2rem)}}@media (max-width:900px){.retro-archive-header,.retro-request-card{grid-template-columns:1fr}.retro-policy-grid,.retro-catalog-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.contact-command-grid{grid-template-columns:1fr}.retro-section-title{flex-direction:column;align-items:start;gap:.65rem}.review-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.review-card,.review-card-wide{grid-column:span 1}.review-roast-teaser{grid-template-columns:1fr}}@media (max-width:640px){.hire-role-tabs,.retro-policy-grid,.retro-catalog-grid,.review-grid,.contact-action-grid,.contact-field-grid{grid-template-columns:1fr}.retro-game-card{min-height:auto}.retro-card-meta div{grid-template-columns:1fr;gap:.2rem}.retro-link-button,.retro-game-action{width:100%}.hire-console-header,.hire-console-actions{flex-direction:column;align-items:stretch}.cv-gate-row{grid-template-columns:1fr}.cv-unlocked-panel{flex-direction:column}.cv-unlock-button,.cv-public-link,.cv-secondary-link{width:100%}}.scroll-animation{opacity:0;will-change:transform,opacity;transition:none;transform:translateY(20px)scale(.95)}.scroll-animation.visible{opacity:1;transition:transform .6s ease-out,opacity .6s ease-out;transform:translateY(0)scale(1)}@keyframes zoomInOut{0%{opacity:0;transform:scale(.8)}50%{opacity:1;transform:scale(1.1)}to{opacity:1;transform:scale(1)}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-20px)}} \ No newline at end of file +@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z:0;--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-space-x-reverse:0;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--color-red-500:oklch(63.7% .237 25.331);--color-gray-200:oklch(92.8% .006 264.531);--color-gray-300:oklch(87.2% .01 258.338);--color-gray-400:oklch(70.7% .022 261.325);--color-gray-500:oklch(55.1% .027 264.364);--color-gray-600:oklch(44.6% .03 256.802);--color-gray-700:oklch(37.3% .034 259.733);--color-gray-800:oklch(27.8% .033 256.848);--color-gray-900:oklch(21% .034 264.665);--color-black:#000;--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-md:28rem;--container-xl:36rem;--container-2xl:42rem;--container-3xl:48rem;--container-4xl:56rem;--container-5xl:64rem;--container-6xl:72rem;--container-7xl:80rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-sm:.875rem;--text-sm--line-height:calc(1.25/.875);--text-base:1rem;--text-base--line-height:calc(1.5/1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75/1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2/1.5);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25/1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5/2.25);--text-5xl:3rem;--text-5xl--line-height:1;--text-6xl:3.75rem;--text-6xl--line-height:1;--text-7xl:4.5rem;--text-7xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--font-weight-bold:700;--font-weight-black:900;--leading-tight:1.25;--leading-relaxed:1.625;--radius-lg:.5rem;--radius-2xl:1rem;--ease-in-out:cubic-bezier(.4,0,.2,1);--animate-pulse:pulse 2s cubic-bezier(.4,0,.6,1)infinite;--animate-bounce:bounce 1s infinite;--blur-xl:24px;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4,0,.2,1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono);--color-tech-dark:#05070a;--color-tech-cyan:#58c7bc;--color-tech-purple:#d8aa4f;--color-tech-green:#9bd66b;--color-tech-pink:#ff766d;--font-display:"Bahnschrift","Aptos Display","Segoe UI Variable","Segoe UI",system-ui,sans-serif;--font-tech:"Cascadia Mono","JetBrains Mono",Consolas,Monaco,monospace;--animate-fade-in:fadeIn .6s ease-in-out;--animate-float:float 6s ease-in-out infinite}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.pointer-events-none{pointer-events:none}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing)*0)}.-top-10{top:calc(var(--spacing)*-10)}.top-0{top:calc(var(--spacing)*0)}.top-1\/2{top:50%}.-right-10{right:calc(var(--spacing)*-10)}.right-0{right:calc(var(--spacing)*0)}.-bottom-1{bottom:calc(var(--spacing)*-1)}.-bottom-10{bottom:calc(var(--spacing)*-10)}.bottom-8{bottom:calc(var(--spacing)*8)}.-left-10{left:calc(var(--spacing)*-10)}.left-0{left:calc(var(--spacing)*0)}.left-1\/2{left:50%}.left-5{left:calc(var(--spacing)*5)}.z-10{z-index:10}.z-40{z-index:40}.z-50{z-index:50}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-4{margin-inline:calc(var(--spacing)*4)}.mx-auto{margin-inline:auto}.mt-1{margin-top:calc(var(--spacing)*1)}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-3{margin-top:calc(var(--spacing)*3)}.mt-5{margin-top:calc(var(--spacing)*5)}.mt-6{margin-top:calc(var(--spacing)*6)}.mt-8{margin-top:calc(var(--spacing)*8)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.mt-14{margin-top:calc(var(--spacing)*14)}.mt-20{margin-top:calc(var(--spacing)*20)}.mt-auto{margin-top:auto}.mr-2{margin-right:calc(var(--spacing)*2)}.mr-3{margin-right:calc(var(--spacing)*3)}.mb-2{margin-bottom:calc(var(--spacing)*2)}.mb-3{margin-bottom:calc(var(--spacing)*3)}.mb-4{margin-bottom:calc(var(--spacing)*4)}.mb-5{margin-bottom:calc(var(--spacing)*5)}.mb-6{margin-bottom:calc(var(--spacing)*6)}.mb-8{margin-bottom:calc(var(--spacing)*8)}.mb-10{margin-bottom:calc(var(--spacing)*10)}.mb-12{margin-bottom:calc(var(--spacing)*12)}.mb-16{margin-bottom:calc(var(--spacing)*16)}.ml-3{margin-left:calc(var(--spacing)*3)}.ml-auto{margin-left:auto}.block{display:block}.contents{display:contents}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-0\.5{height:calc(var(--spacing)*.5)}.h-2\.5{height:calc(var(--spacing)*2.5)}.h-3{height:calc(var(--spacing)*3)}.h-10{height:calc(var(--spacing)*10)}.h-11{height:calc(var(--spacing)*11)}.h-16{height:calc(var(--spacing)*16)}.h-24{height:calc(var(--spacing)*24)}.h-32{height:calc(var(--spacing)*32)}.h-72{height:calc(var(--spacing)*72)}.h-96{height:calc(var(--spacing)*96)}.h-full{height:100%}.max-h-56{max-height:calc(var(--spacing)*56)}.min-h-11{min-height:calc(var(--spacing)*11)}.min-h-\[20px\]{min-height:20px}.min-h-\[72svh\]{min-height:72svh}.min-h-\[84svh\]{min-height:84svh}.w-0{width:calc(var(--spacing)*0)}.w-1{width:calc(var(--spacing)*1)}.w-2\.5{width:calc(var(--spacing)*2.5)}.w-3{width:calc(var(--spacing)*3)}.w-6{width:calc(var(--spacing)*6)}.w-11{width:calc(var(--spacing)*11)}.w-16{width:calc(var(--spacing)*16)}.w-72{width:calc(var(--spacing)*72)}.w-96{width:calc(var(--spacing)*96)}.w-\[calc\(100vw-4rem\)\]{width:calc(100vw - 4rem)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-3xl{max-width:var(--container-3xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-5xl{max-width:var(--container-5xl)}.max-w-6xl{max-width:var(--container-6xl)}.max-w-7xl{max-width:var(--container-7xl)}.max-w-md{max-width:var(--container-md)}.max-w-sm{max-width:var(--container-sm)}.max-w-xl{max-width:var(--container-xl)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.shrink{flex-shrink:1}.-translate-x-1\/2{--tw-translate-x:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.translate-x-full{--tw-translate-x:100%;translate:var(--tw-translate-x)var(--tw-translate-y)}.-translate-y-1\/2{--tw-translate-y:calc(calc(1/2*100%)*-1);translate:var(--tw-translate-x)var(--tw-translate-y)}.transform{transform:var(--tw-rotate-x,)var(--tw-rotate-y,)var(--tw-rotate-z,)var(--tw-skew-x,)var(--tw-skew-y,)}.animate-bounce{animation:var(--animate-bounce)}.animate-fade-in{animation:var(--animate-fade-in)}.animate-float{animation:var(--animate-float)}.animate-pulse{animation:var(--animate-pulse)}.resize{resize:both}.list-inside{list-style-position:inside}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.items-start{align-items:flex-start}.items-stretch{align-items:stretch}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-2{gap:calc(var(--spacing)*2)}.gap-3{gap:calc(var(--spacing)*3)}.gap-4{gap:calc(var(--spacing)*4)}.gap-5{gap:calc(var(--spacing)*5)}.gap-6{gap:calc(var(--spacing)*6)}.gap-8{gap:calc(var(--spacing)*8)}.gap-10{gap:calc(var(--spacing)*10)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*2)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*3)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*5)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*5)*calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*6)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*6)*calc(1 - var(--tw-space-y-reverse)))}.gap-x-6{column-gap:calc(var(--spacing)*6)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-3>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*3)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*3)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}:where(.space-x-8>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*8)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*8)*calc(1 - var(--tw-space-x-reverse)))}.overflow-hidden{overflow:hidden}.overflow-x-hidden{overflow-x:hidden}.overflow-y-auto{overflow-y:auto}.scroll-smooth{scroll-behavior:smooth}.rounded-2xl{border-radius:var(--radius-2xl)}.rounded-full{border-radius:3.40282e38px}.rounded-lg{border-radius:var(--radius-lg)}.border{border-style:var(--tw-border-style);border-width:1px}.border-2{border-style:var(--tw-border-style);border-width:2px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-l-4{border-left-style:var(--tw-border-style);border-left-width:4px}.border-gray-600{border-color:var(--color-gray-600)}.border-gray-700{border-color:var(--color-gray-700)}.border-gray-800{border-color:var(--color-gray-800)}.border-tech-cyan\/20{border-color:#58c7bc33}@supports (color:color-mix(in lab, red, red)){.border-tech-cyan\/20{border-color:color-mix(in oklab,var(--color-tech-cyan)20%,transparent)}}.border-tech-cyan\/30{border-color:#58c7bc4d}@supports (color:color-mix(in lab, red, red)){.border-tech-cyan\/30{border-color:color-mix(in oklab,var(--color-tech-cyan)30%,transparent)}}.border-tech-cyan\/40{border-color:#58c7bc66}@supports (color:color-mix(in lab, red, red)){.border-tech-cyan\/40{border-color:color-mix(in oklab,var(--color-tech-cyan)40%,transparent)}}.border-transparent{border-color:#0000}.border-white\/10{border-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.border-white\/10{border-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.border-white\/15{border-color:#ffffff26}@supports (color:color-mix(in lab, red, red)){.border-white\/15{border-color:color-mix(in oklab,var(--color-white)15%,transparent)}}.border-white\/20{border-color:#fff3}@supports (color:color-mix(in lab, red, red)){.border-white\/20{border-color:color-mix(in oklab,var(--color-white)20%,transparent)}}.border-white\/30{border-color:#ffffff4d}@supports (color:color-mix(in lab, red, red)){.border-white\/30{border-color:color-mix(in oklab,var(--color-white)30%,transparent)}}.bg-black\/30{background-color:#0000004d}@supports (color:color-mix(in lab, red, red)){.bg-black\/30{background-color:color-mix(in oklab,var(--color-black)30%,transparent)}}.bg-black\/50{background-color:#00000080}@supports (color:color-mix(in lab, red, red)){.bg-black\/50{background-color:color-mix(in oklab,var(--color-black)50%,transparent)}}.bg-gray-600{background-color:var(--color-gray-600)}.bg-gray-700{background-color:var(--color-gray-700)}.bg-gray-800\/60{background-color:#1e293999}@supports (color:color-mix(in lab, red, red)){.bg-gray-800\/60{background-color:color-mix(in oklab,var(--color-gray-800)60%,transparent)}}.bg-tech-cyan{background-color:var(--color-tech-cyan)}.bg-tech-cyan\/10{background-color:#58c7bc1a}@supports (color:color-mix(in lab, red, red)){.bg-tech-cyan\/10{background-color:color-mix(in oklab,var(--color-tech-cyan)10%,transparent)}}.bg-tech-dark{background-color:var(--color-tech-dark)}.bg-tech-dark\/60{background-color:#05070a99}@supports (color:color-mix(in lab, red, red)){.bg-tech-dark\/60{background-color:color-mix(in oklab,var(--color-tech-dark)60%,transparent)}}.bg-tech-dark\/95{background-color:#05070af2}@supports (color:color-mix(in lab, red, red)){.bg-tech-dark\/95{background-color:color-mix(in oklab,var(--color-tech-dark)95%,transparent)}}.bg-tech-green{background-color:var(--color-tech-green)}.bg-tech-purple{background-color:var(--color-tech-purple)}.bg-white\/60{background-color:#fff9}@supports (color:color-mix(in lab, red, red)){.bg-white\/60{background-color:color-mix(in oklab,var(--color-white)60%,transparent)}}.bg-gradient-to-br{--tw-gradient-position:to bottom right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-gray-900{--tw-gradient-from:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-cyan{--tw-gradient-from:var(--color-tech-cyan);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-dark{--tw-gradient-from:var(--color-tech-dark);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-green{--tw-gradient-from:var(--color-tech-green);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-purple{--tw-gradient-from:var(--color-tech-purple);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.from-tech-purple\/10{--tw-gradient-from:#d8aa4f1a}@supports (color:color-mix(in lab, red, red)){.from-tech-purple\/10{--tw-gradient-from:color-mix(in oklab,var(--color-tech-purple)10%,transparent)}}.from-tech-purple\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.via-tech-dark{--tw-gradient-via:var(--color-tech-dark);--tw-gradient-via-stops:var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-via)var(--tw-gradient-via-position),var(--tw-gradient-to)var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-via-stops)}.to-gray-900{--tw-gradient-to:var(--color-gray-900);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-cyan{--tw-gradient-to:var(--color-tech-cyan);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-cyan\/10{--tw-gradient-to:#58c7bc1a}@supports (color:color-mix(in lab, red, red)){.to-tech-cyan\/10{--tw-gradient-to:color-mix(in oklab,var(--color-tech-cyan)10%,transparent)}}.to-tech-cyan\/10{--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-dark{--tw-gradient-to:var(--color-tech-dark);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-green{--tw-gradient-to:var(--color-tech-green);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.to-tech-pink{--tw-gradient-to:var(--color-tech-pink);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.object-cover{object-fit:cover}.p-2{padding:calc(var(--spacing)*2)}.p-4{padding:calc(var(--spacing)*4)}.p-6{padding:calc(var(--spacing)*6)}.p-8{padding:calc(var(--spacing)*8)}.px-3{padding-inline:calc(var(--spacing)*3)}.px-4{padding-inline:calc(var(--spacing)*4)}.px-5{padding-inline:calc(var(--spacing)*5)}.px-6{padding-inline:calc(var(--spacing)*6)}.px-7{padding-inline:calc(var(--spacing)*7)}.px-8{padding-inline:calc(var(--spacing)*8)}.py-2{padding-block:calc(var(--spacing)*2)}.py-3{padding-block:calc(var(--spacing)*3)}.py-4{padding-block:calc(var(--spacing)*4)}.py-8{padding-block:calc(var(--spacing)*8)}.py-12{padding-block:calc(var(--spacing)*12)}.py-16{padding-block:calc(var(--spacing)*16)}.py-20{padding-block:calc(var(--spacing)*20)}.py-24{padding-block:calc(var(--spacing)*24)}.pt-8{padding-top:calc(var(--spacing)*8)}.pt-20{padding-top:calc(var(--spacing)*20)}.pt-24{padding-top:calc(var(--spacing)*24)}.pb-16{padding-bottom:calc(var(--spacing)*16)}.pb-24{padding-bottom:calc(var(--spacing)*24)}.text-center{text-align:center}.text-left{text-align:left}.font-display{font-family:var(--font-display)}.font-mono{font-family:var(--font-mono)}.font-tech{font-family:var(--font-tech)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.leading-tight{--tw-leading:var(--leading-tight);line-height:var(--leading-tight)}.font-black{--tw-font-weight:var(--font-weight-black);font-weight:var(--font-weight-black)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-\[0\.14em\]{--tw-tracking:.14em;letter-spacing:.14em}.tracking-\[0\.16em\]{--tw-tracking:.16em;letter-spacing:.16em}.tracking-\[0\.18em\]{--tw-tracking:.18em;letter-spacing:.18em}.break-words{overflow-wrap:break-word}.whitespace-pre-wrap{white-space:pre-wrap}.text-gray-200{color:var(--color-gray-200)}.text-gray-300{color:var(--color-gray-300)}.text-gray-400{color:var(--color-gray-400)}.text-gray-500{color:var(--color-gray-500)}.text-tech-cyan{color:var(--color-tech-cyan)}.text-tech-dark{color:var(--color-tech-dark)}.text-tech-green{color:var(--color-tech-green)}.text-tech-pink{color:var(--color-tech-pink)}.text-tech-purple{color:var(--color-tech-purple)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.underline{text-decoration-line:underline}.opacity-10{opacity:.1}.opacity-20{opacity:.2}.opacity-30{opacity:.3}.opacity-50{opacity:.5}.opacity-70{opacity:.7}.mix-blend-multiply{mix-blend-mode:multiply}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a),0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.shadow-2xl{--tw-shadow:0 25px 50px -12px var(--tw-shadow-color,#00000040);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(1px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-2{--tw-ring-shadow:var(--tw-ring-inset,)0 0 0 calc(2px + var(--tw-ring-offset-width))var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.ring-red-500{--tw-ring-color:var(--color-red-500)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.blur-xl{--tw-blur:blur(var(--blur-xl));filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,)var(--tw-brightness,)var(--tw-contrast,)var(--tw-grayscale,)var(--tw-hue-rotate,)var(--tw-invert,)var(--tw-saturate,)var(--tw-sepia,)var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-200{--tw-duration:.2s;transition-duration:.2s}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}@media (hover:hover){.group-hover\:w-full:is(:where(.group):hover *){width:100%}.group-hover\:translate-y-1:is(:where(.group):hover *){--tw-translate-y:calc(var(--spacing)*1);translate:var(--tw-translate-x)var(--tw-translate-y)}.group-hover\:scale-105:is(:where(.group):hover *),.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:scale-110:hover{--tw-scale-x:110%;--tw-scale-y:110%;--tw-scale-z:110%;scale:var(--tw-scale-x)var(--tw-scale-y)}.hover\:border-tech-cyan:hover{border-color:var(--color-tech-cyan)}.hover\:bg-gray-500:hover{background-color:var(--color-gray-500)}.hover\:bg-gray-600:hover{background-color:var(--color-gray-600)}.hover\:bg-tech-cyan:hover{background-color:var(--color-tech-cyan)}.hover\:bg-tech-cyan\/10:hover{background-color:#58c7bc1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-tech-cyan\/10:hover{background-color:color-mix(in oklab,var(--color-tech-cyan)10%,transparent)}}.hover\:bg-tech-cyan\/20:hover{background-color:#58c7bc33}@supports (color:color-mix(in lab, red, red)){.hover\:bg-tech-cyan\/20:hover{background-color:color-mix(in oklab,var(--color-tech-cyan)20%,transparent)}}.hover\:bg-tech-green\/20:hover{background-color:#9bd66b33}@supports (color:color-mix(in lab, red, red)){.hover\:bg-tech-green\/20:hover{background-color:color-mix(in oklab,var(--color-tech-green)20%,transparent)}}.hover\:bg-tech-purple\/20:hover{background-color:#d8aa4f33}@supports (color:color-mix(in lab, red, red)){.hover\:bg-tech-purple\/20:hover{background-color:color-mix(in oklab,var(--color-tech-purple)20%,transparent)}}.hover\:bg-white\/10:hover{background-color:#ffffff1a}@supports (color:color-mix(in lab, red, red)){.hover\:bg-white\/10:hover{background-color:color-mix(in oklab,var(--color-white)10%,transparent)}}.hover\:from-tech-cyan:hover{--tw-gradient-from:var(--color-tech-cyan);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hover\:from-tech-pink:hover{--tw-gradient-from:var(--color-tech-pink);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hover\:to-tech-green:hover{--tw-gradient-to:var(--color-tech-green);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hover\:to-tech-purple:hover{--tw-gradient-to:var(--color-tech-purple);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position),var(--tw-gradient-from)var(--tw-gradient-from-position),var(--tw-gradient-to)var(--tw-gradient-to-position))}.hover\:text-tech-cyan:hover{color:var(--color-tech-cyan)}.hover\:text-tech-green:hover{color:var(--color-tech-green)}.hover\:text-tech-purple:hover{color:var(--color-tech-purple)}.hover\:text-white:hover{color:var(--color-white)}}@media (min-width:40rem){.sm\:mb-6{margin-bottom:calc(var(--spacing)*6)}.sm\:mb-8{margin-bottom:calc(var(--spacing)*8)}.sm\:mb-12{margin-bottom:calc(var(--spacing)*12)}.sm\:min-h-screen{min-height:100vh}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}:where(.sm\:space-x-4>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*4)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-x-reverse)))}.sm\:p-6{padding:calc(var(--spacing)*6)}.sm\:px-4{padding-inline:calc(var(--spacing)*4)}.sm\:px-6{padding-inline:calc(var(--spacing)*6)}.sm\:px-8{padding-inline:calc(var(--spacing)*8)}.sm\:py-4{padding-block:calc(var(--spacing)*4)}.sm\:py-16{padding-block:calc(var(--spacing)*16)}.sm\:pt-20{padding-top:calc(var(--spacing)*20)}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:48rem){.md\:col-span-2{grid-column:span 2/span 2}.md\:flex{display:flex}.md\:hidden{display:none}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.md\:grid-cols-5{grid-template-columns:repeat(5,minmax(0,1fr))}.md\:py-24{padding-block:calc(var(--spacing)*24)}.md\:pt-28{padding-top:calc(var(--spacing)*28)}.md\:text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.md\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.md\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.md\:text-6xl{font-size:var(--text-6xl);line-height:var(--tw-leading,var(--text-6xl--line-height))}.md\:text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.md\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.md\:text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}}@media (min-width:64rem){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[0\.9fr_1\.1fr\]{grid-template-columns:.9fr 1.1fr}.lg\:grid-cols-\[1fr_auto\]{grid-template-columns:1fr auto}.lg\:gap-8{gap:calc(var(--spacing)*8)}.lg\:px-8{padding-inline:calc(var(--spacing)*8)}.lg\:text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.lg\:text-7xl{font-size:var(--text-7xl);line-height:var(--tw-leading,var(--text-7xl--line-height))}.lg\:text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}}}.gradient-text{color:#d8aa4f;-webkit-text-fill-color:currentColor}.glass-effect{background:linear-gradient(#f6f1e811,#f6f1e809),#0b0f12c7;border:1px solid #f6f1e81f;box-shadow:0 18px 48px #02050847}.glass-effect.rounded-2xl,.project-card.rounded-2xl,.cv-gate.rounded-2xl,.glass-effect.rounded-xl{border-radius:.5rem}.skip-link{z-index:60;color:#fff;background:#05070af2;border:1px solid #38d8c873;border-radius:.75rem;padding:.75rem 1rem;font-weight:600;transition:transform .2s;position:absolute;top:1rem;left:1rem;transform:translateY(-200%)}.skip-link:focus{transform:translateY(0)}h1,h2,h3{letter-spacing:0;text-wrap:balance;font-family:Bahnschrift,Aptos Display,Segoe UI Variable,system-ui,sans-serif}.btn{color:#fff;background-color:#1a202c;border-radius:5px;padding:10px 20px;text-decoration:none;transition:background-color .3s,transform .3s}.btn:hover{background-color:#4a5568;transform:scale(1.05)}.gradient-bg{color:#fff;background:linear-gradient(135deg,#1a202c,#366ecd)}social-links a{color:#fff;margin:0 10px;transition:color .3s}social-links a:hover{color:gold}social-links{z-index:2000;position:relative}#goose-animation{z-index:1000}h1{color:#f6f1e8}body{color:#f6f1e8;background:linear-gradient(90deg,#f6f1e806 1px,#0000 1px) 0 0/40px 40px,linear-gradient(#f6f1e805 1px,#0000 1px) 0 0/40px 40px,linear-gradient(#05070a 0%,#0c1114 44%,#070908 100%)}html{color-scheme:dark;scroll-behavior:smooth}.touch-target{touch-action:manipulation;min-width:46px;min-height:46px}.anchor-section{scroll-margin-top:5.5rem}.mobile-menu-button-open{z-index:70;position:fixed;top:1rem;right:calc(min(24rem,100vw - 4rem) + .5rem);translate:none}#mobile-menu{z-index:60}.project-action-link{border-radius:.75rem;align-items:center;gap:.35rem;min-height:44px;padding:.5rem .75rem;display:inline-flex}.project-icon-link,.footer-social-link{touch-action:manipulation;border-radius:9999px;justify-content:center;align-items:center;min-width:44px;min-height:44px;display:inline-flex}.footer-link-list a{touch-action:manipulation;align-items:center;min-width:44px;min-height:44px;display:inline-flex}.drawer-scroll-region{overscroll-behavior:contain}.hero-photo-bg{background-position:50%;background-repeat:no-repeat;background-size:cover}.hero-signal-grid{opacity:.42;background-color:#0000;background-image:linear-gradient(#38d8c817 1px,#0000 1px),linear-gradient(90deg,#f2b84b12 1px,#0000 1px);background-position:0 0,0 0;background-repeat:repeat,repeat;background-size:72px 72px;background-attachment:scroll,scroll;background-origin:padding-box,padding-box;background-clip:border-box,border-box;-webkit-mask-image:linear-gradient(90deg,#000 0% 56%,#0000 100%);mask-image:linear-gradient(90deg,#000 0% 56%,#0000 100%)}.hire-command-center{background:linear-gradient(135deg,#05070af7,#0e1214ed)}.hire-grid-plane{opacity:.34;background-color:#0000;background-image:linear-gradient(#9bd66b14 1px,#0000 1px),linear-gradient(90deg,#38d8c814 1px,#0000 1px);background-position:0 0,0 0;background-repeat:repeat,repeat;background-size:44px 44px;background-attachment:scroll,scroll;background-origin:padding-box,padding-box;background-clip:border-box,border-box;position:absolute;inset:0;-webkit-mask-image:linear-gradient(#0000,#000 12% 86%,#0000);mask-image:linear-gradient(#0000,#000 12% 86%,#0000)}.hire-role-tabs{grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem;display:grid}.hire-role-tab,.hire-command-button,.hire-contact-link,.roast-button,.retro-link-button,.retro-game-action,.contact-primary-button,.contact-email-strip button,.cv-unlock-button,.cv-public-link,.cv-secondary-link{touch-action:manipulation;border-radius:9999px;justify-content:center;align-items:center;font-weight:900;display:inline-flex}.hire-role-tab{color:#d1d5db;background:#ffffff0e;border:1px solid #ffffff21;min-height:48px;transition:transform .18s,border-color .18s,background-color .18s,color .18s}.hire-role-tab:hover,.hire-role-tab:focus-visible,.hire-role-tab.is-active{color:#f6f1e8;background:#2dd4bf29;border-color:#2dd4bf94;transform:translateY(-1px)}.hire-console{background:#080b0deb;border:1px solid #58c7bc42;border-radius:.5rem;overflow:hidden;box-shadow:0 28px 80px #02050861}.hire-console-header,.hire-console-actions{border-bottom:1px solid #ffffff1a;justify-content:space-between;align-items:center;gap:1rem;padding:1rem 1.25rem;display:flex}.hire-console-actions{border-top:1px solid #ffffff1a;border-bottom:0;flex-wrap:wrap}.hire-status-dot{background:#84cc16;border-radius:9999px;width:.7rem;height:.7rem;box-shadow:0 0 20px #84cc16db}.hire-console-body{padding:clamp(1.25rem,3vw,2rem)}.hire-metric{background:#ffffff0e;border:1px solid #ffffff1a;border-radius:.5rem;min-height:6.5rem;padding:1rem}.hire-metric-label{color:#94a3b8;letter-spacing:.12em;text-transform:uppercase;margin-bottom:.45rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;display:block}.hire-metric strong{color:#f6f1e8;line-height:1.25}.hire-proof-list{color:#d1d5db;gap:.8rem;display:grid}.hire-proof-list li{padding-left:1.5rem;position:relative}.hire-proof-list li:before{content:">";color:#84cc16;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-weight:900;position:absolute;left:0}.hire-command-button{color:#2dd4bf;background:#2dd4bf29;border:1px solid #2dd4bf61;min-height:46px;padding:.75rem 1rem}.hire-contact-link{color:#05070a;background:#f2b84b;min-height:46px;padding:.75rem 1rem}.retro-archive-section{background:linear-gradient(90deg,#d8aa4f0e 1px,#0000 1px) 0 0/54px 54px,linear-gradient(#58c7bc0b 1px,#0000 1px) 0 0/54px 54px,linear-gradient(135deg,#05070afa,#0d1117f0)}.retro-archive-header{grid-template-columns:minmax(0,1fr) minmax(18rem,.42fr);align-items:end;gap:2rem;margin-bottom:2.5rem;display:grid}.retro-archive-note,.retro-request-card,.retro-policy-card,.retro-game-card{background:#f6f1e80d;border:1px solid #ffffff1c;border-radius:.5rem}.retro-archive-note{color:#d1d5db;padding:1.25rem;line-height:1.55}.retro-policy-grid{grid-template-columns:repeat(4,minmax(0,1fr));gap:.8rem;margin-bottom:3rem;display:grid}.retro-policy-card{min-height:5.5rem;padding:1rem}.retro-policy-value{color:#f2b84b;font-size:1.75rem;font-weight:900;line-height:1;display:block}.retro-policy-label{color:#9ca3af;letter-spacing:.08em;text-transform:uppercase;margin-top:.45rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;display:block}.retro-section-title{justify-content:space-between;align-items:end;gap:2rem;margin-bottom:1.25rem;display:flex}.retro-section-title h3{color:#f6f1e8;margin-top:.35rem;font-size:clamp(1.55rem,2.4vw,2.35rem);font-weight:900}.retro-section-title>p{color:#9ca3af;max-width:30rem}.retro-catalog-grid{grid-template-columns:repeat(3,minmax(0,1fr));gap:1rem;display:grid}.retro-game-card{grid-template-rows:auto auto auto 1fr auto;gap:1rem;min-height:23rem;padding:1.25rem;transition:transform .18s,border-color .18s,background-color .18s;display:grid}.retro-game-card:hover,.retro-game-card:focus-within{background:#f6f1e812;border-color:#38d8c86b;transform:translateY(-3px)}.retro-card-topline{justify-content:space-between;align-items:center;gap:.75rem;display:flex}.retro-card-icon{background:#05070a75;border:1px solid #ffffff1f;border-radius:.5rem;place-items:center;width:3rem;height:3rem;font-size:1.65rem;display:inline-grid}.retro-source-pill{color:#38d8c8;letter-spacing:.06em;text-transform:uppercase;white-space:nowrap;background:#38d8c81a;border:1px solid #38d8c852;border-radius:9999px;align-items:center;min-height:1.75rem;padding:.28rem .62rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.68rem;font-weight:800;display:inline-flex}.retro-game-card h3{color:#f6f1e8;font-size:1.35rem;font-weight:900}.retro-game-card p{color:#d1d5db;font-size:.94rem;line-height:1.62}.retro-card-meta{gap:.55rem;display:grid}.retro-card-meta div{grid-template-columns:4.3rem minmax(0,1fr);align-items:start;gap:.75rem;display:grid}.retro-card-meta dt{color:#9ca3af;letter-spacing:.08em;text-transform:uppercase;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.68rem}.retro-card-meta dd{color:#f6f1e8;font-size:.84rem}.retro-link-button,.retro-game-action{color:#38d8c8;background:#38d8c829;border:1px solid #38d8c861;gap:.45rem;min-height:46px;padding:.75rem 1rem}.retro-link-button:hover,.retro-link-button:focus-visible,.retro-game-action:hover,.retro-game-action:focus-visible{color:#f6f1e8;background:#38d8c83d}.retro-game-action{width:100%}.retro-request-card{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:1.5rem;padding:1.25rem;display:grid}.retro-request-card h3{color:#f6f1e8;margin-bottom:.35rem;font-size:1.65rem;font-weight:900}.retro-request-card p{color:#d1d5db}.contact-command-section{background:linear-gradient(90deg,#f2b84b0d 1px,#0000 1px) 0 0/64px 64px,linear-gradient(#38d8c80f 1px,#0000 1px) 0 0/64px 64px,#05070a}.contact-hero-email{background:#05070ab8;border:1px solid #f2b84b61;border-radius:.5rem;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:1rem;max-width:58rem;margin:-1.25rem auto 2rem;padding:.9rem 1rem;display:grid}.contact-hero-email span,.contact-hero-email small{color:#9ca3af;letter-spacing:.1em;text-transform:uppercase;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;font-weight:900}.contact-hero-email a{color:#f6f1e8;overflow-wrap:anywhere;font-weight:900}.contact-hero-email a:hover,.contact-hero-email a:focus-visible{color:#f2b84b}.contact-command-grid{grid-template-columns:minmax(0,.95fr) minmax(0,1.05fr);align-items:start;gap:1rem;display:grid}.contact-direct-panel,.contact-draft-card,.contact-support-panel,.cv-gate{border-radius:.5rem;padding:clamp(1.25rem,2.6vw,2rem)}.contact-direct-panel{min-height:100%}.contact-direct-panel h3,.contact-draft-card h3,.contact-support-panel h3,.cv-gate h3{color:#f6f1e8;margin-bottom:.75rem;font-size:clamp(1.45rem,2vw,2rem);font-weight:900}.contact-direct-copy,.contact-card-header p,.contact-support-panel p,.cv-gate p{color:#d1d5db;line-height:1.65}.contact-email-strip{background:#f2b84b1a;border:1px solid #f2b84b6b;border-radius:.5rem;grid-template-columns:auto minmax(0,1fr) auto;align-items:center;gap:.85rem;margin:1.5rem 0 .4rem;padding:.85rem;display:grid}.contact-email-strip span:first-child{color:#f2b84b;letter-spacing:.1em;text-transform:uppercase;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;font-weight:900}.contact-email-strip strong{color:#f6f1e8;overflow-wrap:anywhere;min-width:0}.contact-email-strip button,.contact-primary-button{min-height:46px;padding:.72rem 1rem}.contact-email-strip button{color:#f6f1e8;background:#05070a94;border:1px solid #f6f1e82e;gap:.4rem}.contact-copy-status{color:#9bd66b;min-height:1.35rem;font-size:.9rem}.contact-action-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:.75rem;margin-top:1.2rem;display:grid}.contact-action-card{color:#f6f1e8;background:#ffffff0e;border:1px solid #ffffff24;border-radius:.5rem;gap:.25rem;min-height:7.4rem;padding:1rem;transition:transform .18s,border-color .18s,background-color .18s;display:grid}.contact-action-card:hover,.contact-action-card:focus-visible{background:#38d8c81a;border-color:#38d8c87a;transform:translateY(-2px)}.contact-action-card span{color:#38d8c8;font-weight:900}.contact-action-card small{color:#9ca3af}.contact-draft-card{gap:1rem;display:grid}.contact-card-header{margin-bottom:.25rem}.contact-field-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;display:grid}.contact-draft-card label,.cv-gate label{color:#d1d5db;margin-bottom:.45rem;font-size:.9rem;font-weight:800;display:block}.contact-draft-card input,.contact-draft-card select,.contact-draft-card textarea{color:#f6f1e8;background:#05070aad;border:1px solid #ffffff29;border-radius:.5rem;outline:0;width:100%;padding:.85rem .95rem}.contact-draft-card textarea{resize:vertical}.contact-draft-card input:focus,.contact-draft-card select:focus,.contact-draft-card textarea:focus{border-color:#38d8c89e;box-shadow:0 0 0 3px #38d8c821}.contact-primary-button{color:#05070a;background:#f2b84b;gap:.65rem}.contact-zero-backend-note{color:#9ca3af;font-size:.9rem}.contact-support-panel{border-color:#9bd66b3d}.contact-availability{color:#9bd66b;align-items:center;gap:.6rem;margin-bottom:1rem;display:flex}.contact-availability span{background:#9bd66b;border-radius:9999px;width:.8rem;height:.8rem;box-shadow:0 0 20px #9bd66bb8}.contact-support-panel ul{color:#d1d5db;gap:.45rem;margin-top:1rem;display:grid}.contact-support-panel li{padding-left:1.25rem;position:relative}.contact-support-panel li:before{content:">";color:#9bd66b;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-weight:900;position:absolute;left:0}.cv-gate{border-color:#f2b84b3d}.cv-gate-row{grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.75rem;display:grid}.cv-gate-input{color:#f6f1e8;background:#05070a9e;border:1px solid #ffffff29;border-radius:.85rem;outline:0;width:100%;min-height:46px;padding:.75rem .95rem}.cv-gate-input:focus{border-color:#38d8c89e;box-shadow:0 0 0 3px #38d8c821}.cv-unlock-button,.cv-public-link{color:#05070a;background:#f2b84b;min-height:46px;padding:.75rem 1rem}.cv-secondary-link{color:#38d8c8;background:#38d8c817;border:1px solid #38d8c852;min-height:46px;padding:.75rem 1rem}.cv-unlocked-panel{flex-wrap:wrap;gap:.75rem;display:flex}.cv-unlocked-panel[hidden]{display:none}.portfolio-fab-stack{z-index:55;flex-direction:column;align-items:flex-end;gap:.5rem;display:flex;position:fixed;bottom:1rem;right:1rem}.portfolio-fab{color:#fff;touch-action:manipulation;border:1px solid #ffffff29;border-radius:9999px;justify-content:center;align-items:center;gap:.5rem;min-height:44px;padding:.65rem .9rem;font-weight:800;transition:transform .2s,background-color .2s,border-color .2s;display:inline-flex;box-shadow:0 14px 36px #02050857}.portfolio-fab:hover,.portfolio-fab:focus-visible{transform:translateY(-2px)}.portfolio-fab-primary{color:#05070a;background:#f2b84beb}.portfolio-fab-secondary{background:#080b11c7}.clippy,.clippy-balloon{pointer-events:none}.command-palette{z-index:1100;place-items:end center;padding:1rem;display:grid;position:fixed;inset:0}.command-palette.hidden{display:none}.command-palette-backdrop{background:#000000a3;position:absolute;inset:0}.command-shell{z-index:1;background:#080b11f5;border:1px solid #2dd4bf47;border-radius:.5rem;grid-template-rows:auto minmax(220px,1fr) auto auto;width:min(900px,100%);max-height:min(760px,100svh - 2rem);display:grid;position:relative;overflow:hidden;box-shadow:0 24px 80px #0000007a}.command-shell-header{border-bottom:1px solid #ffffff1a;justify-content:space-between;align-items:center;gap:1rem;padding:1rem 1.1rem;display:flex}.command-kicker{color:#2dd4bf;letter-spacing:.16em;text-transform:uppercase;margin:0 0 .25rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.74rem}.command-shell h2{color:#fff;margin:0;font-size:clamp(1.2rem,2vw,1.75rem)}.command-close{color:#fff;background:#ffffff14;border-radius:9999px;justify-content:center;align-items:center;font-size:1.7rem;line-height:1;display:inline-flex}.command-output{color:#d1d5db;padding:1rem 1.1rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.9rem;line-height:1.55;overflow-y:auto}.command-line{margin-bottom:1rem}.command-input-line{color:#f59e0b}.command-response{white-space:pre-wrap}.command-response a{color:#2dd4bf;text-underline-offset:3px;text-decoration:underline}.command-response strong{color:#fff}.command-form{background:#ffffff09;border-top:1px solid #ffffff1a;grid-template-columns:auto 1fr auto;align-items:center;gap:.7rem;padding:.85rem 1.1rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;display:grid}.command-form span{color:#84cc16;font-weight:800}.command-form input{color:#fff;background:0 0;border:0;outline:0;min-width:0}.command-form button,.command-suggestions button{color:#2dd4bf;background:#2dd4bf24;border-radius:9999px;padding:.55rem .85rem;font-weight:800}.command-suggestions{flex-wrap:wrap;gap:.5rem;padding:0 1.1rem 1rem;display:flex}.project-visual{isolation:isolate;place-items:center;min-height:12rem;display:grid;position:relative;overflow:hidden}.project-visual:before{content:"";opacity:.35;background-color:#0000;background-image:linear-gradient(#ffffff1f 1px,#0000 1px),linear-gradient(90deg,#ffffff1a 1px,#0000 1px);background-position:0 0,0 0;background-repeat:repeat,repeat;background-size:36px 36px;background-attachment:scroll,scroll;background-origin:padding-box,padding-box;background-clip:border-box,border-box;position:absolute;inset:0}.project-visual-road{background:linear-gradient(135deg,#f59e0bc7,#111827f2)}.project-visual-orbit{background:linear-gradient(135deg,#2dd4bfad,#2563ebb8)}.project-visual-tool{background:linear-gradient(135deg,#84cc16a3,#111827f0)}.project-visual-agent{background:linear-gradient(135deg,#f43f5ea8,#2dd4bf6b)}.project-visual-icon{z-index:1;font-size:4.5rem;transition:transform .3s;position:relative}.project-card:hover .project-visual-icon{transform:scale(1.08)rotate(-2deg)}.project-visual-label{z-index:1;color:#fff;letter-spacing:.12em;text-transform:uppercase;background:#080b11b8;border-radius:9999px;padding:.35rem .7rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;position:absolute;top:1rem;left:1rem}.repo-badge,.repo-chip{white-space:nowrap;border-radius:9999px;align-items:center;display:inline-flex}.repo-badge{color:#f59e0b;background:#f59e0b29;padding:.35rem .65rem;font-size:.72rem;font-weight:900}.repo-chip{color:#d1d5db;background:#ffffff12;padding:.28rem .6rem;font-size:.76rem}.reviews-section{border-block:1px solid #ffffff0f}.review-grid{grid-template-columns:repeat(6,minmax(0,1fr));gap:2rem;display:grid}.review-card{text-align:center;background:#ffffff0e;border:1px solid #ffffff1c;border-radius:.5rem;flex-direction:column;grid-column:span 2;justify-content:center;min-height:21.35rem;padding:2rem;transition:transform .18s,border-color .18s,background-color .18s;display:flex}.review-card:hover{background:#ffffff13;border-color:#f59e0b6b;transform:translateY(-4px)}.review-card-accent{background:linear-gradient(#f59e0b29,#ffffff0e)}.review-card-wide{grid-column:span 3;min-height:18.35rem}.review-mark{margin-bottom:1rem;font-size:2.2rem}.review-card h3{color:#f6f1e8;margin-bottom:.85rem;font-size:1.25rem}.review-card p{color:#d1d5db;line-height:1.65}.review-author{color:#9ca3af;margin-top:1rem;font-size:.9rem}.review-note{color:#6b7280;margin-top:.25rem;font-size:.78rem;font-style:italic}.review-roast-teaser{background:#080b119e;border:1px solid #2dd4bf38;border-radius:.5rem;grid-template-columns:1fr auto;align-items:center;gap:1.5rem;padding:1.5rem;display:grid}.roast-button{color:#fb7185;background:#f43f5e29;border:1px solid #f43f5e70;min-height:46px;padding:.75rem 1rem}.goose-desk-scene{isolation:isolate;width:148px;height:148px}.goose-frame-fallback,.goose-desk-goose{object-fit:contain;filter:drop-shadow(0 18px 28px #00000057);width:100%;height:100%}.goose-desk-goose{z-index:2;transform-origin:50% 88%;animation:3.8s ease-in-out infinite goose-waddle;position:relative}.goose-desk-bubble{z-index:3;color:#f2b84b;background:#05070ac7;border:1px solid #f6f1e838;border-radius:9999px;padding:.25rem .55rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;font-weight:900;position:absolute;top:.2rem;right:-.45rem;transform:rotate(7deg)}.goose-desk-shadow{z-index:1;filter:blur(4px);background:#00000057;border-radius:9999px;height:.72rem;position:absolute;bottom:.18rem;left:1.1rem;right:1.1rem}.goose-terror-trigger{color:#f2b84b;background:#f2b84b29;border:1px solid #f2b84b7a;border-radius:9999px;justify-content:center;align-items:center;min-height:46px;padding:.65rem 1rem;font-weight:900;display:inline-flex}.goose-terror-trigger:hover,.goose-terror-trigger:focus-visible,.goose-terror-trigger[aria-pressed=true]{color:#f6f1e8;background:#f2b84b47}.goose-terror-layer{z-index:1200;pointer-events:none;position:fixed;inset:0;overflow:hidden}.goose-terror-toolbar{top:calc(4.8rem + env(safe-area-inset-top));color:#f6f1e8;pointer-events:auto;background:#05070adb;border:1px solid #f2b84b85;border-radius:9999px;align-items:center;gap:.75rem;padding:.45rem .55rem .45rem .9rem;display:inline-flex;position:fixed;right:1rem;box-shadow:0 16px 46px #0000005c}.goose-terror-toolbar span{color:#f2b84b;letter-spacing:.1em;text-transform:uppercase;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.72rem;font-weight:900}.goose-terror-toolbar button{color:#05070a;background:#f2b84b;border-radius:9999px;min-height:40px;padding:.55rem .8rem;font-weight:900}.goose-terror-goose{filter:drop-shadow(0 20px 26px #0000006b);pointer-events:none;width:clamp(82px,9vw,132px);height:auto;transition:left .85s ease-in-out,top .85s ease-in-out,transform .2s;animation:.72s steps(2,end) infinite goose-terror-bob;position:fixed}.goose-terror-goose.goose-terror-flipped{transform:scaleX(-1)}.goose-terror-note{color:#10151a;width:min(210px,100vw - 2rem);min-height:7rem;transform:rotate(var(--note-rotate,-2deg));pointer-events:none;background:#f2e7a8;border:1px solid #10151a2e;border-radius:.35rem;padding:1.05rem .85rem .85rem;position:fixed;box-shadow:0 18px 42px #00000057}.goose-terror-note:nth-of-type(2n){--note-rotate:2deg}.goose-terror-note strong{letter-spacing:.08em;text-transform:uppercase;margin-bottom:.45rem;font-family:JetBrains Mono,Monaco,Consolas,monospace;font-size:.68rem;display:block}.goose-terror-note span{font-weight:900;line-height:1.35;display:block}.goose-terror-note button{color:#10151a;pointer-events:auto;font-size:1.25rem;font-weight:900;position:absolute;top:.25rem;right:.35rem}.goose-terror-footprint{background:#f2b84b75;border-radius:9999px;width:.72rem;height:.42rem;animation:5.2s forwards goose-footprint-fade;position:fixed;transform:rotate(-18deg)}@keyframes goose-terror-bob{0%,to{translate:0}50%{translate:0 -6px}}@keyframes goose-footprint-fade{0%{opacity:.75}to{opacity:0;transform:translateY(10px)rotate(-18deg)}}@keyframes goose-waddle{0%,to{transform:translate(0)translateY(0)rotate(-2deg)}25%{transform:translate(-3px)translateY(-2px)rotate(2deg)}50%{transform:translate(2px)translateY(0)rotate(-1deg)}75%{transform:translate(4px)translateY(-3px)rotate(2deg)}}@media (prefers-reduced-motion:reduce){.goose-desk-goose,.goose-terror-goose,.goose-terror-footprint{animation:none}.goose-terror-goose{transition:none}}@media (max-width:640px){.anchor-section{padding-bottom:8rem}.contact-command-section.anchor-section{padding-top:3rem}.contact-command-hero{margin-bottom:2rem}.contact-hero-email{text-align:center;grid-template-columns:1fr;gap:.35rem;margin-top:-.75rem}.contact-direct-panel,.contact-draft-card,.contact-support-panel,.cv-gate{padding:1.25rem}.portfolio-fab-stack{left:.75rem;right:.75rem;bottom:calc(.75rem + env(safe-area-inset-bottom));grid-template-columns:1fr 1fr 1.2fr;align-items:stretch;display:grid}#hero [data-command-open],.hero-scroll-cue{display:none}.portfolio-fab{width:100%;padding-inline:.55rem;font-size:.82rem}.command-palette{place-items:end center;padding:0}.command-shell{border-radius:18px 18px 0 0;max-height:92svh}.command-output{font-size:.82rem}.goose-terror-toolbar{top:calc(4.5rem + env(safe-area-inset-top));justify-content:space-between;left:.75rem;right:.75rem}.goose-terror-toolbar span{font-size:.64rem}.goose-terror-note{width:min(190px,100vw - 2rem)}}@media (max-width:900px){.retro-archive-header,.retro-request-card{grid-template-columns:1fr}.retro-policy-grid,.retro-catalog-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.contact-command-grid{grid-template-columns:1fr}.retro-section-title{flex-direction:column;align-items:start;gap:.65rem}.review-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.review-card,.review-card-wide{grid-column:span 1}.review-roast-teaser{grid-template-columns:1fr}}@media (max-width:640px){.hire-role-tabs,.retro-policy-grid,.retro-catalog-grid,.review-grid,.contact-action-grid,.contact-field-grid{grid-template-columns:1fr}.retro-game-card{min-height:auto}.retro-card-meta div{grid-template-columns:1fr;gap:.2rem}.retro-link-button,.retro-game-action{width:100%}.hire-console-header,.hire-console-actions{flex-direction:column;align-items:stretch}.cv-gate-row{grid-template-columns:1fr}.cv-unlocked-panel{flex-direction:column}.cv-unlock-button,.cv-public-link,.cv-secondary-link{width:100%}}.scroll-animation{opacity:0;will-change:transform,opacity;transition:none;transform:translateY(20px)scale(.95)}.scroll-animation.visible{opacity:1;transition:transform .6s ease-out,opacity .6s ease-out;transform:translateY(0)scale(1)}@keyframes zoomInOut{0%{opacity:0;transform:scale(.8)}50%{opacity:1;transform:scale(1.1)}to{opacity:1;transform:scale(1)}}#navbar.glass-effect{-webkit-backdrop-filter:blur(14px)saturate(1.15);backdrop-filter:blur(14px)saturate(1.15);border-width:0 0 1px;box-shadow:0 10px 30px #02050859}[tabindex="-1"]:focus{outline:none}@media (min-width:641px){.portfolio-fab{gap:0;min-width:46px;height:46px;padding:0 .85rem}.portfolio-fab span:last-child{white-space:nowrap;opacity:0;max-width:0;transition:opacity .18s;overflow:hidden}.portfolio-fab:hover span:last-child,.portfolio-fab:focus-visible span:last-child{opacity:1;max-width:8rem;margin-left:.5rem}}@media (prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important;animation-iteration-count:1!important}}.skill-spec-grid{grid-template-columns:repeat(auto-fit,minmax(15rem,1fr));gap:1rem;display:grid}.skill-spec{--skill-accent:#58c7bc;border:1px solid #f6f1e81f;border-top:2px solid var(--skill-accent);background:linear-gradient(#f6f1e80d,#f6f1e804),#0b1014;border-radius:.5rem;flex-direction:column;padding:1.25rem 1.25rem 1rem;display:flex}.skill-spec-cyan{--skill-accent:#58c7bc}.skill-spec-brass{--skill-accent:#d8aa4f}.skill-spec-green{--skill-accent:#9bd66b}.skill-spec-coral{--skill-accent:#ff766d}.skill-spec header{justify-content:space-between;align-items:center;margin-bottom:.9rem;display:flex}.skill-spec-icon{width:2.5rem;height:2.5rem;color:var(--skill-accent);border:1px solid #f6f1e824;border-radius:.4rem;place-items:center;display:inline-grid}.skill-spec-icon svg{width:1.6rem;height:1.6rem}.skill-spec-index{letter-spacing:.16em;color:#8a9894;font-family:Cascadia Mono,JetBrains Mono,Consolas,monospace;font-size:.75rem}.skill-spec h3{color:#f6f1e8;margin-bottom:.6rem;font-size:1.2rem;font-weight:800}.skill-spec ul{flex:1;margin-bottom:.9rem}.skill-spec li{color:#d4dbd6;border-bottom:1px dashed #f6f1e817;padding:.4rem 0;font-size:.95rem}.skill-spec li:before{content:"▸ ";color:var(--skill-accent)}.skill-spec-proof{color:#8a9894;font-size:.82rem}.skill-spec-proof a{color:var(--skill-accent);text-underline-offset:3px;-webkit-text-decoration:underline #f6f1e840;text-decoration:underline #f6f1e840}.skill-spec-proof a:hover{text-decoration-color:currentColor}@media (max-width:640px){.skill-spec ul{flex-wrap:wrap;gap:.4rem;display:flex}.skill-spec li{border:1px solid #f6f1e81f;border-radius:9999px;padding:.25rem .65rem;font-size:.82rem}.skill-spec li:before{content:none}}.retro-archive-note p+p{margin-top:.6rem}.retro-ledger{color:#c7c9c6;margin-top:.8rem;font-family:Cascadia Mono,JetBrains Mono,Consolas,monospace;font-size:.82rem;line-height:1.7}.retro-ledger strong{color:#f2b84b}.retro-ledger span{white-space:nowrap}.retro-swipe-hint{display:none}@media (max-width:640px){.retro-swipe-hint{color:#8a9894;text-align:right;margin:-1rem 0 .75rem;font-family:Cascadia Mono,JetBrains Mono,Consolas,monospace;font-size:.78rem;display:block}.retro-game-grid.retro-catalog-grid{overscroll-behavior-x:contain;scroll-snap-type:x mandatory;scrollbar-width:thin;gap:.75rem;margin-inline:-1rem;padding:.25rem 1rem 1rem;display:flex;overflow-x:auto}.retro-catalog-grid .retro-game-card{scroll-snap-align:center;flex:0 0 84%}.retro-catalog-grid .retro-game-card:hover,.retro-catalog-grid .retro-game-card:focus-within{transform:none}}.secret-key-map{text-align:left;gap:.55rem;font-size:.875rem;display:grid}.secret-key-map div{border-bottom:1px dashed #f6f1e817;grid-template-columns:minmax(0,auto) 1fr;align-items:baseline;gap:.75rem;padding-bottom:.55rem;display:grid}.secret-key-map dd{color:#b8c0bc}.secret-key-map kbd{color:#f6f1e8;text-align:center;white-space:nowrap;background:#05070a99;border:1px solid #f6f1e833;border-bottom-width:2px;border-radius:.3rem;min-width:1.6rem;padding:.1rem .45rem;font-family:Cascadia Mono,JetBrains Mono,Consolas,monospace;font-size:.75rem;display:inline-block}.project-visual-schematic{--tone:88,199,188;background:radial-gradient(120% 140% at 50% 0%,rgba(var(--tone),.12),transparent 62%),#0b1014}.project-visual-schematic[data-tone=brass]{--tone:216,170,79}.project-visual-schematic[data-tone=green]{--tone:155,214,107}.project-visual-schematic:before{opacity:.16}.project-visual-art{z-index:1;object-fit:cover;width:100%;height:100%;transition:transform .3s;position:absolute;inset:0}.project-card:hover .project-visual-art{transform:scale(1.03)}.project-visual-schematic .project-visual-label{z-index:2}@media (min-width:1024px){.project-card-wide{grid-column:1/-1;grid-template-columns:1fr 1fr;align-items:stretch;display:grid}}.project-visual-photo{background:#05070a}.brain-lab-stage{background:#05070a;min-height:34rem;position:relative;overflow:hidden}.brain-lab-stage img,.brain-lab-stage canvas{width:100%;height:100%;position:absolute;inset:0}.brain-lab-stage img{object-fit:contain;transition:opacity .4s}.brain-lab-stage.is-live img{opacity:0}.brain-lab-stage canvas{cursor:grab;touch-action:pan-y}.brain-lab-stage canvas:active{cursor:grabbing}.brain-lab-note{color:#9eafab;font-size:.82rem;line-height:1.5}.path-related-link{color:#d8aa4f;text-underline-offset:3px;font-size:.9rem;text-decoration:underline}@media (max-width:640px){.brain-lab-stage{min-height:26rem}}.doc-page{background:#05070a}.doc-nav{z-index:3;justify-content:space-between;align-items:center;padding:.75rem clamp(1rem,4vw,3rem);font-size:.875rem;display:flex;position:absolute;inset:0 0 auto}.doc-nav div{gap:1.25rem;display:flex}.doc-nav a{color:#c7cfcb;align-items:center;min-height:44px;display:inline-flex}.doc-nav a:hover{color:#f6f1e8}.doc-nav .doc-nav-home{color:#d8aa4f;font-size:1.4rem;font-weight:800}.doc-hero{background:#05070a;align-items:flex-end;min-height:min(100svh,56rem);display:flex;position:relative;overflow:hidden}.doc-hero-poster,#brain-canvas{width:100%;height:100%;position:absolute;inset:0}.doc-hero-poster{object-fit:contain;transition:opacity .6s}#brain-canvas{opacity:0;cursor:grab;touch-action:pan-y;transition:opacity .6s}#brain-canvas:active{cursor:grabbing}.doc-hero.is-live #brain-canvas{opacity:1}.doc-hero.is-live .doc-hero-poster{opacity:0}.doc-hero:after{content:"";z-index:1;pointer-events:none;background:linear-gradient(#0000,#05070af2 70%);position:absolute;inset:40% 0 0}.doc-hero-copy{z-index:2;pointer-events:none;max-width:40rem;padding:0 clamp(1rem,4vw,3rem) clamp(3.5rem,8vh,5rem);position:relative}.doc-eyebrow{color:#9eafab;margin-bottom:.9rem;font-size:.9rem}.doc-hero-copy h1{color:#f6f1e8;letter-spacing:-.02em;margin-bottom:.9rem;font-size:clamp(2.25rem,5.5vw,4rem);font-weight:600;line-height:1.05}.doc-hero-copy p:last-child{color:#d4dbd6;font-size:1.15rem;line-height:1.55}.doc-hero-note{z-index:2;color:#8a9894;font-size:.78rem;position:absolute;bottom:1.25rem;right:clamp(1rem,4vw,3rem)}.doc-body{max-width:62rem;margin:0 auto;padding:3rem clamp(1rem,4vw,2rem) 4rem}.doc-body>section,.doc-details{margin-top:4.5rem}.doc-figures{border-bottom:1px solid #f6f1e81f;grid-template-columns:repeat(auto-fit,minmax(10rem,1fr));gap:1.5rem;padding-bottom:2rem;display:grid}.doc-figures div{flex-direction:column;display:flex}.doc-figures dd{color:#f6f1e8;letter-spacing:-.01em;font-size:2rem;font-weight:600}.doc-figures dt{color:#9eafab;order:1;font-size:.875rem}.doc-body h2{color:#f6f1e8;margin-bottom:1rem;font-size:1.75rem;font-weight:600}.doc-body h3{color:#f6f1e8;margin:2rem 0 .75rem;font-size:1.25rem;font-weight:600}.doc-body p,.doc-body li{color:#cdd5d1;line-height:1.7}.doc-body p{margin-bottom:.9rem}.doc-body strong{color:#f6f1e8;font-weight:600}.doc-body a{color:#8fd4cc;text-underline-offset:3px;-webkit-text-decoration:underline #8fd4cc59;text-decoration:underline #8fd4cc59}.doc-split{grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);align-items:center;gap:3rem;display:grid}.doc-body figure img{background:#05070a;border-radius:.25rem;width:100%;height:auto}.doc-body figcaption{color:#8a9894;margin-top:.6rem;font-size:.82rem;line-height:1.5}.doc-steps{gap:.5rem;max-width:44rem;margin-bottom:2rem;padding-left:1.3rem;list-style:decimal;display:grid}.doc-pair{grid-template-columns:repeat(2,minmax(0,1fr));gap:1rem;display:grid}.doc-results div{border-bottom:1px solid #f6f1e81a;justify-content:space-between;gap:1rem;padding:.75rem 0;display:flex}.doc-results dt{color:#cdd5d1}.doc-results dd{color:#f6f1e8;text-align:right;font-weight:600}.doc-body .doc-note{color:#9eafab;margin-top:.9rem;font-size:.92rem}.doc-details{border-top:1px solid #f6f1e81f;border-bottom:1px solid #f6f1e81f}.doc-details summary{color:#d4dbd6;cursor:pointer;align-items:center;min-height:3.5rem;font-weight:600;display:flex}.doc-details summary:before{content:"+";color:#9eafab;margin-right:.6rem}.doc-details[open] summary:before{content:"−"}.doc-details>div{padding-bottom:2rem}.doc-details ul,.doc-details ol{gap:.6rem;margin-bottom:1rem;padding-left:1.3rem;list-style:outside;display:grid}.doc-table-wrap{margin-bottom:1rem;overflow-x:auto}.doc-table-wrap table{border-collapse:collapse;width:100%;font-size:.95rem}.doc-table-wrap th,.doc-table-wrap td{color:#cdd5d1;text-align:left;border-bottom:1px solid #f6f1e81a;padding:.55rem .75rem .55rem 0}.doc-table-wrap th{color:#f6f1e8;font-weight:600}.doc-body .doc-sources{font-size:.9rem}.doc-sources h2{font-size:1.1rem}.doc-sources ul{gap:.4rem;margin-bottom:1rem;display:grid}@media (max-width:760px){.doc-split,.doc-pair{grid-template-columns:1fr}.doc-split{gap:1.5rem}.doc-hero-note{display:none}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{syntax:"*";inherits:false;initial-value:0}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"";inherits:false;initial-value:100%}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}@keyframes pulse{50%{opacity:.5}}@keyframes bounce{0%,to{animation-timing-function:cubic-bezier(.8,0,1,1);transform:translateY(-25%)}50%{animation-timing-function:cubic-bezier(0,0,.2,1);transform:none}}@keyframes fadeIn{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-20px)}} \ No newline at end of file diff --git a/assets/css/styles.css b/assets/css/styles.css index fb7c008901..384a4ef5f8 100644 --- a/assets/css/styles.css +++ b/assets/css/styles.css @@ -230,6 +230,7 @@ html { .project-action-link { display: inline-flex; align-items: center; + gap: 0.35rem; min-height: 44px; padding: 0.5rem 0.75rem; border-radius: 0.75rem; @@ -1729,3 +1730,708 @@ html { opacity: 1; } } + +/* Scrolled nav stays near-opaque so section copy never reads through the menu. */ +#navbar.glass-effect { + -webkit-backdrop-filter: blur(14px) saturate(1.15); + backdrop-filter: blur(14px) saturate(1.15); + border-width: 0 0 1px; + box-shadow: 0 10px 30px rgba(2, 5, 8, 0.35); +} + +/* Sections receive focus from skip links and in-page anchors; the scroll is the indicator. */ +[tabindex="-1"]:focus { + outline: none; +} + +/* Desktop dock: icon buttons that name themselves on hover or focus, so they stop covering card links. */ +@media (min-width: 641px) { + .portfolio-fab { + min-width: 46px; + height: 46px; + padding: 0 0.85rem; + gap: 0; + } + + .portfolio-fab span:last-child { + max-width: 0; + overflow: hidden; + white-space: nowrap; + opacity: 0; + transition: opacity 0.18s ease; + } + + .portfolio-fab:hover span:last-child, + .portfolio-fab:focus-visible span:last-child { + max-width: 8rem; + margin-left: 0.5rem; + opacity: 1; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } +} + +/* Technical expertise: a spec sheet, each column pointing at the work that proves it. */ +.skill-spec-grid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); +} + +.skill-spec { + --skill-accent: #58c7bc; + display: flex; + flex-direction: column; + border: 1px solid rgba(246, 241, 232, 0.12); + border-top: 2px solid var(--skill-accent); + border-radius: 0.5rem; + background: linear-gradient(180deg, rgba(246, 241, 232, 0.05), rgba(246, 241, 232, 0.015)), #0b1014; + padding: 1.25rem 1.25rem 1rem; +} + +.skill-spec-cyan { --skill-accent: #58c7bc; } +.skill-spec-brass { --skill-accent: #d8aa4f; } +.skill-spec-green { --skill-accent: #9bd66b; } +.skill-spec-coral { --skill-accent: #ff766d; } + +.skill-spec header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.9rem; +} + +.skill-spec-icon { + display: inline-grid; + place-items: center; + width: 2.5rem; + height: 2.5rem; + border: 1px solid rgba(246, 241, 232, 0.14); + border-radius: 0.4rem; + color: var(--skill-accent); +} + +.skill-spec-icon svg { + width: 1.6rem; + height: 1.6rem; +} + +.skill-spec-index { + font-family: 'Cascadia Mono', 'JetBrains Mono', Consolas, monospace; + font-size: 0.75rem; + letter-spacing: 0.16em; + color: #8a9894; +} + +.skill-spec h3 { + font-size: 1.2rem; + font-weight: 800; + color: #f6f1e8; + margin-bottom: 0.6rem; +} + +.skill-spec ul { + flex: 1; + margin-bottom: 0.9rem; +} + +.skill-spec li { + padding: 0.4rem 0; + border-bottom: 1px dashed rgba(246, 241, 232, 0.09); + color: #d4dbd6; + font-size: 0.95rem; +} + +.skill-spec li::before { + content: "▸ "; + color: var(--skill-accent); +} + +.skill-spec-proof { + font-size: 0.82rem; + color: #8a9894; +} + +.skill-spec-proof a { + color: var(--skill-accent); + text-decoration: underline; + text-underline-offset: 3px; + text-decoration-color: rgba(246, 241, 232, 0.25); +} + +.skill-spec-proof a:hover { + text-decoration-color: currentColor; +} + +@media (max-width: 640px) { + .skill-spec ul { + display: flex; + flex-wrap: wrap; + gap: 0.4rem; + } + + .skill-spec li { + border: 1px solid rgba(246, 241, 232, 0.12); + border-radius: 9999px; + padding: 0.25rem 0.65rem; + font-size: 0.82rem; + } + + .skill-spec li::before { + content: none; + } +} + +/* Retro shelf: one header, one ledger line, and a swipeable shelf on phones instead of six stacked cards. */ +.retro-archive-note p + p { + margin-top: 0.6rem; +} + +.retro-ledger { + margin-top: 0.8rem; + color: #c7c9c6; + font-family: 'Cascadia Mono', 'JetBrains Mono', Consolas, monospace; + font-size: 0.82rem; + line-height: 1.7; +} + +.retro-ledger strong { + color: #f2b84b; +} + +.retro-ledger span { + white-space: nowrap; +} + +.retro-swipe-hint { + display: none; +} + +@media (max-width: 640px) { + .retro-swipe-hint { + display: block; + margin: -1rem 0 0.75rem; + color: #8a9894; + font-family: 'Cascadia Mono', 'JetBrains Mono', Consolas, monospace; + font-size: 0.78rem; + text-align: right; + } + + .retro-game-grid.retro-catalog-grid { + display: flex; + gap: 0.75rem; + overflow-x: auto; + overscroll-behavior-x: contain; + scroll-snap-type: x mandatory; + margin-inline: -1rem; + padding: 0.25rem 1rem 1rem; + scrollbar-width: thin; + } + + .retro-catalog-grid .retro-game-card { + flex: 0 0 84%; + scroll-snap-align: center; + } + + .retro-catalog-grid .retro-game-card:hover, + .retro-catalog-grid .retro-game-card:focus-within { + transform: none; + } +} + +.secret-key-map { + display: grid; + gap: 0.55rem; + text-align: left; + font-size: 0.875rem; +} + +.secret-key-map div { + display: grid; + grid-template-columns: minmax(0, auto) 1fr; + gap: 0.75rem; + align-items: baseline; + padding-bottom: 0.55rem; + border-bottom: 1px dashed rgba(246, 241, 232, 0.09); +} + +.secret-key-map dd { + color: #b8c0bc; +} + +.secret-key-map kbd { + display: inline-block; + min-width: 1.6rem; + padding: 0.1rem 0.45rem; + border: 1px solid rgba(246, 241, 232, 0.2); + border-bottom-width: 2px; + border-radius: 0.3rem; + background: rgba(5, 7, 10, 0.6); + color: #f6f1e8; + font-family: 'Cascadia Mono', 'JetBrains Mono', Consolas, monospace; + font-size: 0.75rem; + text-align: center; + white-space: nowrap; +} + +/* Project cards: line-art schematics of the real mechanism on a dark bench, tinted per discipline. */ +.project-visual-schematic { + --tone: 88, 199, 188; + background: + radial-gradient(120% 140% at 50% 0%, rgba(var(--tone), 0.12), transparent 62%), + #0b1014; +} + +.project-visual-schematic[data-tone="brass"] { --tone: 216, 170, 79; } +.project-visual-schematic[data-tone="green"] { --tone: 155, 214, 107; } + +.project-visual-schematic::before { + opacity: 0.16; +} + +.project-visual-art { + position: absolute; + inset: 0; + z-index: 1; + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; +} + +.project-card:hover .project-visual-art { + transform: scale(1.03); +} + +.project-visual-schematic .project-visual-label { + z-index: 2; +} + +/* Wide project card: art beside the copy, so an odd card closes the two-column grid. */ +@media (min-width: 1024px) { + .project-card-wide { + grid-column: 1 / -1; + display: grid; + grid-template-columns: 1fr 1fr; + align-items: stretch; + } +} + +.project-visual-photo { + background: #05070a; +} + +/* Connectome lab: same two-column frame as the other labs, with a black bench for the point cloud. */ +.brain-lab-stage { + position: relative; + min-height: 34rem; + overflow: hidden; + background: #05070a; +} + +.brain-lab-stage img, +.brain-lab-stage canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} + +.brain-lab-stage img { + object-fit: contain; + transition: opacity 0.4s ease; +} + +.brain-lab-stage.is-live img { + opacity: 0; +} + +.brain-lab-stage canvas { + cursor: grab; + touch-action: pan-y; +} + +.brain-lab-stage canvas:active { + cursor: grabbing; +} + +.brain-lab-note { + color: #9eafab; + font-size: 0.82rem; + line-height: 1.5; +} + +.path-related-link { + color: #d8aa4f; + font-size: 0.9rem; + text-decoration: underline; + text-underline-offset: 3px; +} + +@media (max-width: 640px) { + .brain-lab-stage { + min-height: 26rem; + } +} + +/* Project notes pages: quiet type, one accent, the imagery does the work. */ +.doc-page { + background: #05070a; +} + +.doc-nav { + position: absolute; + inset: 0 0 auto; + z-index: 3; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.75rem clamp(1rem, 4vw, 3rem); + font-size: 0.875rem; +} + +.doc-nav div { + display: flex; + gap: 1.25rem; +} + +.doc-nav a { + display: inline-flex; + align-items: center; + min-height: 44px; + color: #c7cfcb; +} + +.doc-nav a:hover { + color: #f6f1e8; +} + +.doc-nav .doc-nav-home { + color: #d8aa4f; + font-size: 1.4rem; + font-weight: 800; +} + +.doc-hero { + position: relative; + display: flex; + min-height: min(100svh, 56rem); + align-items: flex-end; + overflow: hidden; + background: #05070a; +} + +.doc-hero-poster, +#brain-canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; +} + +.doc-hero-poster { + object-fit: contain; + transition: opacity 0.6s ease; +} + +#brain-canvas { + opacity: 0; + cursor: grab; + touch-action: pan-y; + transition: opacity 0.6s ease; +} + +#brain-canvas:active { + cursor: grabbing; +} + +.doc-hero.is-live #brain-canvas { + opacity: 1; +} + +.doc-hero.is-live .doc-hero-poster { + opacity: 0; +} + +.doc-hero::after { + content: ""; + position: absolute; + inset: 40% 0 0; + z-index: 1; + background: linear-gradient(180deg, transparent, rgba(5, 7, 10, 0.95) 70%); + pointer-events: none; +} + +.doc-hero-copy { + position: relative; + z-index: 2; + max-width: 40rem; + padding: 0 clamp(1rem, 4vw, 3rem) clamp(3.5rem, 8vh, 5rem); + pointer-events: none; +} + +.doc-eyebrow { + margin-bottom: 0.9rem; + color: #9eafab; + font-size: 0.9rem; +} + +.doc-hero-copy h1 { + margin-bottom: 0.9rem; + color: #f6f1e8; + font-size: clamp(2.25rem, 5.5vw, 4rem); + font-weight: 600; + line-height: 1.05; + letter-spacing: -0.02em; +} + +.doc-hero-copy p:last-child { + color: #d4dbd6; + font-size: 1.15rem; + line-height: 1.55; +} + +.doc-hero-note { + position: absolute; + right: clamp(1rem, 4vw, 3rem); + bottom: 1.25rem; + z-index: 2; + color: #8a9894; + font-size: 0.78rem; +} + +.doc-body { + max-width: 62rem; + margin: 0 auto; + padding: 3rem clamp(1rem, 4vw, 2rem) 4rem; +} + +.doc-body > section, +.doc-details { + margin-top: 4.5rem; +} + +.doc-figures { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); + gap: 1.5rem; + padding-bottom: 2rem; + border-bottom: 1px solid rgba(246, 241, 232, 0.12); +} + +.doc-figures div { + display: flex; + flex-direction: column; +} + +.doc-figures dd { + color: #f6f1e8; + font-size: 2rem; + font-weight: 600; + letter-spacing: -0.01em; +} + +.doc-figures dt { + order: 1; + color: #9eafab; + font-size: 0.875rem; +} + +.doc-body h2 { + margin-bottom: 1rem; + color: #f6f1e8; + font-size: 1.75rem; + font-weight: 600; +} + +.doc-body h3 { + margin: 2rem 0 0.75rem; + color: #f6f1e8; + font-size: 1.25rem; + font-weight: 600; +} + +.doc-body p, +.doc-body li { + color: #cdd5d1; + line-height: 1.7; +} + +.doc-body p { + margin-bottom: 0.9rem; +} + +.doc-body strong { + color: #f6f1e8; + font-weight: 600; +} + +.doc-body a { + color: #8fd4cc; + text-decoration: underline; + text-decoration-color: rgba(143, 212, 204, 0.35); + text-underline-offset: 3px; +} + +.doc-split { + display: grid; + grid-template-columns: minmax(0, 1.1fr) minmax(0, 0.9fr); + gap: 3rem; + align-items: center; +} + +.doc-body figure img { + width: 100%; + height: auto; + border-radius: 0.25rem; + background: #05070a; +} + +.doc-body figcaption { + margin-top: 0.6rem; + color: #8a9894; + font-size: 0.82rem; + line-height: 1.5; +} + +.doc-steps { + display: grid; + gap: 0.5rem; + max-width: 44rem; + margin-bottom: 2rem; + padding-left: 1.3rem; + list-style: decimal; +} + +.doc-pair { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1rem; +} + +.doc-results div { + display: flex; + justify-content: space-between; + gap: 1rem; + padding: 0.75rem 0; + border-bottom: 1px solid rgba(246, 241, 232, 0.1); +} + +.doc-results dt { + color: #cdd5d1; +} + +.doc-results dd { + color: #f6f1e8; + font-weight: 600; + text-align: right; +} + +.doc-body .doc-note { + margin-top: 0.9rem; + color: #9eafab; + font-size: 0.92rem; +} + +.doc-details { + border-top: 1px solid rgba(246, 241, 232, 0.12); + border-bottom: 1px solid rgba(246, 241, 232, 0.12); +} + +.doc-details summary { + display: flex; + align-items: center; + min-height: 3.5rem; + color: #d4dbd6; + font-weight: 600; + cursor: pointer; +} + +.doc-details summary::before { + content: "+"; + margin-right: 0.6rem; + color: #9eafab; +} + +.doc-details[open] summary::before { + content: "\2212"; +} + +.doc-details > div { + padding-bottom: 2rem; +} + +.doc-details ul, +.doc-details ol { + display: grid; + gap: 0.6rem; + margin-bottom: 1rem; + padding-left: 1.3rem; + list-style: disc; +} + +.doc-table-wrap { + overflow-x: auto; + margin-bottom: 1rem; +} + +.doc-table-wrap table { + width: 100%; + border-collapse: collapse; + font-size: 0.95rem; +} + +.doc-table-wrap th, +.doc-table-wrap td { + padding: 0.55rem 0.75rem 0.55rem 0; + border-bottom: 1px solid rgba(246, 241, 232, 0.1); + color: #cdd5d1; + text-align: left; +} + +.doc-table-wrap th { + color: #f6f1e8; + font-weight: 600; +} + +.doc-body .doc-sources { + font-size: 0.9rem; +} + +.doc-sources h2 { + font-size: 1.1rem; +} + +.doc-sources ul { + display: grid; + gap: 0.4rem; + margin-bottom: 1rem; +} + +@media (max-width: 760px) { + .doc-split, + .doc-pair { + grid-template-columns: 1fr; + } + + .doc-split { + gap: 1.5rem; + } + + .doc-hero-note { + display: none; + } +} diff --git a/assets/data/fly-brain-points.bin b/assets/data/fly-brain-points.bin new file mode 100644 index 0000000000..9ddba1246c Binary files /dev/null and b/assets/data/fly-brain-points.bin differ diff --git a/assets/data/github-repos.json b/assets/data/github-repos.json index 4a5cbfba58..8cf6cebeac 100644 --- a/assets/data/github-repos.json +++ b/assets/data/github-repos.json @@ -1,6 +1,6 @@ { "source": "https://api.github.com/users/grimgrimberg/repos?per_page=100&sort=updated", - "snapshotDate": "2026-05-13", + "snapshotDate": "2026-11-02", "categories": { "featured": { "label": "Featured", @@ -60,6 +60,14 @@ "tags": ["LangGraph", "agentic AI", "reports", "observability"], "pitch": "Agentic market-movers pipeline with explainable HTML, Excel, and email digests plus deterministic fallbacks." }, + { + "name": "fpv-vggt-visual-geometry-lab", + "category": "featured", + "url": "https://github.com/grimgrimberg/fpv-vggt-visual-geometry-lab", + "language": "Python", + "tags": ["computer vision", "3D reconstruction", "VGGT", "WebGL"], + "pitch": "Offline FPV visual geometry lab: VGGT on another creator's public dataset, experimental reconstruction methods, smoothing, and 13 published scene packages." + }, { "name": "gnc-tracking-intercept-sim", "category": "lab", @@ -243,6 +251,46 @@ "language": "", "tags": ["formula student", "simulator"], "pitch": "Forked simulator reference for Formula Student Driverless." + }, + { + "name": "LSD_Thesis", + "category": "lab", + "url": "https://github.com/grimgrimberg/LSD_Thesis", + "language": "Python", + "tags": ["explainable AI", "surrogate models", "dashboard"], + "pitch": "Thesis dashboard built around transparent surrogate models and explainable-AI evidence loops." + }, + { + "name": "neetcode-gpt", + "category": "learning", + "url": "https://github.com/grimgrimberg/neetcode-gpt", + "language": "Python", + "tags": ["transformers", "from scratch", "ML course"], + "pitch": "A GPT built from scratch, assembled from my NeetCode ML course submissions." + }, + { + "name": "neetcode-submissions", + "category": "learning", + "url": "https://github.com/grimgrimberg/neetcode-submissions", + "language": "Python", + "tags": ["algorithms", "practice"], + "pitch": "NeetCode problem submissions: algorithm practice in public." + }, + { + "name": "lerobot", + "category": "forks", + "url": "https://github.com/grimgrimberg/lerobot", + "language": "Python", + "tags": ["robot learning", "Hugging Face"], + "pitch": "Fork of Hugging Face LeRobot for end-to-end robot learning reference." + }, + { + "name": "rosbridge_suite", + "category": "forks", + "url": "https://github.com/grimgrimberg/rosbridge_suite", + "language": "Python", + "tags": ["ROS", "rosbridge", "integration"], + "pitch": "Fork of the rosbridge v2 protocol servers, kept as ROS integration context." } ] } diff --git a/assets/images/fly101/arm.webp b/assets/images/fly101/arm.webp new file mode 100644 index 0000000000..2f474eed7c Binary files /dev/null and b/assets/images/fly101/arm.webp differ diff --git a/assets/images/fly101/brain.webp b/assets/images/fly101/brain.webp new file mode 100644 index 0000000000..0cecf5b74a Binary files /dev/null and b/assets/images/fly101/brain.webp differ diff --git a/assets/images/fly101/cns.webp b/assets/images/fly101/cns.webp new file mode 100644 index 0000000000..2d606485a3 Binary files /dev/null and b/assets/images/fly101/cns.webp differ diff --git a/assets/images/fly101/fly.webp b/assets/images/fly101/fly.webp new file mode 100644 index 0000000000..c52a765421 Binary files /dev/null and b/assets/images/fly101/fly.webp differ diff --git a/assets/images/fly101/fly101-og.jpg b/assets/images/fly101/fly101-og.jpg new file mode 100644 index 0000000000..bcd91251b5 Binary files /dev/null and b/assets/images/fly101/fly101-og.jpg differ diff --git a/assets/images/icons/autonomy.svg b/assets/images/icons/autonomy.svg new file mode 100644 index 0000000000..7620d6b95f --- /dev/null +++ b/assets/images/icons/autonomy.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/icons/control.svg b/assets/images/icons/control.svg new file mode 100644 index 0000000000..fae574c338 --- /dev/null +++ b/assets/images/icons/control.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/images/icons/ml.svg b/assets/images/icons/ml.svg new file mode 100644 index 0000000000..0ca88deed8 --- /dev/null +++ b/assets/images/icons/ml.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/icons/programming.svg b/assets/images/icons/programming.svg new file mode 100644 index 0000000000..010237e343 --- /dev/null +++ b/assets/images/icons/programming.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/og-card.png b/assets/images/og-card.png new file mode 100644 index 0000000000..d158ab1d0b Binary files /dev/null and b/assets/images/og-card.png differ diff --git a/assets/images/projects/bgr-path-planning.svg b/assets/images/projects/bgr-path-planning.svg new file mode 100644 index 0000000000..f57e233b53 --- /dev/null +++ b/assets/images/projects/bgr-path-planning.svg @@ -0,0 +1,37 @@ + + Pure pursuit on a Formula Student track: blue and yellow cone boundaries, the planned line, the slightly-off driven trail, the car, and the lookahead point at distance Ld with the steering arc to it + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ld + α + SACRIFICIAL + κ = 2 sin α / Ld + PLAN + DRIVEN + + diff --git a/assets/images/projects/daily-movers.svg b/assets/images/projects/daily-movers.svg new file mode 100644 index 0000000000..f2003855d8 --- /dev/null +++ b/assets/images/projects/daily-movers.svg @@ -0,0 +1,39 @@ + + Daily movers pipeline: fetch, analyze, deterministic fallback and report stages producing HTML and XLSX, above a strip of daily candles with one big gainer and one big loser + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FETCH + ANALYZE + FALLBACK + REPORT + HTMLXLSX + HONK +18.4% + −9.1% + + diff --git a/assets/images/projects/fpv-reconstruction.svg b/assets/images/projects/fpv-reconstruction.svg new file mode 100644 index 0000000000..12ae82fbd4 --- /dev/null +++ b/assets/images/projects/fpv-reconstruction.svg @@ -0,0 +1,32 @@ + + FPV reconstruction sketch: a curved drone flight path with camera frustums, two of which triangulate one point of a sparse point cloud showing the ground, a shed and a tree + + + + + + + + + + + + + + + + + + + + + + + + + TREE + (PROBABLY) + CAMERA PATH + xyz + + diff --git a/assets/images/projects/orbital-rendezvous.svg b/assets/images/projects/orbital-rendezvous.svg new file mode 100644 index 0000000000..76e9c506d3 --- /dev/null +++ b/assets/images/projects/orbital-rendezvous.svg @@ -0,0 +1,48 @@ + + Orbital rendezvous in the LVLH frame: a chaser hops along the V-bar on Clohessy-Wiltshire arcs toward a target inside a dashed keep-out circle, with thrust arrows bounded by a maximum + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x R-BAR + y V-BAR + KEEP-OUT + (NO BOOPING) + |u|≤umax + Δv 0.38 m/s + + diff --git a/assets/images/projects/repo-summarizer.svg b/assets/images/projects/repo-summarizer.svg new file mode 100644 index 0000000000..579fc01133 --- /dev/null +++ b/assets/images/projects/repo-summarizer.svg @@ -0,0 +1,70 @@ + + Repo summarizer pipeline: a file tree feeds deterministic top-k selection, then token-budgeted shards, then an LLM provider that emits strict JSON, with a dashed provider-fallback route around it + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + repo/ + src/ + api.py + cli.py + tests/ + node_modules/ + README.md + pyproject.toml + + SELECT + SHARDS + LLM + JSON + + k + PROVIDER FALLBACK + "summary""stack""layout" + POST /summarize + $ summarize . + SCHEMA OK + + diff --git a/assets/images/projects/ugv-diagnostics.svg b/assets/images/projects/ugv-diagnostics.svg new file mode 100644 index 0000000000..d55d68c2d3 --- /dev/null +++ b/assets/images/projects/ugv-diagnostics.svg @@ -0,0 +1,66 @@ + + UGV diagnostics schematic: four software components on a shared bus, a heartbeat trace with a missed beat, and a watchdog timeout + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OPERATOR + BACKEND + TELEMETRY + VEHICLE IO + 250 MS + + HEARTBEAT + WATCHDOG + BUS + THREADS + + hb ok + cmd ack + tlm ok + hb late + wdt bark + + + diff --git a/assets/js/fly-brain.js b/assets/js/fly-brain.js new file mode 100644 index 0000000000..26660c05d3 --- /dev/null +++ b/assets/js/fly-brain.js @@ -0,0 +1,131 @@ +// Rotating point cloud of real MaleCNS neuron positions, used by the Fly101 page and the homepage lab. +// Anatomy only: no neural activity is simulated here. A poster or status line stays if WebGL or the data fails. +const COLORS = [[0.85, 0.67, 0.31], [0.35, 0.78, 0.74], [0.62, 0.72, 0.7], [1, 0.46, 0.43]]; +const DATA_URL = new URL('../data/fly-brain-points.bin', import.meta.url); + +const FRAMINGS = { + // Keeps the brain clear of the headline; the nerve cord falls away below it. + hero: (narrow) => ({ z: narrow ? 6.2 : 3, x: narrow ? 0 : 0.42, y: narrow ? 0.15 : -0.3, opacity: narrow ? 0.14 : 0.24 }), + panel: (narrow) => ({ z: narrow ? 3.9 : 2.9, x: 0, y: 0, opacity: narrow ? 0.13 : 0.2 }) +}; + +export async function mountBrain({ stage, canvas, framing = 'hero' }) { + const [THREE, response] = await Promise.all([ + import('../vendor/three/three.module.min.js'), + fetch(DATA_URL) + ]); + if (!response.ok) throw new Error('Neuron positions failed to load'); + const buffer = await response.arrayBuffer(); + const count = buffer.byteLength / 7; + const raw = new Int16Array(buffer, 0, count * 3); + const regions = new Uint8Array(buffer, count * 6, count); + const positions = new Float32Array(count * 3); + const colors = new Float32Array(count * 3); + for (let i = 0; i < count; i++) { + positions[i * 3] = raw[i * 3] / 32000; + positions[i * 3 + 1] = -raw[i * 3 + 2] / 32000; + positions[i * 3 + 2] = raw[i * 3 + 1] / 32000; + colors.set(COLORS[regions[i]], i * 3); + } + + const geometry = new THREE.BufferGeometry(); + geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3)); + const colorAttribute = new THREE.BufferAttribute(colors, 3); + geometry.setAttribute('color', colorAttribute); + geometry.center(); + const material = new THREE.PointsMaterial({ + size: 1.4, sizeAttenuation: false, vertexColors: true, + transparent: true, opacity: 0.24, depthWrite: false, blending: THREE.AdditiveBlending + }); + const cloud = new THREE.Points(geometry, material); + const rig = new THREE.Group(); + rig.add(cloud); + const scene = new THREE.Scene(); + scene.add(rig); + const camera = new THREE.PerspectiveCamera(28, 1, 0.1, 20); + const renderer = new THREE.WebGLRenderer({ canvas, antialias: false, powerPreference: 'low-power' }); + renderer.setClearColor(0x05070a); + renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2)); + + const state = { yaw: -0.35, pitch: 0.08, dragging: false, x: 0, y: 0, visible: true }; + const reduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches; + + function draw() { + cloud.rotation.set(state.pitch, state.yaw, 0); + renderer.render(scene, camera); + } + + const resize = () => { + const { width, height } = stage.getBoundingClientRect(); + if (!width || !height) return; + renderer.setSize(width, height, false); + camera.aspect = width / height; + const view = FRAMINGS[framing](width / height < 1); + camera.position.z = view.z; + rig.position.set(view.x, view.y, 0); + material.opacity = view.opacity; + camera.updateProjectionMatrix(); + draw(); + }; + resize(); + new ResizeObserver(resize).observe(stage); + + canvas.addEventListener('pointerdown', (event) => { + state.dragging = true; + state.x = event.clientX; + state.y = event.clientY; + canvas.setPointerCapture(event.pointerId); + }); + canvas.addEventListener('pointermove', (event) => { + if (!state.dragging) return; + state.yaw += (event.clientX - state.x) * 0.006; + state.pitch = Math.max(-0.9, Math.min(0.9, state.pitch + (event.clientY - state.y) * 0.004)); + state.x = event.clientX; + state.y = event.clientY; + if (reduced) draw(); + }); + const release = () => { + state.dragging = false; + }; + canvas.addEventListener('pointerup', release); + canvas.addEventListener('pointercancel', release); + canvas.addEventListener('keydown', (event) => { + const steps = { ArrowLeft: -0.15, ArrowRight: 0.15 }; + if (!(event.key in steps)) return; + event.preventDefault(); + state.yaw += steps[event.key]; + draw(); + }); + new IntersectionObserver((entries) => { + state.visible = entries[0].isIntersecting; + }).observe(stage); + + stage.classList.add('is-live'); + if (!reduced) { + renderer.setAnimationLoop(() => { + if (!state.visible) return; + if (!state.dragging) state.yaw += 0.0022; + draw(); + }); + } + + return { + // Additive blending makes black points invisible, so a region can switch off without rebuilding anything. + setRegion(region, visible) { + const color = visible ? COLORS[region] : [0, 0, 0]; + for (let i = 0; i < count; i++) { + if (regions[i] === region) colors.set(color, i * 3); + } + colorAttribute.needsUpdate = true; + draw(); + } + }; +} + +const heroStage = document.getElementById('brain-stage'); +const heroCanvas = document.getElementById('brain-canvas'); +if (heroStage && heroCanvas && 'WebGL2RenderingContext' in window) { + const begin = () => mountBrain({ stage: heroStage, canvas: heroCanvas }).catch(() => {}); + if (document.readyState === 'complete') begin(); + else window.addEventListener('load', begin, { once: true }); +} diff --git a/assets/js/index-page.js b/assets/js/index-page.js index 29e0416568..c769380b4a 100644 --- a/assets/js/index-page.js +++ b/assets/js/index-page.js @@ -5,9 +5,9 @@ const COPY_RESET_DELAY_MS = 1600; const MAILTO_DELAY_MS = 100; const EMAIL_ADDRESS = 'yuval.grimberg@gmail.com'; - const CV_PUBLIC_PATH = ['cv', 'html'].join('.'); const REPO_SNAPSHOT_URL = './assets/data/github-repos.json'; const CLIPPY_AGENT_BASE_PATH = './assets/vendor/clippyjs/agents/'; + const CLIPPY_SCRIPTS = ['./assets/vendor/clippyjs/jquery-3.7.1.min.js', './assets/vendor/clippyjs/clippy.min.js']; const FALLBACK_REPO_SNAPSHOT = { categories: { @@ -50,6 +50,14 @@ language: 'Python', tags: ['LangGraph', 'reports', 'agentic AI'], pitch: 'Agentic market-movers pipeline with explainable digests.' + }, + { + name: 'fpv-vggt-visual-geometry-lab', + category: 'featured', + url: 'https://github.com/grimgrimberg/fpv-vggt-visual-geometry-lab', + language: 'Python', + tags: ['computer vision', '3D reconstruction', 'VGGT'], + pitch: 'Offline FPV visual geometry lab with VGGT, experimental reconstruction methods and a WebGL viewer.' } ] }; @@ -102,21 +110,21 @@ tools: 'Python, ROS2, OpenCV', mode: 'Build, test, iterate', bullets: [ - 'Developed shared Python backend and operator interfaces for command handling, telemetry and diagnostics on a full-time UGV engagement, July–September 2026. Added logging across four components, thread-timing diagnostics, watchdogs and heartbeat handling.', - 'Led BGR planning/control from 2023 to late 2025, sharing implementation and contributing to approximately 50 metres of physical autonomous driving.', + 'Built shared Python backend and operator interfaces for a UGV: command handling, telemetry, diagnostics, watchdogs and heartbeats (full-time, July–September 2026).', + 'Led five student developers on BGR Formula Student planning and control, 2023 to late 2025; our car drove about 50 metres autonomously.', 'Useful when the job needs range, not a one-trick framework certificate.' ] }, autonomy: { title: 'Autonomy / Controls Engineer', summary: - 'Strong fit for path planning, control loops, estimation-adjacent work, and teams that need engineering judgment when the demo meets reality.', + 'Path planning and control loops that still behave when the demo meets the real car, plus the judgment to tell which one is lying.', proof: 'Path planning + LQI', tools: 'Control, dynamics, Python', mode: 'Model, simulate, validate', bullets: [ - 'BGR shared implementation used an adapted planner, Pure Pursuit, Stanley steering and curvature-based PID speed control.', - 'Orbital rendezvous work shows comfort with state-space thinking and controller comparison.', + 'BGR: co-wrote an adapted cone planner, Pure Pursuit, Stanley steering and curvature-based PID speed control, tested in simulation and on physical actuators.', + 'Orbital rendezvous: Clohessy–Wiltshire dynamics with LQI, compared against constrained MPC.', 'I like systems that can be explained, plotted, and debugged under pressure.' ] }, @@ -124,12 +132,12 @@ title: 'Simulation / GNC Developer', summary: 'I can build the simulation scaffolding around a control idea, then keep it inspectable enough that other humans can trust it.', - proof: 'Orbital rendezvous LQI', + proof: 'Orbital LQI + GNC tracking sim', tools: 'SciPy, MATLAB, plotting', mode: 'Reproduce, compare, explain', bullets: [ - 'Built orbital propagation and rendezvous-control experiments with reproducible plots.', - 'Comfortable moving between equations, code, and visual inspection.', + 'Orbital study: two-body propagation, rendezvous control and reproducible plots.', + 'GNC sim: Kalman line-of-sight tracking and a 3D intercept simulator (pure pursuit, predictive lead, proportional navigation) built around a real drone flight log.', 'Good fit for simulation work where correctness and communication both matter.' ] }, @@ -141,7 +149,7 @@ tools: 'FastAPI, LangGraph, testing', mode: 'Ship useful tools', bullets: [ - 'GitHub repo summarizer shows product-shaped AI tooling, not just prompt confetti.', + 'GitHub repo summarizer: deterministic file selection, sharding, validated JSON and provider fallbacks. Product-shaped AI tooling, not prompt confetti.', 'FPV: VGGT on another creator’s public dataset, experimental methods and 13 scene packages. Daily Movers: optional LLM analysis; the published sample is heuristic.', 'I care about deterministic structure, testability, and making tools usable by people.' ] @@ -159,7 +167,7 @@ const ROAST_LINES = [ 'Roast mode is opt-in, so here it is: some repos are polished, some are learning fossils, and one or two look like they escaped a lab notebook. Still useful. Still honest.', 'Yuval has range. This is a strength until you ask him to name the one thing he does. Then the spreadsheet starts sweating.', - 'The portfolio has a goose, a terminal, and a CV request flow. Subtle? No. Memorable? Unfortunately, yes.', + 'The portfolio has a goose, a terminal, and a retro games shelf. Subtle? No. Memorable? Unfortunately, yes.', 'He says "systems thinking" because "I keep pulling on threads until the machine confesses" sounds less LinkedIn-compliant.' ]; @@ -181,6 +189,9 @@ let konamiSequence = []; let repoSnapshot = FALLBACK_REPO_SNAPSHOT; let commandHistory = []; + let commandPaletteWelcomed = false; + let commandPaletteOpener = null; + let modalCount = 0; const gooseTerrorState = { active: false, layer: null, @@ -221,17 +232,6 @@ realClippyHideTimer = realClippySpeechTimer = realClippyGoodbyeTimer = null; } - function initAos() { - if (window.AOS && typeof window.AOS.init === 'function') { - window.AOS.init({ - duration: 800, - easing: 'ease-in-out', - once: true, - mirror: false - }); - } - } - function updateContactStatus(message) { const statusEl = document.getElementById('contact-status'); if (statusEl) { @@ -351,77 +351,6 @@ return copied; } - function setCvGateStatus(message, isError = false) { - const status = document.getElementById('cv-gate-status'); - if (!status) { - return; - } - - status.textContent = message; - status.classList.toggle('text-tech-pink', isError); - status.classList.toggle('text-tech-green', !isError); - } - - function revealPublicCv(announce = true) { - const panel = document.getElementById('cv-unlocked-panel'); - const publicLink = document.getElementById('cv-public-link'); - - if (publicLink) { - publicLink.href = CV_PUBLIC_PATH; - } - - if (panel) { - panel.hidden = false; - } - - try { - window.sessionStorage.setItem('portfolioCvUnlocked', 'true'); - } catch (error) { - // Session persistence is a convenience only. - } - - setCvGateStatus('Human enough. Public CV unlocked.'); - if (announce) { - showClippy('Public CV ready. The Master PDF is available from the resume page.', 18000); - } - } - - function initCvGate() { - const answerInput = document.getElementById('cv-human-answer'); - const unlockButton = document.getElementById('cv-unlock-button'); - - if (!answerInput || !unlockButton) { - return; - } - - try { - if (window.sessionStorage.getItem('portfolioCvUnlocked') === 'true') { - revealPublicCv(false); - } - } catch (error) { - // Ignore storage failures and keep the gate interactive. - } - - const verify = () => { - const answer = answerInput.value.trim(); - if (answer === '13' || answer.toLowerCase() === 'thirteen') { - revealPublicCv(); - return; - } - - setCvGateStatus('Not quite. Hint: Clippy counted on his wire and got 13.', true); - answerInput.focus(); - }; - - unlockButton.addEventListener('click', verify); - answerInput.addEventListener('keydown', (event) => { - if (event.key === 'Enter') { - event.preventDefault(); - verify(); - } - }); - } - function renderContactSuccess(mailtoLink, previewText) { const formContainer = document.getElementById('simple-contact-form'); if (!formContainer) { @@ -441,7 +370,7 @@

Email Draft Ready

If your mail app did not open, use the link below or copy the draft. No backend, no pretend inbox, no nonsense.

- + Try Opening Email Again
@@ -916,46 +845,77 @@ clippyElement.dataset.hideTimer = String(timerId); } + function loadScript(src) { + return new Promise((resolve, reject) => { + const script = document.createElement('script'); + script.src = src; + script.onload = resolve; + script.onerror = reject; + document.head.appendChild(script); + }); + } + function initRealClippy() { if (realClippyLoadStarted) { - return; + return realClippyLoadStarted; } - realClippyLoadStarted = true; updateRealClippyGlobals(); + realClippyLoadStarted = CLIPPY_SCRIPTS + .reduce((chain, src) => chain.then(() => loadScript(src)), Promise.resolve()) + .then(() => new Promise((resolve) => { + if (!window.clippy || typeof window.clippy.load !== 'function' || !window.jQuery) { + resolve(false); + return; + } + + window.clippy.BASE_PATH = CLIPPY_AGENT_BASE_PATH; + window.clippy.load( + 'Clippy', + (agent) => { + realClippyAgent = agent; + realClippyLoaded = true; + updateRealClippyGlobals(); + allowRealClippySoundAfterGesture(realClippyAgent); + realClippyAgent.hide(true); + resetRealClippyQueue(realClippyAgent); + const close = document.createElement('button'); + close.id = 'real-clippy-dismiss'; close.className = 'clippy-visit-close'; close.type = 'button'; close.textContent = '×'; + close.setAttribute('aria-label', 'Dismiss Clippy for this visit'); + close.addEventListener('mousedown', event => event.stopPropagation()); + close.addEventListener('click', event => {event.stopPropagation();hideClippy(true);}); + document.querySelector('.clippy')?.appendChild(close); + window.dispatchEvent(new CustomEvent('portfolio:clippy-ready')); + resolve(true); + }, + () => { + realClippyLoaded = false; + updateRealClippyGlobals(); + resolve(false); + } + ); + })) + .catch(() => { + realClippyLoaded = false; + updateRealClippyGlobals(); + return false; + }); + return realClippyLoadStarted; + } - if (!window.clippy || typeof window.clippy.load !== 'function' || !window.jQuery) { + function scheduleRealClippy() { + // The animated agent is ~1.5 MB. Phones and Save-Data get the text helper until someone asks for Clippy. + if (navigator.connection?.saveData || window.matchMedia('(max-width: 640px)').matches) { return; } - window.clippy.BASE_PATH = CLIPPY_AGENT_BASE_PATH; - - try { - window.clippy.load( - 'Clippy', - (agent) => { - realClippyAgent = agent; - realClippyLoaded = true; - updateRealClippyGlobals(); - allowRealClippySoundAfterGesture(realClippyAgent); - realClippyAgent.hide(true); - resetRealClippyQueue(realClippyAgent); - const close = document.createElement('button'); - close.id = 'real-clippy-dismiss'; close.className = 'clippy-visit-close'; close.type = 'button'; close.textContent = '×'; - close.setAttribute('aria-label', 'Dismiss Clippy for this visit'); - close.addEventListener('mousedown', event => event.stopPropagation()); - close.addEventListener('click', event => {event.stopPropagation();hideClippy(true);}); - document.querySelector('.clippy')?.appendChild(close); - window.dispatchEvent(new CustomEvent('portfolio:clippy-ready')); - }, - () => { - realClippyLoaded = false; - updateRealClippyGlobals(); - } - ); - } catch { - realClippyLoaded = false; - updateRealClippyGlobals(); + const start = () => ('requestIdleCallback' in window + ? window.requestIdleCallback(() => initRealClippy(), { timeout: 3000 }) + : window.setTimeout(initRealClippy, 1500)); + if (document.readyState === 'complete') { + start(); + } else { + window.addEventListener('load', start, { once: true }); } } @@ -1213,7 +1173,7 @@ if (event.altKey && event.shiftKey && event.code === 'KeyC') { event.preventDefault(); - showClippy('Old-school Clippy activated. No third-party script, no clipboard drama, just one paperclip doing its job.', 18000); + showClippyGuide('Old-school Clippy activated. No third-party script, no clipboard drama, just one paperclip doing its job.'); } konamiSequence.push(event.code); @@ -1231,10 +1191,14 @@ showClippy('Konami code activated. Ultimate easter egg unlocked.', 18000); - document.body.style.animation = 'rainbow 2s infinite'; - window.setTimeout(() => { - document.body.style.animation = ''; - }, 5000); + // Animate main, not body: a filter on body would re-anchor every fixed element for five seconds. + const stage = document.getElementById('main-content'); + if (stage && !window.matchMedia('(prefers-reduced-motion: reduce)').matches) { + stage.style.animation = 'rainbow 2s infinite'; + window.setTimeout(() => { + stage.style.animation = ''; + }, 5000); + } konamiSequence = []; }); } @@ -1402,6 +1366,7 @@ ${role.bullets.map((bullet) => `- ${escapeHtml(bullet)}`).join('\n')}`; const isActive = button.getAttribute('data-hire-role') === roleKey; button.classList.toggle('is-active', isActive); button.setAttribute('aria-selected', isActive ? 'true' : 'false'); + button.tabIndex = isActive ? 0 : -1; }); document.querySelectorAll('[data-command-run^="hire "]').forEach((button) => { @@ -1412,10 +1377,29 @@ ${role.bullets.map((bullet) => `- ${escapeHtml(bullet)}`).join('\n')}`; } function initHireCommandCenter() { - document.querySelectorAll('[data-hire-role]').forEach((button) => { + const tabs = [...document.querySelectorAll('[data-hire-role]')]; + tabs.forEach((button, index) => { + button.tabIndex = button.getAttribute('aria-selected') === 'true' ? 0 : -1; button.addEventListener('click', () => { setHireRole(button.getAttribute('data-hire-role') || 'robotics'); }); + // Standard tablist keys: arrows move between roles, Home/End jump to the ends. + button.addEventListener('keydown', (event) => { + const offsets = { ArrowRight: 1, ArrowDown: 1, ArrowLeft: -1, ArrowUp: -1 }; + let next = null; + if (event.key in offsets) { + next = tabs[(index + offsets[event.key] + tabs.length) % tabs.length]; + } else if (event.key === 'Home') { + next = tabs[0]; + } else if (event.key === 'End') { + next = tabs[tabs.length - 1]; + } + if (next) { + event.preventDefault(); + next.focus(); + next.click(); + } + }); }); } @@ -1424,7 +1408,21 @@ ${role.bullets.map((bullet) => `- ${escapeHtml(bullet)}`).join('\n')}`; message || 'Old Clippy reporting for duty. I can route you to hire fit, projects, repos, CV, contact, reviews, or the goose.'; - pinClippyGuide(guideMessage); + if (realClippyLoaded || useTextHelper()) { + pinClippyGuide(guideMessage); + return; + } + + // First explicit request fetches the agent, but nobody waits more than a beat for a paperclip. + let shown = false; + const show = () => { + if (!shown) { + shown = true; + pinClippyGuide(guideMessage); + } + }; + initRealClippy().then(show); + window.setTimeout(show, 2500); } function getCommandResponse(rawCommand) { @@ -1466,7 +1464,7 @@ clear clear the terminal`; if (verb === 'projects') { scrollToSection('projects'); - return 'Jumping to featured projects. The short version: BGR, orbital LQI, repo summarizer, daily movers agent.'; + return 'Jumping to featured projects. The short version: UGV diagnostics, FPV visual geometry, BGR, orbital LQI, repo summarizer, daily movers agent, Fly101.'; } if (verb === 'hire' && args.length > 0) { @@ -1582,13 +1580,17 @@ This is opt-in. Public surface remains hireable. The terminal is where the eyebr } if (verb === 'goose' && ['terror', 'release', 'attack', 'chaos', 'loose'].includes(args[0])) { + const alreadyLoose = gooseTerrorState.active; const started = startGooseTerror(); - return started - ? `Goose terror mode released. + if (started) { + return `Goose terror mode released. It waddles, brings dumb notes, and leaves footprints like a tiny workplace liability. Tap the goose for another note. -Stop it with \`goose stop\`, Esc, or the on-screen banish button.` - : 'Goose terror mode is already active. This is not a second goose economy.'; +Stop it with \`goose stop\`, Esc, or the on-screen banish button.`; + } + return alreadyLoose + ? 'Goose terror mode is already active. This is not a second goose economy.' + : 'The goose looked for a clear patch of screen and found none. Give it a bigger window and try again.'; } if (verb === 'goose' && ['stop', 'banish', 'calm', 'enough', 'off'].includes(args[0])) { @@ -1681,12 +1683,17 @@ The flex is not perfection. The flex is range, follow-through, and enough taste return; } + if (palette.classList.contains('hidden')) { + commandPaletteOpener = document.activeElement; + } + palette.classList.remove('hidden'); document.querySelectorAll('[data-command-open]').forEach((button) => { button.setAttribute('aria-expanded', 'true'); }); - if (!commandHistory.length) { + if (!commandPaletteWelcomed) { + commandPaletteWelcomed = true; appendCommandOutput('welcome', 'Type `help`, `repos`, `projects`, `cv`, `contact`, or `goose`. This is a static GitHub Pages site, so the repo list comes from a baked-in snapshot.'); } @@ -1707,11 +1714,41 @@ The flex is not perfection. The flex is range, follow-through, and enough taste document.querySelectorAll('[data-command-open]').forEach((button) => { button.setAttribute('aria-expanded', 'false'); }); + + if (commandPaletteOpener?.isConnected && typeof commandPaletteOpener.focus === 'function') { + commandPaletteOpener.focus({ preventScroll: true }); + } + commandPaletteOpener = null; + } + + function trapFocus(container, event) { + if (event.key !== 'Tab') { + return; + } + + const focusable = [...container.querySelectorAll('a[href], button, input')] + .filter((element) => !element.disabled && element.getClientRects().length); + const first = focusable[0]; + const last = focusable[focusable.length - 1]; + if (!first) { + return; + } + + if (!container.contains(document.activeElement) || (event.shiftKey && document.activeElement === first)) { + event.preventDefault(); + (event.shiftKey ? last : first).focus(); + } else if (!event.shiftKey && document.activeElement === last) { + event.preventDefault(); + first.focus(); + } } function initCommandPalette() { const form = document.getElementById('command-form'); const input = document.getElementById('command-input'); + const palette = document.getElementById('command-palette'); + + palette?.addEventListener('keydown', (event) => trapFocus(palette, event)); document.querySelectorAll('[data-command-open]').forEach((button) => { button.addEventListener('click', () => openCommandPalette()); @@ -1745,7 +1782,7 @@ The flex is not perfection. The flex is range, follow-through, and enough taste const clippyCloseButton = document.getElementById('clippy-close-button'); const emailButton = document.getElementById('open-email-client-btn'); const gameRequestButton = document.querySelector('[data-game-request]'); - const copyButtons = [document.getElementById('copy-email-address'), document.getElementById('quick-copy-email')]; + const copyButtons = [document.getElementById('copy-email-address')]; clippyCloseButton?.addEventListener('click', () => hideClippy(true)); emailButton?.addEventListener('click', openEmailClient); @@ -1811,28 +1848,55 @@ The flex is not perfection. The flex is range, follow-through, and enough taste } function buildModalShell(icon, title, bodyHtml) { + const opener = document.activeElement; + const titleId = `portfolio-modal-title-${++modalCount}`; const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; + modal.setAttribute('role', 'dialog'); + modal.setAttribute('aria-modal', 'true'); + modal.setAttribute('aria-labelledby', titleId); modal.innerHTML = `
${icon}
-

${title}

+

${title}

${bodyHtml}
`; + const onKeydown = (event) => { + if (event.key === 'Escape') { + modal.closeModal(); + } else { + trapFocus(modal, event); + } + }; + + modal.closeModal = () => { + modal.remove(); + document.removeEventListener('keydown', onKeydown); + if (opener?.isConnected && typeof opener.focus === 'function') { + opener.focus({ preventScroll: true }); + } + }; + modal.addEventListener('click', (event) => { if (event.target === modal) { - modal.remove(); + modal.closeModal(); } }); + document.addEventListener('keydown', onKeydown); return modal; } + function showModal(modal) { + document.body.appendChild(modal); + modal.querySelector('[data-modal-actions] a, [data-modal-actions] button')?.focus({ preventScroll: true }); + } + function appendModalButton(container, label, className, onClick, href) { if (href) { const link = document.createElement('a'); @@ -1862,8 +1926,8 @@ The flex is not perfection. The flex is range, follow-through, and enough taste appendModalButton( actions, 'Got It', - 'w-full bg-gradient-to-r from-tech-cyan to-tech-green text-white font-bold py-3 px-6 rounded-lg', - () => modal.remove() + 'w-full bg-gradient-to-r from-tech-cyan to-tech-green text-tech-dark font-bold py-3 px-6 rounded-lg', + () => modal.closeModal() ); const note = document.createElement('div'); @@ -1871,7 +1935,7 @@ The flex is not perfection. The flex is range, follow-through, and enough taste note.textContent = 'Use external game pages responsibly. Verify availability and licensing before downloading anything.'; actions.appendChild(note); - document.body.appendChild(modal); + showModal(modal); } function showRetroGameRequest() { @@ -1885,7 +1949,7 @@ The flex is not perfection. The flex is range, follow-through, and enough taste appendModalButton( actions, 'Send Request', - 'block w-full bg-gradient-to-r from-tech-purple to-tech-pink text-white font-bold py-3 px-6 rounded-lg text-center', + 'block w-full bg-gradient-to-r from-tech-purple to-tech-pink text-tech-dark font-bold py-3 px-6 rounded-lg text-center', null, 'mailto:yuval.grimberg@gmail.com?subject=Retro Classics Nomination&body=Hi Yuval! I would nominate this retro game for the classics shelf:%0D%0A%0D%0ATitle:%0D%0ASource page:%0D%0AWhy it belongs:' ); @@ -1894,10 +1958,10 @@ The flex is not perfection. The flex is range, follow-through, and enough taste actions, 'Maybe Later', 'w-full bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-6 rounded-lg', - () => modal.remove() + () => modal.closeModal() ); - document.body.appendChild(modal); + showModal(modal); } function openGameSource(gameId) { @@ -1958,14 +2022,12 @@ The flex is not perfection. The flex is range, follow-through, and enough taste } function initPage() { - initAos(); - initRealClippy(); + scheduleRealClippy(); loadRepoSnapshot(); initProjectCardObserver(); initFunZone(); initGooseAnimation(); initHireCommandCenter(); - initCvGate(); initCommandPalette(); initGuideButtons(); initKeyboardEasterEggs(); diff --git a/assets/js/lab-experiments.js b/assets/js/lab-experiments.js index 9843487e77..87f8d8e019 100644 --- a/assets/js/lab-experiments.js +++ b/assets/js/lab-experiments.js @@ -52,7 +52,7 @@ function replaceLine(object, positions) { } function label(text,position,color='#9eafab') { const c=document.createElement('canvas'); c.width=512;c.height=64; - const ctx=c.getContext('2d');ctx.font='28px Segoe UI';ctx.fillStyle=color;ctx.fillText(text,8,42); + const ctx=c.getContext('2d');ctx.font='28px "Segoe UI", system-ui, sans-serif';ctx.fillStyle=color;ctx.fillText(text,8,42); const sprite=new THREE.Sprite(new THREE.SpriteMaterial({map:new THREE.CanvasTexture(c),depthTest:false,transparent:true})); sprite.position.copy(position);sprite.scale.set(5,.625,1);scene.add(sprite); } diff --git a/assets/js/portfolio-labs.js b/assets/js/portfolio-labs.js index 993c4f3431..8bd35551e1 100644 --- a/assets/js/portfolio-labs.js +++ b/assets/js/portfolio-labs.js @@ -2,6 +2,7 @@ const driving = document.getElementById('path-playground'); const experiments = document.getElementById('experiment-playground'); const reconstruction = document.getElementById('reconstruction-lab'); +const brainLab = document.getElementById('brain-lab'); const frameSlot = document.getElementById('reconstruction-frame-slot'); const loadViewer = document.getElementById('reconstruction-load'); const closeViewer = document.getElementById('reconstruction-close'); @@ -38,24 +39,59 @@ closeViewer?.addEventListener('click', () => { document.addEventListener('portfolio:lab-visibility', () => { document.body.classList.toggle('path-scene-near', labReading.size > 0 || [driving, experiments].some(panel => panel && !panel.hidden && panel.dataset.nearViewport === 'true')); }); +// The connectome data and three.js load only when someone opens this lab. +let brainRequested = false; +function openBrainLab() { + if (brainRequested) return; + brainRequested = true; + const status = document.getElementById('brain-lab-status'); + if (!('WebGL2RenderingContext' in window)) { + status.textContent = 'This view needs WebGL. The picture shows the same neurons.'; + return; + } + import('./fly-brain.js?v=20261106b') + .then(({ mountBrain }) => mountBrain({ + stage: document.getElementById('brain-lab-stage'), + canvas: document.getElementById('brain-lab-canvas'), + framing: 'panel' + })) + .then((brain) => { + status.textContent = 'Drag to rotate. Switch a region off to look inside.'; + document.querySelectorAll('[data-brain-region]').forEach((button) => { + button.disabled = false; + button.addEventListener('click', () => { + const visible = button.getAttribute('aria-pressed') !== 'true'; + button.setAttribute('aria-pressed', String(visible)); + button.classList.toggle('is-active', visible); + brain.setRegion(Number(button.dataset.brainRegion), visible); + }); + }); + }) + .catch(() => { + brainRequested = false; + status.textContent = 'The neuron data did not load. Reopen this lab to try again.'; + }); +} function selectLab(mode, updateHash = false) { - if (!['drive','space','perception','reconstruction'].includes(mode)) return; + if (!['drive','space','perception','reconstruction','brain'].includes(mode)) return; if (mode !== 'reconstruction') unloadReconstruction(); if (driving) driving.hidden = mode !== 'drive'; if (experiments) experiments.hidden = !['space','perception'].includes(mode); if (reconstruction) reconstruction.hidden = mode !== 'reconstruction'; + if (brainLab) brainLab.hidden = mode !== 'brain'; + if (mode === 'brain') openBrainLab(); document.querySelectorAll('[data-lab-mode]').forEach(button => { button.setAttribute('aria-pressed', String(button.dataset.labMode === mode)); }); document.querySelectorAll('[data-experiment-copy]').forEach(el => el.hidden = el.dataset.experimentCopy !== mode); document.body.dataset.activeLab = mode; document.dispatchEvent(new CustomEvent('portfolio:lab-change', { detail: mode })); - if (updateHash) history.replaceState(null, '', '#' + ({drive:'path-playground',space:'space-lab',perception:'perception-lab',reconstruction:'reconstruction-lab'}[mode])); + if (updateHash) history.replaceState(null, '', '#' + ({drive:'path-playground',space:'space-lab',perception:'perception-lab',reconstruction:'reconstruction-lab',brain:'brain-lab'}[mode])); } document.querySelectorAll('[data-lab-mode]').forEach(button => button.addEventListener('click', () => selectLab(button.dataset.labMode, true))); document.querySelectorAll('[data-lab-target]').forEach(link => link.addEventListener('click', () => selectLab(link.dataset.labTarget, true), {capture:true})); function readHash() { - const mode = {'#path-playground':'drive','#space-lab':'space','#perception-lab':'perception','#reconstruction-lab':'reconstruction'}[location.hash]; + const mode = {'#path-playground':'drive','#space-lab':'space','#perception-lab':'perception','#reconstruction-lab':'reconstruction','#brain-lab':'brain'}[location.hash]; if (mode) { selectLab(mode); requestAnimationFrame(() => document.getElementById('lab-picker')?.scrollIntoView({block:'start',behavior:'instant'})); } } window.addEventListener('hashchange', readHash); @@ -83,6 +119,23 @@ const readingObserver = new IntersectionObserver(entries => { document.dispatchEvent(new Event('portfolio:lab-visibility')); }, {rootMargin:'-80px 0px -35% 0px'}); document.querySelectorAll('#hero, #about, #contact, #lab-picker, .path-playground').forEach(el => readingObserver.observe(el)); + +// three.js is ~750 KB, so the scenes load when a lab gets close or the page goes idle, never before first paint. +let labScenesRequested = false; +function loadLabScenes() { + if (labScenesRequested) return; + labScenesRequested = true; + labLoadObserver.disconnect(); + Promise.all([import('./path-playground.js?v=20261102'), import('./lab-experiments.js?v=20261102')]) + .catch(() => document.querySelectorAll('#path-status, #experiment-status').forEach(el => { el.textContent = 'Lab failed to load. Refresh to try again.'; })); +} +const labLoadObserver = new IntersectionObserver(entries => { + if (entries.some(entry => entry.isIntersecting)) loadLabScenes(); +}, {rootMargin:'1200px 0px'}); +document.querySelectorAll('#lab-picker, .path-playground').forEach(el => labLoadObserver.observe(el)); +const scheduleIdleLoad = () => ('requestIdleCallback' in window ? requestIdleCallback(loadLabScenes, {timeout:4000}) : setTimeout(loadLabScenes, 2500)); +if (document.readyState === 'complete') scheduleIdleLoad(); +else window.addEventListener('load', scheduleIdleLoad, {once:true}); document.addEventListener('visibilitychange', () => { if (document.hidden) unloadReconstruction('Viewer closed while this tab was hidden. Load it again to continue.'); }); diff --git a/assets/js/scripts.js b/assets/js/scripts.js deleted file mode 100644 index bf135cc215..0000000000 --- a/assets/js/scripts.js +++ /dev/null @@ -1,92 +0,0 @@ -document.addEventListener('DOMContentLoaded', function () { - const LEGACY_GLOBAL_TIMEOUT_MS = 3000; - const LEGACY_GLOBAL_INTERVAL_MS = 100; - - function initSmoothAnchors() { - document.querySelectorAll('a[href^="#"]:not([href="#"])').forEach((anchor) => { - if (anchor.dataset.anchorReady === 'true') { - return; - } - - anchor.addEventListener('click', function (event) { - const target = document.querySelector(this.getAttribute('href')); - if (!target) { - return; - } - - event.preventDefault(); - target.scrollIntoView({ behavior: 'smooth', block: 'start' }); - }); - - anchor.dataset.anchorReady = 'true'; - }); - } - - function initBackToTopButton() { - if (window.backToTopButton) { - return; - } - - const backToTopButton = document.createElement('button'); - backToTopButton.type = 'button'; - backToTopButton.textContent = '\u2191'; - backToTopButton.className = 'fixed bottom-4 right-4 bg-blue-500 text-white p-2 rounded-full hidden'; - backToTopButton.setAttribute('aria-label', 'Back to top'); - document.body.appendChild(backToTopButton); - - function syncVisibility() { - if (window.scrollY > 300) { - backToTopButton.classList.remove('hidden'); - } else { - backToTopButton.classList.add('hidden'); - } - } - - window.addEventListener('scroll', syncVisibility, { passive: true }); - backToTopButton.addEventListener('click', () => { - window.scrollTo({ top: 0, behavior: 'smooth' }); - }); - - syncVisibility(); - window.backToTopButton = backToTopButton; - } - - function waitForGlobal(name, onReady) { - const deadline = Date.now() + LEGACY_GLOBAL_TIMEOUT_MS; - - function check() { - if (window[name]) { - onReady(window[name]); - return; - } - - if (Date.now() >= deadline) { - console.info(`Legacy helper timed out waiting for ${name}.`); - return; - } - - window.setTimeout(check, LEGACY_GLOBAL_INTERVAL_MS); - } - - check(); - } - - initSmoothAnchors(); - initBackToTopButton(); - - if (document.querySelector('[data-legacy-clippy-shortcut]')) { - waitForGlobal('clippy', function (clippy) { - clippy.load('Clippy', function (agent) { - agent.hide(); - - document.addEventListener('keydown', function (event) { - if (event.altKey && event.shiftKey && event.code === 'KeyC') { - agent.show(); - agent.moveTo(100, 100); - agent.speak('Legacy helper ready.'); - } - }); - }); - }); - } -}); diff --git a/assets/js/site-shell.js b/assets/js/site-shell.js index 49d74f0964..3dc51830ca 100644 --- a/assets/js/site-shell.js +++ b/assets/js/site-shell.js @@ -1,5 +1,6 @@ (function () { - const NAV_ACTIVE_BACKGROUND = 'rgba(10, 15, 28, 0.8)'; + const NAV_ACTIVE_BACKGROUND = 'rgba(7, 10, 13, 0.94)'; + const reducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); function initNavbarBackground() { const navbar = document.getElementById('navbar'); @@ -36,15 +37,30 @@ event.preventDefault(); target.scrollIntoView({ - behavior: 'smooth', + behavior: reducedMotion.matches ? 'auto' : 'smooth', block: 'start' }); + // Keep the URL shareable and move keyboard focus with the scroll, so skip links actually skip. + if (!anchor.hasAttribute('data-lab-target')) { + history.pushState(null, '', anchor.getAttribute('href')); + } + if (!target.matches('a[href], button, input, select, textarea, [tabindex]')) { + target.setAttribute('tabindex', '-1'); + } + target.focus({ preventScroll: true }); }); anchor.dataset.anchorReady = 'true'; }); } + function initCurrentYear() { + const year = String(new Date().getFullYear()); + document.querySelectorAll('[data-current-year]').forEach((element) => { + element.textContent = year; + }); + } + function cloneDesktopLinks(mobileNavList, desktopNav) { if (!mobileNavList || !desktopNav || mobileNavList.children.length > 0) { return; @@ -186,5 +202,6 @@ initNavbarBackground(); initMobileMenu(); initSmoothAnchors(); + initCurrentYear(); }); })(); diff --git a/cv.html b/cv.html index c7d0e2a813..fc1b06abcf 100644 --- a/cv.html +++ b/cv.html @@ -13,6 +13,21 @@ + + + + + + + + + + + + + + + diff --git a/docs/site/SETUP_INSTRUCTIONS.md b/docs/site/SETUP_INSTRUCTIONS.md index c6d38673e3..1c9a5cc170 100644 --- a/docs/site/SETUP_INSTRUCTIONS.md +++ b/docs/site/SETUP_INSTRUCTIONS.md @@ -2,7 +2,7 @@ ## Purpose -This repo is a root-static GitHub Pages site. The maintained user-facing flow lives in `index.html`, `photo.html`, `thank-you.html`, and `cv.html`. +This repo is a root-static GitHub Pages site. The maintained user-facing flow lives in `index.html`, `photo.html`, and `cv.html`, plus `404.html` for missing routes. ## Quick Start @@ -22,7 +22,7 @@ npm run serve - [http://127.0.0.1:8000/](http://127.0.0.1:8000/) - [http://127.0.0.1:8000/photo.html](http://127.0.0.1:8000/photo.html) -- [http://127.0.0.1:8000/thank-you.html](http://127.0.0.1:8000/thank-you.html) +- [http://127.0.0.1:8000/cv.html](http://127.0.0.1:8000/cv.html) ## Optional CSS Build @@ -36,11 +36,14 @@ Use this when you change Tailwind classes, theme tokens, or shared site styles. ```bash npm run lint +npm run test:models npm test ``` - `npm run lint` runs the repo-specific static and runtime hygiene checks. +- `npm run test:models` checks the lab simulation models in plain Node. - `npm test` runs the maintained Playwright smoke matrix aimed at the current live pages, not a broad cross-device lab. +- `.github/workflows/site-checks.yml` runs all of the above, plus a check that `output.css` matches `styles.css`, on pushes to `dev` and on pull requests. ## Branching @@ -49,4 +52,4 @@ npm test ## Legacy Pages -`about.html`, `projects.html`, and `vision.html` remain in the repo for compatibility and reference, but they are not the primary maintained surfaces. Local harness pages live under `tests/fixtures/`. +`about.html`, `projects.html`, `vision.html`, and `thank-you.html` are tiny `noindex` redirect pages into the matching homepage section, so old links still land somewhere useful. Local harness pages live under `tests/fixtures/`. diff --git a/favicon.svg b/favicon.svg index 091e5424bf..1b256f6930 100644 --- a/favicon.svg +++ b/favicon.svg @@ -1,5 +1,5 @@ - - + + diff --git a/fly101.html b/fly101.html new file mode 100644 index 0000000000..893909b5a6 --- /dev/null +++ b/fly101.html @@ -0,0 +1,196 @@ + + + + + + + Fly101: a fruit-fly brain wired to a robot arm | Yuval Grimberg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ + +
+

Fly101 · research in simulation

+

A fruit-fly brain, wired to a robot arm.

+

The complete MaleCNS connectome, simulated neuron by neuron, follows a live human hand through a simulated SO-101 arm.

+
+

Real neuron positions from the connectome. Drag to rotate.

+
+ +
+
+
neurons, wired as mapped
165,122
+
closed-loop step
30 ms
+
median live tracking error
17.8 mm
+
synapses changed
0
+
+ +
+
+

The idea

+

MaleCNS is a complete electron-microscopy map of a male fruit fly’s central nervous system: 165,122 neurons and 25.6 million connections. Fly101 runs every neuron as a spiking model on one laptop GPU, in a live loop, wired exactly as in the data.

+

Nothing inside the brain is trained. Only a small linear readout learns, turning the activity of 250 neurons into commands for the arm.

+
+
+ The fruit-fly brain and ventral nerve cord, drawn from neuron positions. +
The whole nervous system, drawn only from neuron positions. Optic lobes in brass, central brain in teal, nerve cord in grey.
+
+
+ +
+

The loop

+
    +
  1. A webcam tracks your hand and sets a target pose for the gripper.
  2. +
  3. The error reaches the fly’s eye as firing across 5,307 first-stage visual neurons.
  4. +
  5. The whole brain responds for 30 ms of simulated time.
  6. +
  7. A learned readout turns the activity into a motion command.
  8. +
  9. The arm moves in MuJoCo, under speed limits, and the loop repeats.
  10. +
+
+
+ Rendered model of a fruit fly with red eyes and spread wings. +
The fly: the flybody model in MuJoCo.
+
+
+ Rendered SO-101 robot arm reaching forward. +
The arm: a simulated SO-101.
+
+
+
+ +
+

Results

+
+
Live human hand, 104 s
17.8 mm median
+
Held-out recorded hand motion
20.05 mm median
+
Gripper accuracy
100 %
+
Live rate
32 updates/s
+
Joint-speed or acceleration violations
0
+
+

Measured in simulation. The real SO-101 arms are calibrated and teleoperated, and hardware work is under way.

+
+ +
+ Technical notes: full results, baselines and open questions +
+

Status on 4 November 2026. No brain has driven the real arm yet, and these experiments are ongoing.

+
+

Full test results

+
+ + + + + + + + +
TestResultBar
Held-out hand motion20.05 mm≤ 25 mm
Same motion at 1.8× speed43.2 mm≤ 45 mm
Brain replaced by its average activity177 mm> 50 mm
Live, 104 s (teacher on the same motion: 19.0 mm)17.8 mmNone
+
+

These use an ideal simulated servo. With a 12 V motor model, median tracking rises to about 22–30 mm, 37–49 % worse. The readout is trained with ridge regression and DAgger, with a hand-written controller as the teacher.

+
+
+

Where the signal lives

+

I measured how well groups of 250 neurons predict the teacher’s commands (held-out R²), by their synaptic distance from the input. The whole brain sits within five synapses of the input, but the signal doesn’t get that far:

+
+ + + + + + + + + + +
GroupMost informative 250Random 250
1 synapse from input0.7760.546
2 synapses0.6440.260
3 synapses0.0520.017
4 synapses≈ 0≈ 0
Medulla (visual)0.7870.523
Mushroom body, protocerebrum, GNG, VNC≈ 0≈ 0
+
+

With this input, the fly’s early visual system is the signal path: 240 of the best readout’s 250 neurons are in the medulla. The central brain and the descending neurons, which normally drive the fly’s body, carry almost none of it.

+
+
+

The wire test

+

To steer an arm, a controller has to combine the error with the arm’s current posture: an error × posture product. So I gave the brain both signals and asked whether its descending neurons could produce the required joint rates. Then I compared them with a “wire”, a readout that skips the brain entirely.

+
+ + + + + + + +
ReadoutJoint-rate R² (held out)
Brain, descending neurons0.34
Linear wire, raw inputs, no brain0.65
Bilinear wire, inputs plus error × angle products0.92
+
+

The descending neurons do carry the errors (R² 0.67) and the joint angles (0.79), but not their product. A follow-up read from all 12,847 recorded neurons reached 0.54, and still added nothing beyond the linear wire. By the rule I had set before running it, that stage stopped there.

+

The later experiments point the same way:

+
    +
  • Passing the bars isn’t proof. A linear wire with the motor model also tracks reasonably (32.8 mm), so tracking alone can’t show the brain does the inverse kinematics.
  • +
  • A harder test separates controllers. On pan-heavy poses, linear controllers fail 59–72 % of the cases the teacher holds, and nonlinear ones about 13 %. The fixed brain with a linear readout fails like the linear wires.
  • +
  • Real wiring versus rewired brains. The real graph first beat rewired controls on one joint, but on fresh targets the effect shrank and the winning joint changed. Silencing the candidate neurons did not remove what was left, so the preregistered reading is “not causal”.
  • +
  • Neuron model. A conductance-synapse variant showed no useful gain (the test was inconclusive), and voltage floors ruled out one artefact explanation for the remaining pan interaction.
  • +
+

Each stage has its success bar, stop rule and compute budget fixed before the run, which is what makes a “no” worth trusting.

+
+
+

What comes next

+
    +
  • Path to the real arm. The brain drives a task-space readout, fixed analytical inverse kinematics maps it to joints, and LeRobot drives the hardware. The loop already runs through LeRobot’s simulated SO-101 interface; fitting the motor model to the real arm comes next.
  • +
  • Can the brain form the product itself? The open research question is whether any input encoding gets a fixed connectome to compute error × posture, and whether the real wiring is what does it.
  • +
+
+
+
+ +
+

Sources and credits

+
    +
  • MaleCNS v1.0 ↗ connectome (CC-BY), with regions from neuPrint ↗. The brain images are my renders of its neuron positions.
  • +
  • flybody ↗ fly model by Google DeepMind and HHMI Janelia (Apache-2.0).
  • +
  • SO-ARM100 ↗ robot model (Apache-2.0), with kinematics from LeRobot (Apache-2.0).
  • +
  • flycoinrh ↗ neural reference (MIT). GeNN, MuJoCo and MediaPipe run the simulation, physics and hand tracking.
  • +
+

The repository is private for now. Email me for a walkthrough.

+
+
+
+ + + + diff --git a/index.html b/index.html index 941aa77427..bb3d18103a 100644 --- a/index.html +++ b/index.html @@ -15,15 +15,56 @@ + - - + + + + - - - + + + + + + + + + + + + @@ -38,14 +79,17 @@ bottom: 14rem; z-index: 1000; opacity: 0; + visibility: hidden; transform: translateY(20px); - transition: all 0.3s ease; + transition: opacity 0.3s ease, transform 0.3s ease, visibility 0s linear 0.3s; pointer-events: none; } #custom-clippy.show { opacity: 1; + visibility: visible; transform: translateY(0); + transition: opacity 0.3s ease, transform 0.3s ease, visibility 0s; pointer-events: auto; } @@ -304,7 +348,7 @@

Ask the site what matters

- © 2025 Yuval Grimberg + © 2026 Yuval Grimberg

@@ -328,7 +372,7 @@

Grimberg

- I build Python software for autonomous systems, computer vision, and applied AI. + I build Python software for autonomous systems: planning and control, diagnostics, computer vision and applied AI.

@@ -341,14 +385,14 @@

- Open to software development and R&D roles. Based in Israel, open to remote or relocation. + Open to R&D and software roles in autonomy, robotics and applied AI. Tel Aviv, open to remote or relocation.

+ class="group inline-flex justify-center items-center gap-3 bg-tech-purple hover:bg-tech-cyan text-tech-dark font-bold py-4 px-7 rounded-full transition-all duration-300 transform hover:scale-105 shadow-2xl"> See the work @@ -377,11 +421,10 @@

Hire Me Command Center

- Pick the mission. I will show the proof. + Pick the mission. I will show the proof.

- Recruiters should not need a treasure map. Choose the role shape and the site will line up - projects, skills, and the honest reason I fit. + Recruiters shouldn’t need a treasure map. Pick the role you’re hiring for and I’ll line up the projects, the tools and the honest reason I fit.

    -
  • Led path planning and control work for a real Formula Student driverless effort.
  • -
  • Comfortable translating messy system behavior into debuggable software loops.
  • +
  • Built shared Python backend and operator interfaces for a UGV: command handling, telemetry, diagnostics, watchdogs and heartbeats (full-time, July–September 2026).
  • +
  • Led five student developers on BGR Formula Student planning and control, 2023 to late 2025; our car drove about 50 metres autonomously.
  • Useful when the job needs range, not a one-trick framework certificate.

@@ -444,8 +487,8 @@

R&D Sof

About me

I’m Yuval, a software developer with a mechanical engineering degree. I work on algorithms, Python systems and applied AI.

-

From July to September 2026, I worked full-time on a UGV defense project. I developed shared Python backend and operator interfaces for command handling, telemetry and diagnostics, with logging across four components, thread-timing diagnostics, watchdogs and heartbeat handling.

-

At BGR Formula Student, I led planning and control from 2023 to late 2025, sharing implementation with the team. We achieved approximately 50 metres of physical autonomous driving using ego-centric navigation without a functioning IMU.

+

From July to September 2026 I worked full-time on a UGV defense project. I built a shared Python backend and operator interfaces for command handling, telemetry and diagnostics, then made failures traceable: logging across four components, thread-timing diagnostics, watchdogs and heartbeat handling.

+

At BGR Formula Student I led the five-person planning and control group from 2023 to late 2025 and wrote code alongside them. Our car drove about 50 metres autonomously, navigating ego-centrically without a working IMU.

I like turning an unclear problem into a model I can inspect, a first version I can run, and an experiment that tells me what to change.

Working with
Python, ROS 2, FastAPI, MATLAB; basic C++
@@ -462,7 +505,8 @@

About me

-
+
+

toolbox

Technical Expertise

@@ -471,64 +515,60 @@

-
- -
-
⚙️
-

Control Systems

-
    -
  • • Pure Pursuit
  • -
  • • Stanley steering
  • -
  • • Curvature-based PID
  • -
  • • Vehicle dynamics
  • +
    +
    +
    01
    +

    Control systems

    +
      +
    • Pure Pursuit
    • +
    • Stanley steering
    • +
    • Curvature-based PID
    • +
    • Vehicle dynamics
    -
    +

    Proof: BGR stack, orbital LQI

    + - -
    -
    🚗
    -

    Autonomous Vehicles

    -
      -
    • • Path Planning
    • -
    • • Ego-centric navigation
    • -
    • • Simulation-driven tests
    • -
    • • ROS 2 diagnostics
    • +
      +
      02
      +

      Autonomous vehicles

      +
        +
      • Path planning
      • +
      • Ego-centric navigation
      • +
      • Simulation-driven tests
      • +
      • ROS 2 diagnostics
      -
    +

    Proof: BGR Formula Student, UGV work

    + - -
    -
    💻
    -

    Programming

    -
      -
    • • Python
    • -
    • • MATLAB/Simulink
    • -
    • • C++ (basic)
    • -
    • • ROS2
    • +
      +
      03
      +

      Programming

      +
        +
      • Python
      • +
      • MATLAB / Simulink
      • +
      • C++ (basic)
      • +
      • ROS 2
      -
    +

    Proof: repo summarizer ↗

    + - -
    -
    🧠
    -

    Machine Learning

    -
      -
    • • VGGT / 3D reconstruction
    • -
    • • Computer vision experiments
    • -
    • • LangGraph / LLM APIs
    • -
    • • Structured outputs / fallbacks
    • +
      +
      04
      +

      Perception & applied AI

      +
        +
      • VGGT / 3D reconstruction
      • +
      • Computer vision experiments
      • +
      • LangGraph / LLM APIs
      • +
      • Structured outputs / fallbacks
      -
    +

    Proof: FPV lab, movers agent ↗

    +
-
-

Tools & Technologies

-
+
+

Also in the toolbox

+
OpenCV YOLO Stable @@ -555,27 +595,28 @@

Featured Projects

- Projects that show the useful part: math, systems, code, and enough polish to inspect without a - treasure map. + Each one shows the useful part: the math, the system, the code, and enough polish that you can inspect it yourself.

-
-
software development
🛠️
+
+
software development
+ Schematic: four software components on a shared bus with a heartbeat trace and a watchdog timeout marker

UGV Software & Diagnostics

Make commands, state and failures traceable across a robot’s software.

-

Full-time, July–September 2026. Developed a shared Python backend and operator interfaces for command handling, telemetry, and diagnostics. Added logging across four components, thread-timing diagnostics, watchdogs, and heartbeat handling.

+

Full-time, July–September 2026. I built a shared Python backend and operator interfaces for command handling, telemetry and diagnostics, then added logging across four components, thread-timing diagnostics, watchdogs and heartbeat handling.

Read my contribution ↗

Private defense project. Public description only.

-
-
computer vision
📷
+
+
computer vision
+ Schematic: camera frustums along an FPV flight path above a sparse reconstructed point cloud

FPV Visual Geometry Lab

@@ -590,9 +631,9 @@

FPV Visual Geometry Lab

-
+
autonomy
-
🏎️
+ Schematic: planned path versus driven trail between blue and yellow cones, with a pure-pursuit lookahead arc
@@ -600,7 +641,7 @@

BGR Path Planning & Control

FEATURED

A driverless race car needs a path it can actually follow.

-

I led the student planning and control team from 2023 to late 2025, sharing implementation of an adapted planner, Pure Pursuit, Stanley steering and curvature-based PID speed control. Our physical car drove approximately 50 metres autonomously; testing also included simulation-driven physical actuators.

+

I led five student developers from 2023 to late 2025 and co-wrote the stack: an adapted cone planner, Pure Pursuit and Stanley steering, and curvature-based PID speed control. We tested in simulation and on physical steering and wheel actuators; the car drove about 50 metres autonomously.

BGR Path Planning & Control
-
+
control
-
🛰️
+ Schematic: a chaser spiralling toward its target in the LVLH frame with bounded thrust arrows and a keep-out circle

Orbital Rendezvous LQI

Bring a chaser to its target while respecting limited thrust.

-

My orbital mechanics study pairs two-body propagation with CW rendezvous control, cached LQI and a constrained MPC comparison. A Streamlit viewer and reproducible plots make the trajectories inspectable.

+

Two-body propagation, Clohessy–Wiltshire rendezvous dynamics, and an LQI controller with Bayesian-optimised cost weights, compared against constrained MPC. A Streamlit viewer and reproducible plots make every trajectory inspectable.

Orbital Rendezvous LQI
-
+
tooling
-
🧰
+ Schematic: a file tree feeding deterministic selection, shard bins and a JSON output, with a dashed provider fallback route

GitHub Repo Summarizer

Understand an unfamiliar repository without opening every file.

-

I built a FastAPI and CLI summarizer with deterministic file selection, adaptive sharding and strict JSON output. Provider fallback keeps failure handling part of the pipeline.

+

A FastAPI service and CLI with deterministic file selection, adaptive sharding and validated JSON output. Provider fallbacks and mocked API tests make failure handling part of the design, not an afterthought.

@@ -694,6 +754,7 @@

Take the controls

+
From frames to a scene.
+

lab layer

There is more in the terminal.

@@ -851,10 +932,10 @@

There is more in the terminal.

-
@@ -964,13 +1045,12 @@

+ class="glass-effect rounded-2xl p-8 hover-lift">
📸

Travel Photography

A collection of moments captured from travels near and far. This - is the world as I see it - .

+ is the world as I see it.

Travel photo gallery preview 1Travel Photography

+ class="glass-effect rounded-2xl p-8 text-center hover-lift">
🦆

Meet Desk Goose

Helpful guide first, dumb joyful desk menace second. He is not @@ -1011,7 +1091,7 @@

Meet Desk Goose

@@ -1094,43 +1174,20 @@

Retro Classics

- Six old-school games that still explain timing, constraints, flow, and why children in the 90s - developed suspiciously strong patience. No hosted binaries; only source pages. + Games I would actually defend in a meeting. Six old-school classics that still explain timing, + constraints, flow, and why children in the 90s developed suspiciously strong patience.

-
-
- 6 - Curated Classics -
-
- 3 - Protected Favorites -
-
- 0 - Source Opens -
-
- 0 - Hosted Game Files -
-
- -
-
-

one shelf, no landfill

-

Games I would actually defend in a meeting

-
-

Fast to understand, mechanically sharp, and still useful as tiny design lessons. Also: legally safer than hosting mystery ZIPs.

-
- +
@@ -1373,20 +1427,15 @@

Specializations

- © 2025 Yuval Grimberg. All rights reserved. | - Ready to innovate together? + © 2026 Yuval Grimberg. Static site, real projects, one goose.

- - - - - - - + + + diff --git a/package.json b/package.json index 037d10e16e..effb46556c 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "grimgrimberg.github.io", "version": "1.0.0", - "description": "", + "description": "Static GitHub Pages portfolio for Yuval Grimberg.", "scripts": { "test": "playwright test", "test:headed": "playwright test --headed", + "test:models": "node tests/lab-models.mjs", "lint": "npm run lint:static && npm run lint:runtime", "lint:static": "node scripts/lint-site.mjs", "lint:runtime": "playwright test --config=playwright.hygiene.config.js", diff --git a/photo.html b/photo.html index 7a844df17a..fac0996aa0 100644 --- a/photo.html +++ b/photo.html @@ -467,7 +467,7 @@

Engineering Meets Art

class="footer-social-link touch-target text-gray-400 hover:text-tech-cyan transition-colors"> - @@ -504,7 +504,7 @@

Photography

- © 2025 Yuval Grimberg. All rights reserved. | + © 2026 Yuval Grimberg. All rights reserved. | Capturing the world through an engineer's lens

@@ -512,7 +512,7 @@

Photography

- + diff --git a/projects.html b/projects.html index 0051a51ca3..6c00853954 100644 --- a/projects.html +++ b/projects.html @@ -1,77 +1,22 @@ + - Projects | Yuval Grimberg - + Projects moved | Yuval Grimberg + + + + + - -
- -
-
-

- Legacy page: this standalone route is kept for compatibility. Start from - the homepage - for the maintained experience. -

-
- -
-
-

My Projects

-

A showcase of my work in mechanical engineering, control systems, and autonomous vehicles.

- -
-
-

Autonomous Vehicle Control System

-

Developed a cutting-edge control system for autonomous vehicles, ensuring safety and efficiency in various driving conditions.

-
- Control Theory - Robotics -
-
- -
-

Advanced Robotics Simulation

-

Created a comprehensive simulation environment for testing and optimizing robotic algorithms and control systems.

-
- Simulation - Python -
-
- -
-

Machine Learning for Predictive Maintenance

-

Implemented machine learning models to predict and prevent equipment failures in industrial settings.

-
- Machine Learning - Data Analysis -
-
-
-
-
+ +
+

This page moved. Go to the current projects.

+
+ -
-
-

© 2025 Yuval. All rights reserved.

-
- GH - in -
-
-
- - diff --git a/robots.txt b/robots.txt new file mode 100644 index 0000000000..91d7b075f2 --- /dev/null +++ b/robots.txt @@ -0,0 +1,7 @@ +User-agent: * +Allow: / +Disallow: /docs/ +Disallow: /tests/ +Disallow: /scripts/ + +Sitemap: https://grimgrimberg.github.io/sitemap.xml diff --git a/scripts/lint-site.mjs b/scripts/lint-site.mjs index dcb6faaf07..c29254d174 100644 --- a/scripts/lint-site.mjs +++ b/scripts/lint-site.mjs @@ -13,7 +13,8 @@ const fixtureHtmlPages = existsSync(fixtureHtmlDirectory) .sort() : []; const checkedHtmlPages = [...rootHtmlPages, ...fixtureHtmlPages]; -const maintainedPages = ['index.html', 'photo.html', 'thank-you.html', 'cv.html']; +const maintainedPages = ['index.html', 'photo.html', 'cv.html', 'fly101.html']; +const legacyRedirectPages = ['about.html', 'projects.html', 'vision.html', 'thank-you.html']; const failures = []; function read(filePath) { @@ -96,6 +97,14 @@ for (const filePath of maintainedPages) { } } +for (const filePath of legacyRedirectPages) { + const normalized = normalize(read(filePath)); + requireMatch(filePath, normalized, /]+http-equiv=["']refresh["'][^>]+url=index\.html#/i, 'legacy page should redirect into index.html'); + requireMatch(filePath, normalized, /]+name=["']robots["'][^>]+noindex/i, 'legacy redirect should be noindex'); +} + +requireMatch('404.html', normalize(read('404.html')), /href=["']\/assets\/css\/output\.css["']/i, '404 page must use root-absolute asset paths'); + for (const filePath of ['index.html', 'photo.html']) { const normalized = normalize(read(filePath)); const menuButton = findTag(normalized, /]*id=["']mobile-menu-button["'][^>]*>/i); diff --git a/scripts/optimize-images.ps1 b/scripts/optimize-images.ps1 index 7af0f14aba..cd7b5197af 100644 --- a/scripts/optimize-images.ps1 +++ b/scripts/optimize-images.ps1 @@ -4,8 +4,8 @@ Add-Type -AssemblyName System.Drawing $jpegQuality = 85 # Path to your images -$sourcePath = "c:\Users\yuval\grimgrimberg.github.io\assets\images" -$destPath = "c:\Users\yuval\grimgrimberg.github.io\assets\images\optimized" +$sourcePath = Join-Path $PSScriptRoot "..\assets\images" +$destPath = Join-Path $sourcePath "optimized" # Ensure destination directory exists if (!(Test-Path -Path $destPath)) { diff --git a/sitemap.xml b/sitemap.xml new file mode 100644 index 0000000000..fdf96ee936 --- /dev/null +++ b/sitemap.xml @@ -0,0 +1,19 @@ + + + + https://grimgrimberg.github.io/ + 2026-11-02 + + + https://grimgrimberg.github.io/cv.html + 2026-11-02 + + + https://grimgrimberg.github.io/fly101.html + 2026-11-06 + + + https://grimgrimberg.github.io/photo.html + 2026-11-02 + + diff --git a/tests/README.md b/tests/README.md index a1d4463132..12ba80cbab 100644 --- a/tests/README.md +++ b/tests/README.md @@ -4,6 +4,7 @@ This repo keeps two maintained Playwright surfaces: - `smoke.spec.js` runs through `npm test` using `playwright.config.js`. - `site-hygiene.spec.js` runs through `npm run lint:runtime` using `playwright.hygiene.config.js`. +- `lab-models.mjs` runs through `npm run test:models` and checks the lab simulation models without a browser. - `fixtures/mobile-nav-test.html` is a legacy navigation harness covered by hygiene checks. It is publicly accessible because GitHub Pages publishes the repository root. Use `npm run lint` and `npm test -- --reporter=line` as the default acceptance diff --git a/tests/site-hygiene.spec.js b/tests/site-hygiene.spec.js index fe3e268157..dc8590c9e1 100644 --- a/tests/site-hygiene.spec.js +++ b/tests/site-hygiene.spec.js @@ -3,14 +3,19 @@ const { test, expect } = require('@playwright/test'); const CHECKED_HTML_PAGES = [ 'index.html', 'photo.html', - 'thank-you.html', - 'about.html', - 'projects.html', - 'vision.html', 'cv.html', + 'fly101.html', + '404.html', 'tests/fixtures/mobile-nav-test.html' ]; +const LEGACY_REDIRECTS = { + 'about.html': '#about', + 'projects.html': '#projects', + 'vision.html': '#about', + 'thank-you.html': '#contact' +}; + function siteUrl(relativePath) { if (relativePath === 'index.html') { return '/'; @@ -80,6 +85,15 @@ test.describe('site hygiene', () => { await skipLink.click(); await expect(page.locator('#main-content'), `${target} should keep the main landmark reachable`).toBeInViewport(); + await expect(page.locator('#main-content'), `${target} skip link should move keyboard focus into main`).toBeFocused(); + await expect(page, `${target} skip link should update the URL hash`).toHaveURL(/#main-content$/); + } + }); + + test('legacy pages redirect into the matching homepage section', async ({ page }) => { + for (const [legacyPage, hash] of Object.entries(LEGACY_REDIRECTS)) { + await page.goto(siteUrl(legacyPage)); + await page.waitForURL((url) => url.pathname.endsWith('/index.html') && url.hash === hash); } }); diff --git a/tests/smoke.spec.js b/tests/smoke.spec.js index ad75b99daa..09be769bd9 100644 --- a/tests/smoke.spec.js +++ b/tests/smoke.spec.js @@ -91,6 +91,8 @@ test.describe('maintained smoke suite', () => { for (const width of [508, 320]) { requests.length = 0; await page.setViewportSize({ width, height: width === 508 ? 642 : 568 }); + // Same URL + same hash is an in-page jump, not a load; leave first so each width gets a real fresh load. + await page.goto('about:blank'); await page.goto('/#reconstruction-lab', { waitUntil: 'networkidle' }); const panel = page.locator('#reconstruction-lab'); const iframe = panel.locator('iframe'); @@ -126,10 +128,35 @@ test.describe('maintained smoke suite', () => { expect(errors).toEqual([]); }); + test('connectome lab loads its data only on request and region switches work', async ({ page }, testInfo) => { + test.skip(testInfo.project.name !== DESKTOP_PROJECT, 'Lab lifecycle runs once on desktop'); + const errors = []; + const dataRequests = []; + page.on('pageerror', (error) => errors.push(error.message)); + page.on('request', (request) => { + if (request.url().includes('fly-brain-points.bin')) dataRequests.push(request.url()); + }); + await page.goto('/', { waitUntil: 'networkidle' }); + await expect(page.locator('#brain-lab')).toBeHidden(); + expect(dataRequests).toEqual([]); + + await page.locator('[data-lab-mode="brain"]').click(); + await expect(page.locator('#brain-lab')).toBeVisible(); + await expect(page).toHaveURL(/#brain-lab$/); + await expect.poll(() => dataRequests.length).toBe(1); + const optic = page.locator('[data-brain-region="0"]'); + await expect(optic).toBeEnabled({ timeout: 15000 }); + await optic.click(); + await expect(optic).toHaveAttribute('aria-pressed', 'false'); + await page.locator('[data-lab-mode="drive"]').click(); + await expect(page.locator('#brain-lab')).toBeHidden(); + expect(errors).toEqual([]); + }); + test('primary pages use local generated CSS instead of Tailwind CDN', async ({ page }, testInfo) => { test.skip(testInfo.project.name !== DESKTOP_PROJECT, 'Static runtime check runs once on desktop'); - for (const target of ['index.html', 'photo.html', 'thank-you.html']) { + for (const target of ['index.html', 'photo.html', 'cv.html']) { await page.goto(siteUrl(target), { waitUntil: 'domcontentloaded' }); await expect(page.locator('script[src*="cdn.tailwindcss.com"]')).toHaveCount(0); await expect(page.locator('link[href="assets/css/output.css"]')).toHaveCount(1); @@ -185,10 +212,12 @@ test.describe('maintained smoke suite', () => { await expect(page.locator('#reviews')).toContainText('Stephen Hawking'); await expect(page.locator('#reviews')).toContainText('Doctor Strange'); await expect(page.locator('#retro-games')).toContainText('Retro Classics'); - await expect(page.locator('#retro-games')).toContainText('Hosted Game Files'); + await expect(page.locator('#retro-games')).toContainText(/hosted game files/i); await expect(page.locator('#retro-games [data-game-source]')).toHaveCount(6); await expect(page.locator('#retro-games')).toContainText('Dangerous Dave'); await expect(page.locator('#retro-games')).toContainText('SkyRoads'); + // Clippy is fetched lazily (idle on desktop, first Guide click on phones), so ask once, wait, then ask again. + await page.locator('#clippy-guide-button').click(); await page.waitForFunction(() => window.clippyLoaded === true && Boolean(window.clippyAgent), null, { timeout: 10000 }); diff --git a/thank-you.html b/thank-you.html index 6715cd9c40..e5f8bdfdfa 100644 --- a/thank-you.html +++ b/thank-you.html @@ -1,108 +1,22 @@ - + - Thank You | Yuval Grimberg - - - - - - + Contact moved | Yuval Grimberg + + + + - - - - - - - - - - -
-
- -
-
- -
-
- - -
-

- Thank you -

-

- Your message was received. I'll get back to you within 24 hours. -

-

- - In the meantime, feel free to explore my portfolio or connect with me on social media. -

-
- - - - - - -
-
- + +
+

This page moved. Go to the contact section.

+
diff --git a/vision.html b/vision.html index 6d730d4bd9..20a239d7a4 100644 --- a/vision.html +++ b/vision.html @@ -1,69 +1,22 @@ - - - Vision | Yuval Grimberg - - - - - -
- -
- -
-

- Legacy page: this standalone route is kept for compatibility. Start from - the homepage - for the maintained experience. -

-
-
-

Vision

-

- My vision is to create intelligent, adaptive systems that empower people and machines to move, interact, and create in harmony. I believe in blending rigorous engineering with creative exploration to solve real-world problems—whether it's racing autonomous vehicles, orchestrating satellite rendezvous, or making music recommendations that spark joy. -

-
-
-
🤖
-

Smart Mobility

-

Pushing the boundaries of robotics, autonomy, and control for safer, faster, and more efficient movement.

-
-
-
🌌
-

Space & Exploration

-

Designing algorithms and simulations for rendezvous, docking, and exploration beyond Earth.

-
-
-
🎶
-

Human Experience

-

Building tools that connect technology and emotion, from music analysis to user-centric design.

-
-
-
+ + + + Vision moved | Yuval Grimberg + + + + + + + - - + +
+

This page moved. Go to the About section.

+
+