diff --git a/docs.html b/docs.html index 7bea3d3..01918bf 100644 --- a/docs.html +++ b/docs.html @@ -173,9 +173,5 @@ - - diff --git a/index.html b/index.html index 9f10e67..8016f95 100644 --- a/index.html +++ b/index.html @@ -20,7 +20,7 @@ - +
@@ -69,10 +69,7 @@
-
- - -
+
@@ -119,9 +116,5 @@ - - diff --git a/js/engine.js b/js/engine.js index 4e2d283..326b353 100644 --- a/js/engine.js +++ b/js/engine.js @@ -5,7 +5,6 @@ var Engine = (function () { var canvas, gl, program, uniforms = {}; var playing = true; var suspended = false; - var started = false; var loopT = 0; // seconds into current loop var lastTick = 0; var fps = 60, fpsAcc = 0, fpsN = 0, fpsCb = null; @@ -33,14 +32,13 @@ var Engine = (function () { return sh; } - function init(canvasEl, paramsGetter, opts) { - opts = opts || {}; + function init(canvasEl, paramsGetter) { canvas = canvasEl; getParams = paramsGetter; gl = canvas.getContext("webgl2", { antialias: false, preserveDrawingBuffer: true, - powerPreference: "default" + powerPreference: "high-performance" }); if (!gl) throw new Error("WebGL2 not available"); @@ -63,15 +61,6 @@ var Engine = (function () { uniforms[n] = gl.getUniformLocation(program, n); }); - lastTick = performance.now(); - started = false; - if (opts.autostart) start(); - } - - function start() { - if (started) return; - started = true; - suspended = false; lastTick = performance.now(); requestAnimationFrame(tick); } @@ -179,7 +168,6 @@ var Engine = (function () { return { init: init, - start: start, setSize: setSize, renderAt: renderAt, readPixels: readPixels, diff --git a/js/main.js b/js/main.js index 352d02f..ca3c679 100644 --- a/js/main.js +++ b/js/main.js @@ -483,7 +483,7 @@ function updateMeta() { document.getElementById("meta-res").textContent = s[0] + "\u00d7" + s[1]; } -function fitCanvas(preview) { +function fitCanvas() { var frame = document.getElementById("canvas-frame"); var availW = frame.clientWidth - 80; var availH = frame.clientHeight - 52; @@ -491,18 +491,10 @@ function fitCanvas(preview) { var ar = ASPECTS[P.aspect]; var w = availW, h = w / ar; if (h > availH) { h = availH; w = h * ar; } - w = Math.round(w); - h = Math.round(h); var canvas = Engine.canvas(); - canvas.style.width = w + "px"; - canvas.style.height = h + "px"; - var boot = document.getElementById("canvas-boot"); - if (boot) { - boot.style.width = w + "px"; - boot.style.height = h + "px"; - } - var dprCap = preview ? 0.75 : 1.35; - var dpr = Math.min(window.devicePixelRatio || 1, dprCap); + canvas.style.width = Math.round(w) + "px"; + canvas.style.height = Math.round(h) + "px"; + var dpr = Math.min(window.devicePixelRatio || 1, 1.35); Engine.setSize(2 * Math.round(w * dpr / 2), 2 * Math.round(h * dpr / 2)); updateMeta(); } @@ -515,35 +507,21 @@ function setPlayingUI(v) { /* ---------------- boot ---------------- */ -function runIntro() { - var items = []; - document.querySelectorAll(".brand, .segmented button, .topbar-actions > *").forEach(function (n) { - items.push(n); - }); - items.push(document.getElementById("canvas-shell")); - items.push(document.querySelector(".stage-meta")); - document.querySelectorAll(".rail-section").forEach(function (n) { items.push(n); }); - document.querySelectorAll(".mode-grid > *, .export-grid > *").forEach(function (n) { items.push(n); }); +document.addEventListener("DOMContentLoaded", function () { + var canvas = document.getElementById("view"); + try { + Engine.init(canvas, function () { return P; }); + } catch (e) { + document.querySelector(".canvas-frame").innerHTML = + '
' + + "WebGL2 is required. Please use a recent Chrome, Edge or Firefox.
" + + String(e.message).split("\n")[0] + "
"; + return; + } - var d = 0; - items.forEach(function (n, i) { - if (!n) return; - d += i < 10 ? 40 : 24; - n.classList.add("stagger-item"); - n.style.setProperty("--intro-d", Math.min(d, 820) + "ms"); - }); - document.body.classList.add("intro"); - setTimeout(function () { - document.body.classList.remove("intro"); - items.forEach(function (n) { - if (!n) return; - n.classList.remove("stagger-item"); - n.style.removeProperty("--intro-d"); - }); - }, 1500); -} + buildRail(); -function wireControls() { + /* aspect segmented control */ var segRefresh = UI.segmented( document.getElementById("aspect-seg"), Object.keys(ASPECTS).map(function (k) { return [k, k]; }), @@ -552,6 +530,61 @@ function wireControls() { ); reg(segRefresh); + /* load a shared design from the URL hash, e.g. .../#LMN1.xxxx */ + var hash = decodeURIComponent(location.hash.slice(1) || ""); + if (hash.indexOf("LMN1.") === 0 && decodeDesign(hash)) { + UI.toast("Shared design loaded"); + } + + fitCanvas(); + new ResizeObserver(fitCanvas).observe(document.getElementById("canvas-frame")); + + /* load a shared design from the URL hash, e.g. site/#LMN1.xxxx */ + if (location.hash.length > 6) { + if (decodeDesign(decodeURIComponent(location.hash.slice(1)))) { + UI.toast("Shared design loaded"); + } + } + + /* intro: every element pops in big and settles into place, + one after another. transform/opacity only, fully compositor-driven. */ + (function intro() { + var items = []; + document.querySelectorAll(".brand, .segmented button, .topbar-actions > *").forEach(function (n) { items.push(n); }); + items.push(document.querySelector(".canvas-frame")); + items.push(document.querySelector(".stage-meta")); + document.querySelectorAll(".rail-section").forEach(function (sec) { + Array.prototype.forEach.call(sec.children, function (child) { + if (child.classList.contains("mode-grid") || + child.classList.contains("preset-row") || + child.classList.contains("export-grid")) { + Array.prototype.forEach.call(child.children, function (n) { items.push(n); }); + } else { + items.push(child); + } + }); + }); + + var d = 0; + items.forEach(function (n, i) { + if (!n) return; + /* fast cadence inside the rail, slower for the hero pieces */ + d += i < 12 ? 50 : 16; + n.classList.add("stagger-item"); + n.style.setProperty("--intro-d", Math.min(d, 2100) + "ms"); + }); + document.body.classList.add("intro"); + setTimeout(function () { + document.body.classList.remove("intro"); + items.forEach(function (n) { + if (!n) return; + n.classList.remove("stagger-item"); + n.style.removeProperty("--intro-d"); + n.style.willChange = "auto"; + }); + }, 3000); + })(); + Engine.onFps(function (fps) { document.getElementById("meta-fps").textContent = fps + " fps"; }); @@ -572,59 +605,6 @@ function wireControls() { else if (e.key === "r" || e.key === "R") { randomizeAll(); } else if (e.key === "s" || e.key === "S") { Exporter.exportPNG(P, ASPECTS[P.aspect]); } }); -} -function showBootError(msg) { - document.body.classList.remove("booting"); - document.querySelector(".canvas-frame").innerHTML = - '
' + - "WebGL2 is required. Please use a recent Chrome, Edge or Firefox.
" + - String(msg).split("\n")[0] + "
"; -} - -function finishBoot(opts) { - document.body.classList.remove("booting"); - document.body.classList.add("ready"); - Engine.start(); - if (opts.intro) runIntro(); - setTimeout(function () { fitCanvas(false); }, 1400); -} - -document.addEventListener("DOMContentLoaded", function () { - var canvas = document.getElementById("view"); - var lowPower = (navigator.hardwareConcurrency || 8) <= 4 || - ((navigator.deviceMemory || 8) <= 4); - var reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; - - /* paint the shell first, then build UI, then compile shaders off the first frame */ - requestAnimationFrame(function () { - buildRail(); - wireControls(); - - var hash = decodeURIComponent(location.hash.slice(1) || ""); - if (hash.indexOf("LMN1.") === 0 && decodeDesign(hash)) { - UI.toast("Shared design loaded"); - } - - fitCanvas(true); - new ResizeObserver(function () { - fitCanvas(!document.body.classList.contains("ready")); - }).observe(document.getElementById("canvas-frame")); - - requestAnimationFrame(function () { - try { - Engine.init(canvas, function () { return P; }, { autostart: false }); - } catch (e) { - showBootError(e.message); - return; - } - - Engine.renderAt(0); - - requestAnimationFrame(function () { - finishBoot({ intro: !lowPower && !reduceMotion }); - updateMeta(); - }); - }); - }); + updateMeta(); }); diff --git a/styles.css b/styles.css index 31279cb..a828e63 100644 --- a/styles.css +++ b/styles.css @@ -189,38 +189,6 @@ button { font: inherit; color: inherit; cursor: pointer; background: none; borde min-height: 0; } -.canvas-shell { - position: relative; - display: flex; - align-items: center; - justify-content: center; - max-width: 100%; - max-height: 100%; -} - -.canvas-boot { - position: absolute; - border-radius: var(--radius-l); - background: - linear-gradient(135deg, #120804 0%, #3d1204 38%, #ff6a00 72%, #ffb347 100%); - background-size: 220% 220%; - animation: boot-shimmer 2.2s ease-in-out infinite; - box-shadow: - 0 0 0 1px rgba(255,255,255,0.07), - inset 0 1px 0 rgba(255,255,255,0.08), - 0 30px 80px -20px rgba(0,0,0,0.9), - 0 8px 26px -10px rgba(0,0,0,0.65); - transition: opacity 0.45s ease; - pointer-events: none; -} - -@keyframes boot-shimmer { - 0%, 100% { background-position: 0% 50%; } - 50% { background-position: 100% 50%; } -} - -body.ready .canvas-boot { opacity: 0; } - #view { max-width: 100%; max-height: 100%; @@ -231,12 +199,8 @@ body.ready .canvas-boot { opacity: 0; } 0 30px 80px -20px rgba(0,0,0,0.9), 0 8px 26px -10px rgba(0,0,0,0.65); background: #000; - opacity: 0; - transition: opacity 0.45s ease; } -body.ready #view { opacity: 1; } - .stage-meta { display: flex; align-items: center; @@ -854,35 +818,19 @@ select { cursor: pointer; appearance: none; padding-right: 22px; border-radius: 5px; } -/* ---------- boot: keep shell visible, defer heavy glass ---------- */ - -body.booting .topbar, -body.booting .rail { - backdrop-filter: none; - -webkit-backdrop-filter: none; -} -body.booting .topbar { background: var(--bg); } -body.booting .rail { - opacity: 0; - pointer-events: none; -} -body.ready .rail { - opacity: 1; - pointer-events: auto; - transition: opacity 0.35s ease; -} - /* ---------- intro stagger animation ---------- */ @keyframes intro-pop { - 0% { opacity: 0; transform: scale(1.1) translateY(6px); } + 0% { opacity: 0; transform: scale(1.45) translateY(-4px); } + 62% { opacity: 1; } 100% { opacity: 1; transform: scale(1) translateY(0); } } .intro .stagger-item { - animation: intro-pop 420ms cubic-bezier(.22,1,.36,1) both; + animation: intro-pop 560ms cubic-bezier(.22,1.3,.36,1) both; animation-delay: var(--intro-d, 0ms); + will-change: transform, opacity; } -.intro .canvas-shell.stagger-item { animation-duration: 520ms; } +.intro .canvas-frame.stagger-item { animation-duration: 780ms; } /* ---------- download success effect ---------- */