From c4faf12f23831425c22da048e0739f857738e91b Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 13 May 2026 15:14:59 +0000 Subject: [PATCH 1/2] feat: live demo section, nav overlay, and 4th partner Implements the deltas from the latest Provekit Design System bundle: - Live Demo (LiveDemo.tsx + LiveDemoSection.astro): interactive zero-knowledge proof generation widget on /benchmarks, above the methodology panel. React island via @astrojs/react with framer-motion driving the step progress bars, dot pulse, byte reveal, caret blink, and section swap-ins. Locked to the Age verification scenario per the final design iteration; Poseidon/SHA-256 toggle re-scales constraints and timing. Hydrated only on /benchmarks via client:visible. - Nav overlay (NavOverlay.astro + nav-overlay.ts): full-page navigation sheet that opens from the hamburger. Five numbered items with hover translate + arrow rotate, two meta columns (Resources / Community), CTA strip, backdrop blur, ESC + backdrop click to close, body scroll lock, and a hamburger -> X swap. Vanilla TS controller, no React. - Nethermind added as 4th partner in EngineeringCredit. - 4 demo keyframes (pulse, caret, byte-in, swap-in) in global.css with prefers-reduced-motion guards. Type-checks (pnpm check: 0 errors) and builds cleanly. The new React bundle is loaded only when /benchmarks scrolls into view, so the landing page critical path is unchanged. --- astro.config.mjs | 3 +- package.json | 6 + pnpm-lock.yaml | 199 ++++ public/figma/nethermind-logo.svg | 27 + .../credits/EngineeringCredit.astro | 11 + src/components/demo/LiveDemo.tsx | 861 ++++++++++++++++++ src/components/demo/LiveDemoSection.astro | 74 ++ src/components/nav/NavOverlay.astro | 406 +++++++++ src/components/nav/TopBar.astro | 21 +- src/pages/benchmarks.astro | 3 + src/scripts/nav-overlay.ts | 57 ++ src/styles/global.css | 50 + tsconfig.json | 2 + 13 files changed, 1717 insertions(+), 3 deletions(-) create mode 100644 public/figma/nethermind-logo.svg create mode 100644 src/components/demo/LiveDemo.tsx create mode 100644 src/components/demo/LiveDemoSection.astro create mode 100644 src/components/nav/NavOverlay.astro create mode 100644 src/scripts/nav-overlay.ts diff --git a/astro.config.mjs b/astro.config.mjs index b279928..cb7724e 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,6 +1,7 @@ import { defineConfig } from 'astro/config'; import tailwindcss from '@tailwindcss/vite'; import sitemap from '@astrojs/sitemap'; +import react from '@astrojs/react'; export default defineConfig({ site: 'https://provekit.org', @@ -9,7 +10,7 @@ export default defineConfig({ build: { inlineStylesheets: 'auto', }, - integrations: [sitemap()], + integrations: [sitemap(), react({ include: ['**/*.tsx'] })], vite: { plugins: [tailwindcss()], }, diff --git a/package.json b/package.json index 8ca1652..1a5e961 100644 --- a/package.json +++ b/package.json @@ -18,15 +18,21 @@ "lhci": "lhci autorun" }, "dependencies": { + "@astrojs/react": "^3.6.2", "@astrojs/sitemap": "3.2.1", "@fontsource/geist-mono": "^5.1.0", "@fontsource/outfit": "^5.1.0", "@tailwindcss/vite": "^4.0.0", "astro": "^4.16.0", + "framer-motion": "^11.11.17", + "react": "^18.3.1", + "react-dom": "^18.3.1", "tailwindcss": "^4.0.0" }, "devDependencies": { "@astrojs/check": "^0.9.4", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", "@lhci/cli": "^0.14.0", "@playwright/test": "^1.49.0", "@types/node": "^22.10.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b623e0..9dbc7e5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,9 @@ importers: .: dependencies: + '@astrojs/react': + specifier: ^3.6.2 + version: 3.6.3(@types/node@22.19.18)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@astrojs/sitemap': specifier: 3.2.1 version: 3.2.1 @@ -23,6 +26,15 @@ importers: astro: specifier: ^4.16.0 version: 4.16.19(@types/node@22.19.18)(lightningcss@1.32.0)(rollup@4.60.3)(typescript@5.9.3) + framer-motion: + specifier: ^11.11.17 + version: 11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: + specifier: ^18.3.1 + version: 18.3.1 + react-dom: + specifier: ^18.3.1 + version: 18.3.1(react@18.3.1) tailwindcss: specifier: ^4.0.0 version: 4.3.0 @@ -39,6 +51,12 @@ importers: '@types/node': specifier: ^22.10.0 version: 22.19.18 + '@types/react': + specifier: ^18.3.12 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.3.1 + version: 18.3.7(@types/react@18.3.28) eslint: specifier: ^9.16.0 version: 9.39.4(jiti@2.7.0) @@ -121,6 +139,15 @@ packages: resolution: {integrity: sha512-Z9IYjuXSArkAUx3N6xj6+Bnvx8OdUSHA8YoOgyepp3+zJmtVYJIl/I18GozdJVW1p5u/CNpl3Km7/gwTJK85cw==} engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} + '@astrojs/react@3.6.3': + resolution: {integrity: sha512-5ihLQDH5Runddug5AZYlnp/Q5T81QxhwnWJXA9rchBAdh11c6UhBbv9Kdk7b2PkXoEU70CGWBP9hSh0VCR58eA==} + engines: {node: ^18.17.1 || ^20.3.0 || >=21.0.0} + peerDependencies: + '@types/react': ^17.0.50 || ^18.0.21 + '@types/react-dom': ^17.0.17 || ^18.0.6 + react: ^17.0.2 || ^18.0.0 || ^19.0.0-beta + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0-beta + '@astrojs/sitemap@3.2.1': resolution: {integrity: sha512-uxMfO8f7pALq0ADL6Lk68UV6dNYjJ2xGUzyjjVj60JLBs5a6smtlkBYv3tQ0DzoqwS7c9n4FUx5lgv0yPo/fgA==} @@ -200,6 +227,18 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx@7.28.6': resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} engines: {node: '>=6.9.0'} @@ -805,6 +844,9 @@ packages: engines: {node: '>=18'} hasBin: true + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + '@rollup/pluginutils@5.3.0': resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} engines: {node: '>=14.0.0'} @@ -1122,6 +1164,17 @@ packages: '@types/node@22.19.18': resolution: {integrity: sha512-9v00a+dn2yWVsYDEunWC4g/TcRKVq3r8N5FuZp7u0SGrPvdN9c2yXI9bBuf5Fl0hNCb+QTIePTn5pJs2pwBOQQ==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + '@types/sax@1.2.7': resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} @@ -1193,6 +1246,12 @@ packages: '@ungap/structured-clone@1.3.1': resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/expect@2.1.9': resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} @@ -1680,6 +1739,9 @@ packages: engines: {node: '>=4'} hasBin: true + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-uri-to-buffer@6.0.2: resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} engines: {node: '>= 14'} @@ -2050,6 +2112,20 @@ packages: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} + framer-motion@11.18.2: + resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -2534,6 +2610,10 @@ packages: lookup-closest-locale@6.2.0: resolution: {integrity: sha512-/c2kL+Vnp1jnV6K6RpDTHK3dgg0Tu2VVp+elEiJpjfS1UyY7AjOYHohRug6wT0OpoX2qFgNORndE9RqesfVxWQ==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -2761,6 +2841,12 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true + motion-dom@11.18.1: + resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} + + motion-utils@11.18.1: + resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==} + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} @@ -3121,6 +3207,19 @@ packages: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + readdirp@4.1.2: resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} engines: {node: '>= 14.18.0'} @@ -3257,6 +3356,9 @@ packages: resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} engines: {node: '>=v12.22.7'} + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} engines: {node: '>=4'} @@ -3607,6 +3709,9 @@ packages: engines: {node: '>=14.17'} hasBin: true + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + unbzip2-stream@1.4.3: resolution: {integrity: sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==} @@ -4132,6 +4237,26 @@ snapshots: dependencies: prismjs: 1.30.0 + '@astrojs/react@3.6.3(@types/node@22.19.18)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(lightningcss@1.32.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + '@vitejs/plugin-react': 4.7.0(vite@5.4.21(@types/node@22.19.18)(lightningcss@1.32.0)) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + ultrahtml: 1.6.0 + vite: 5.4.21(@types/node@22.19.18)(lightningcss@1.32.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + '@astrojs/sitemap@3.2.1': dependencies: sitemap: 8.0.3 @@ -4242,6 +4367,16 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -4765,6 +4900,8 @@ snapshots: - react-native-b4a - supports-color + '@rolldown/pluginutils@1.0.0-beta.27': {} + '@rollup/pluginutils@5.3.0(rollup@4.60.3)': dependencies: '@types/estree': 1.0.9 @@ -5046,6 +5183,17 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + '@types/sax@1.2.7': dependencies: '@types/node': 22.19.18 @@ -5150,6 +5298,18 @@ snapshots: '@ungap/structured-clone@1.3.1': {} + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@22.19.18)(lightningcss@1.32.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21(@types/node@22.19.18)(lightningcss@1.32.0) + transitivePeerDependencies: + - supports-color + '@vitest/expect@2.1.9': dependencies: '@vitest/spy': 2.1.9 @@ -5738,6 +5898,8 @@ snapshots: cssesc@3.0.0: {} + csstype@3.2.3: {} + data-uri-to-buffer@6.0.2: {} data-urls@7.0.0: @@ -6151,6 +6313,15 @@ snapshots: forwarded@0.2.0: {} + framer-motion@11.18.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + motion-dom: 11.18.1 + motion-utils: 11.18.1 + tslib: 2.8.1 + optionalDependencies: + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + fresh@0.5.2: {} fs.realpath@1.0.0: {} @@ -6689,6 +6860,10 @@ snapshots: lookup-closest-locale@6.2.0: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + loupe@3.2.1: {} lru-cache@11.3.6: {} @@ -7079,6 +7254,12 @@ snapshots: dependencies: minimist: 1.2.8 + motion-dom@11.18.1: + dependencies: + motion-utils: 11.18.1 + + motion-utils@11.18.1: {} + mrmime@2.0.1: {} ms@2.0.0: {} @@ -7384,6 +7565,18 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-refresh@0.17.0: {} + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + readdirp@4.1.2: {} regex-recursion@5.1.1: @@ -7579,6 +7772,10 @@ snapshots: dependencies: xmlchars: 2.2.0 + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + section-matter@1.0.0: dependencies: extend-shallow: 2.0.1 @@ -8002,6 +8199,8 @@ snapshots: typescript@5.9.3: {} + ultrahtml@1.6.0: {} + unbzip2-stream@1.4.3: dependencies: buffer: 5.7.1 diff --git a/public/figma/nethermind-logo.svg b/public/figma/nethermind-logo.svg new file mode 100644 index 0000000..1e44617 --- /dev/null +++ b/public/figma/nethermind-logo.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/components/credits/EngineeringCredit.astro b/src/components/credits/EngineeringCredit.astro index 3563969..ea145ed 100644 --- a/src/components/credits/EngineeringCredit.astro +++ b/src/components/credits/EngineeringCredit.astro @@ -50,6 +50,17 @@ import ArrowRight from '~/components/icons/ArrowRight.astro'; style="height:36px;width:auto;display:block" /> + +
+ Nethermind +
+ {state === 'active' && ( + + + diff --git a/src/components/nav/TopBar.astro b/src/components/nav/TopBar.astro index b98d10a..0d5ec26 100644 --- a/src/components/nav/TopBar.astro +++ b/src/components/nav/TopBar.astro @@ -1,5 +1,6 @@ --- import ArrowRight from '~/components/icons/ArrowRight.astro'; +import NavOverlay from '~/components/nav/NavOverlay.astro'; ---
+ +
+ +
+ + diff --git a/src/pages/benchmarks.astro b/src/pages/benchmarks.astro index 378bf0d..5ff8669 100644 --- a/src/pages/benchmarks.astro +++ b/src/pages/benchmarks.astro @@ -2,6 +2,7 @@ import BaseLayout from '~/layouts/BaseLayout.astro'; import TopBar from '~/components/nav/TopBar.astro'; import BenchmarksHero from '~/components/benchmarks-page/BenchmarksHero.astro'; +import LiveDemoSection from '~/components/demo/LiveDemoSection.astro'; import BenchmarksMethodology from '~/components/benchmarks-page/BenchmarksMethodology.astro'; import BenchmarkDetail from '~/components/benchmarks-page/BenchmarkDetail.astro'; import BenchmarksSummary from '~/components/benchmarks-page/BenchmarksSummary.astro'; @@ -18,6 +19,8 @@ import SiteFooter from '~/components/footer/SiteFooter.astro'; + + diff --git a/src/scripts/nav-overlay.ts b/src/scripts/nav-overlay.ts new file mode 100644 index 0000000..6f23367 --- /dev/null +++ b/src/scripts/nav-overlay.ts @@ -0,0 +1,57 @@ +/* + * nav-overlay.ts — controller for the full-page navigation sheet. + * + * Wires: + * - [data-nav-toggle] hamburger button ↔ [data-nav-overlay] sheet root + * - aria-expanded / aria-hidden / data-open state in lockstep + * - ESC + backdrop click + nav link click to close + * - body scroll lock while open + */ + +const toggle = document.querySelector('[data-nav-toggle]'); +const overlay = document.querySelector('[data-nav-overlay]'); +const backdrop = overlay?.querySelector('[data-nav-backdrop]'); + +if (toggle && overlay) { + const navLinks = overlay.querySelectorAll('a[href]'); + + let prevOverflow = ''; + + const setOpen = (open: boolean) => { + overlay.dataset['open'] = open ? 'true' : 'false'; + overlay.setAttribute('aria-hidden', open ? 'false' : 'true'); + toggle.setAttribute('aria-expanded', open ? 'true' : 'false'); + toggle.setAttribute('aria-label', open ? 'Close menu' : 'Open menu'); + if (open) { + prevOverflow = document.body.style.overflow; + document.body.style.overflow = 'hidden'; + // Focus the first nav link for keyboard users. + const first = overlay.querySelector('.pk-nav-item'); + first?.focus({ preventScroll: true }); + } else { + document.body.style.overflow = prevOverflow; + toggle.focus({ preventScroll: true }); + } + }; + + toggle.addEventListener('click', () => { + const open = toggle.getAttribute('aria-expanded') !== 'true'; + setOpen(open); + }); + + backdrop?.addEventListener('click', () => setOpen(false)); + + navLinks.forEach((a) => + a.addEventListener('click', () => { + // Close on same-page anchor clicks too; routing handles the rest. + setOpen(false); + }), + ); + + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && toggle.getAttribute('aria-expanded') === 'true') { + e.preventDefault(); + setOpen(false); + } + }); +} diff --git a/src/styles/global.css b/src/styles/global.css index b685119..7c7adbd 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -243,6 +243,56 @@ fill: var(--pk-ink); } + /* Live-demo keyframes (CSS-only counterparts; framer-motion drives the + * primary in-app animations, but these are used by inline byte spans and + * for any consumers that opt out of motion). Mirror of