From 99c2851285ad93a5912acf110f5fdcbccea5bccc Mon Sep 17 00:00:00 2001 From: Vincent van der Wal Date: Sun, 19 Jul 2026 10:55:51 +0200 Subject: [PATCH] page transition, optimize lighting --- README.md | 33 +++-- src/css/styles.scss | 155 ++++++++++++++------ src/flower.html | 1 - src/flower.js | 7 +- src/index.html | 1 - src/lib/cameraMemory.js | 32 +++++ src/lib/chrome.js | 45 +++--- src/lib/glow.js | 35 +++++ src/lib/stage.js | 5 + src/lib/theme.js | 4 +- src/lib/transit.js | 9 +- src/matrix.html | 1 - src/matrix.js | 4 +- src/metatron.html | 1 - src/metatron.js | 309 +++++++++++++++------------------------- src/phyllotaxis.html | 1 - src/phyllotaxis.js | 33 +++-- src/seed.html | 1 - src/seed.js | 7 +- src/torus.html | 1 - src/torus.js | 5 +- 21 files changed, 388 insertions(+), 302 deletions(-) create mode 100644 src/lib/cameraMemory.js create mode 100644 src/lib/glow.js diff --git a/README.md b/README.md index 1a44c51..f5adfce 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Most sacred-geometry figures are drawn flat. This project asks what they look li when they are built in real 3D space: the Fruit of Life as a rotated cubic sphere packing, the Platonic solids nested inside it with their vertices on the circle centres, the torus as a field of winding strands, and so on. Every page is an -interactive scene with orbit controls and a lil-gui panel for playing with the +interactive scene with orbit controls and a control panel for playing with the parameters. ## Pages @@ -17,25 +17,38 @@ parameters. | Metatron's Cube | `src/metatron.js` | The 3D Fruit of Life circle grid with the five Platonic solids (fire, earth, air, water, aether) aligned to it, plus the Merkaba and the full Metatron web | | Toroidal Field | `src/torus.js` | A torus with field-line strands winding around the surface and particles flowing along them | | Vector Equilibrium | `src/matrix.js` | The cuboctahedron: radial spokes equal to the edges, and the 12-around-1 kissing-sphere packing | -| Golden Spiral | `src/phyllotaxis.js` | Phyllotaxis at the golden angle: sunflower disc, pinecone and Fibonacci sphere; nudge the angle to watch the pattern collapse | +| Golden Spiral | `src/phyllotaxis.js` | Phyllotaxis at the golden angle: sunflower disc, pinecone and Fibonacci sphere, with a looping growth animation; nudge the angle to watch the pattern collapse | | Flower of Life | `src/flower.js` | A cubic grid of interlocking spheres | | Seed of Life | `src/seed.js` | The seven seed circles rotated through space | +## Design + +The landing page palette (deep warm gradient, orange `#f44f04`, cream `#f5e9de`) +is used 1:1 in the scenes: the canvases are transparent, so the CSS gradient is +the scenery background. The shadow wall is invisible; each shape projects a +light orange outline onto it. Dark/light theme toggle in the nav, persisted in +localStorage. + +Page navigations fly the shapes out past the camera, crossfade documents via +the View Transitions API (Chromium; graceful cut elsewhere), and fly the next +page's shapes in from the distance. + ## Development +Built with [Vite](https://vite.dev/) as a multi-page app. + ```sh npm install -npm start # dev server with hot reload +npm start # dev server npm run build # production build into build/ +npm run preview # serve the production build ``` -Each page is its own webpack entry; the page list lives at the top of -`webpack.config.js`. To add a new exploration, add `src/.js` (the shared -scene setup is in `src/lib/stage.js`) and one line to the `pages` array. - -Every page shares the chrome in `src/lib/chrome.js`: fade transitions between -pages and a dark/light toggle (bottom right, persisted in localStorage). Dark -mode matches the landing page; light mode is the original orange room. +Each exploration is a `src/.html` + `src/.js` pair; register new +pages in the `pages` list in `vite.config.js`. Shared code lives in `src/lib/`: +`stage.js` (scene/camera/lights/wall/controls), `theme.js` (palette), +`transit.js` (fly in/out), `chrome.js` (nav, theme toggle, transitions), +`edgeTubes.js` (line-thickness helpers). ## Debug flags (Metatron page) diff --git a/src/css/styles.scss b/src/css/styles.scss index 91d1d51..b1f46b9 100644 --- a/src/css/styles.scss +++ b/src/css/styles.scss @@ -3,22 +3,20 @@ // Every page shares --page-bg: the scene canvases are transparent, so the // landing gradient is the scenery background, 1:1. :root { - --page-bg: radial-gradient(ellipse at 50% 30%, #2a1204 0%, #120701 60%, #000 100%); - --fade-bg: #120701; + --page-bg: radial-gradient(ellipse at 50% 30%, #52220a 0%, #140801 60%, #000 100%); --ink: #f5e9de; --ink-soft: rgba(245, 233, 222, 0.7); --ink-faint: rgba(245, 233, 222, 0.55); - --panel-bg: rgba(18, 7, 1, 0.75); + --panel-bg: rgba(18, 7, 1, 0.6); --panel-border: rgba(244, 79, 4, 0.35); } html[data-theme='light'] { - --page-bg: radial-gradient(ellipse at 50% 30%, #fff8f0 0%, #ffe7d3 60%, #ffd4b3 100%); - --fade-bg: #ffe7d3; + --page-bg: radial-gradient(ellipse at 50% 30%, #fff8f0 0%, #fdca9e 55%, #ef9d55 100%); --ink: #1b0a02; --ink-soft: rgba(27, 10, 2, 0.7); --ink-faint: rgba(27, 10, 2, 0.5); - --panel-bg: rgba(255, 248, 240, 0.85); + --panel-bg: rgba(255, 248, 240, 0.6); --panel-border: rgba(244, 79, 4, 0.4); } @@ -34,39 +32,66 @@ body { color: var(--ink); } +// the scenes cast their projection onto the wall, so the glow of the gradient +// follows it: lib/glow.js projects the wall centre into screen space every +// frame and drives --glow-x/--glow-y; the landing keeps it centred +html:not([data-theme='light']) body:not(.landing) { + background: radial-gradient(ellipse 75% 90% at var(--glow-x, 12%) var(--glow-y, 50%), #5c2609 0%, #140801 65%, #000 100%); +} + +html[data-theme='light'] body:not(.landing) { + background: radial-gradient(ellipse 75% 90% at var(--glow-x, 12%) var(--glow-y, 50%), #fff8f0 0%, #fdca9e 60%, #ef9d55 100%); +} + canvas { display: block; } -// page transition: opaque at first paint, faded out by chrome.js once the page -// is ready, faded back in before navigating away. High z-index so it also -// covers stats (z-index 10000) and lil-gui. -.fade { - position: fixed; - inset: 0; - z-index: 10001; - background: var(--fade-bg); - opacity: 1; - pointer-events: none; - transition: opacity 0.35s ease; +// page transitions: while the shapes fly out, the page content fades +// (html.leaving, set by chrome.js), the browser crossfades the documents +// (Chromium), and the next page's content fades back in. The gradient +// background never fades, so pages blend through it instead of through black. +@view-transition { + navigation: auto; +} - &.done { +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 0.45s; +} + +canvas, +.page-nav, +.lil-gui, +.theme-toggle, +.landing-content { + animation: page-enter 0.6s ease; + transition: opacity 0.45s ease; +} + +html.leaving :is(canvas, .page-nav, .lil-gui, .theme-toggle, .landing-content) { + opacity: 0; +} + +@keyframes page-enter { + from { opacity: 0; } + + to { + opacity: 1; + } } .theme-toggle { - position: fixed; - bottom: 16px; - right: 16px; - z-index: 20; - width: 40px; - height: 40px; + width: 48px; + height: 48px; border: 1px solid var(--panel-border); border-radius: 50%; background: var(--panel-bg); + backdrop-filter: blur(10px); color: var(--ink); - font-size: 18px; + font-size: 22px; line-height: 1; cursor: pointer; @@ -75,26 +100,34 @@ canvas { } } -// back-link on the exploration pages; bottom-left because stats sits top-left -// and lil-gui top-right +// floating variant (landing page): top right +body > .theme-toggle { + position: fixed; + top: 16px; + right: 16px; + z-index: 20; +} + +// top bar on the exploration pages: back link, page title, theme toggle .page-nav { position: fixed; - bottom: 16px; + top: 16px; left: 16px; z-index: 10; display: flex; - gap: 12px; - align-items: baseline; + gap: 16px; + align-items: center; font-family: Georgia, 'Times New Roman', serif; a { color: var(--ink); background: var(--panel-bg); + backdrop-filter: blur(10px); border: 1px solid var(--panel-border); text-decoration: none; - padding: 6px 12px; + padding: 14px 26px; border-radius: 999px; - font-size: 14px; + font-size: 18px; &:hover { border-color: #f44f04; @@ -103,7 +136,7 @@ canvas { span { color: var(--ink-soft); - font-size: 14px; + font-size: 17px; letter-spacing: 0.04em; } } @@ -121,16 +154,22 @@ canvas { --number-color: #ff9a5c; --string-color: #ff9a5c; --font-family: Georgia, 'Times New Roman', serif; - --font-size: 13px; - --input-font-size: 13px; - --widget-border-radius: 6px; + --font-size: 17px; + --input-font-size: 17px; + --title-height: 44px; + --widget-height: 34px; + --spacing: 10px; + --padding: 14px; + --folder-indent: 14px; + --widget-border-radius: 8px; &.root { - margin: 12px; + margin: 16px; + width: 370px; border: 1px solid var(--panel-border); border-radius: 12px; overflow: hidden; - backdrop-filter: blur(6px); + backdrop-filter: blur(10px); } &.root > .title { @@ -143,16 +182,37 @@ html[data-theme='light'] .lil-gui { --string-color: #c2410c; } +@media (max-width: 600px) { + .lil-gui.root { + width: calc(100vw - 24px); + max-height: 45vh; + overflow-y: auto; + } + + .page-nav { + gap: 8px; + + a, + span { + font-size: 15px; + } + + a { + padding: 10px 16px; + } + } +} + // ---------- landing ---------- // body.landing { - --card-bg: rgba(18, 7, 1, 0.65); - --card-bg-hover: rgba(42, 18, 4, 0.85); + --card-bg: rgba(18, 7, 1, 0.5); + --card-bg-hover: rgba(42, 18, 4, 0.75); } html[data-theme='light'] body.landing { - --card-bg: rgba(255, 255, 255, 0.6); - --card-bg-hover: rgba(255, 255, 255, 0.95); + --card-bg: rgba(255, 255, 255, 0.45); + --card-bg-hover: rgba(255, 255, 255, 0.8); } .landing-bg { @@ -202,6 +262,7 @@ html[data-theme='light'] body.landing { border: 1px solid var(--panel-border); border-radius: 12px; background: var(--card-bg); + backdrop-filter: blur(10px); color: inherit; text-decoration: none; transition: transform 0.2s ease, border-color 0.2s ease, background 0.2s ease; @@ -228,7 +289,13 @@ html[data-theme='light'] body.landing { } .soon { - margin-top: 72px; + width: fit-content; + margin: 72px auto 0; + padding: 28px 40px; + border: 1px solid var(--panel-border); + border-radius: 12px; + background: var(--card-bg); + backdrop-filter: blur(10px); text-align: center; h3 { @@ -247,7 +314,7 @@ html[data-theme='light'] body.landing { li { font-size: 14px; - color: var(--ink-faint); + color: var(--ink-soft); margin: 6px 0; } } diff --git a/src/flower.html b/src/flower.html index fb1bea9..3865564 100644 --- a/src/flower.html +++ b/src/flower.html @@ -7,7 +7,6 @@ -