chore: deploy private lightloom build
Browse files- frontend/css/immersive.css +28 -0
- frontend/index.html +4 -1
- frontend/js/controller.js +11 -9
frontend/css/immersive.css
CHANGED
|
@@ -313,3 +313,31 @@ body.in-world .controls .ctl:nth-child(5) { animation-delay: 0.32s; }
|
|
| 313 |
.status--work::after, .mic.is-recording { animation: none !important; }
|
| 314 |
*, *::before, *::after { transition-duration: 0.01ms !important; }
|
| 315 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 313 |
.status--work::after, .mic.is-recording { animation: none !important; }
|
| 314 |
*, *::before, *::after { transition-duration: 0.01ms !important; }
|
| 315 |
}
|
| 316 |
+
|
| 317 |
+
/* ---- IF-TIME polish: wordmark · title-card · entrance fades · contrast ---- */
|
| 318 |
+
/* 8 · the wordmark reads as a logo: gilded LOOM + a glowing projector dot */
|
| 319 |
+
.hud__brand { display: inline-flex; align-items: center; letter-spacing: 0.2em; opacity: 0.96; }
|
| 320 |
+
.hud__brand b { color: var(--amber); font-weight: 700; }
|
| 321 |
+
.hud__brand::before { content: ""; width: 6px; height: 6px; border-radius: 50%; margin-right: 9px; flex: none;
|
| 322 |
+
background: var(--amber); box-shadow: 0 0 9px var(--amber), 0 0 3px var(--amber); }
|
| 323 |
+
|
| 324 |
+
/* 9 · the Director NAMING your world is a cinematic title card: tracking-in + a growing gold filet */
|
| 325 |
+
.world-title { letter-spacing: 0.3em; transition: opacity 0.8s ease, transform 0.8s ease, letter-spacing 0.8s var(--ease); }
|
| 326 |
+
.world-title.is-on { letter-spacing: 0.015em; }
|
| 327 |
+
.world-title::after { content: ""; display: block; height: 1px; width: 0; margin: 7px auto 0;
|
| 328 |
+
background: linear-gradient(90deg, transparent, var(--amber), transparent); transition: width 0.8s var(--ease) 0.12s; }
|
| 329 |
+
.world-title.is-on::after { width: 130px; }
|
| 330 |
+
|
| 331 |
+
/* 11 · keepsake + explore cross-fade in (the panel already pops); stagger the keepsake text */
|
| 332 |
+
.keepsake, .explore3d { animation: llFade 0.42s ease both; }
|
| 333 |
+
.keepsake__title { animation: llRise 0.5s var(--ease) both 0.12s; }
|
| 334 |
+
.keepsake__caption { animation: llRise 0.5s var(--ease) both 0.2s; }
|
| 335 |
+
.keepsake__notes { animation: llRise 0.5s var(--ease) both 0.28s; }
|
| 336 |
+
|
| 337 |
+
/* 13 · idle orchestra labels crisp on a big projector, not muddy */
|
| 338 |
+
.orch { color: rgba(241, 234, 217, 0.52); }
|
| 339 |
+
|
| 340 |
+
@media (prefers-reduced-motion: reduce) {
|
| 341 |
+
.keepsake, .explore3d, .keepsake__title, .keepsake__caption, .keepsake__notes {
|
| 342 |
+
animation: none !important; opacity: 1 !important; transform: none !important; }
|
| 343 |
+
}
|
frontend/index.html
CHANGED
|
@@ -17,6 +17,9 @@
|
|
| 17 |
name="description"
|
| 18 |
content="Speak, and your world unrolls ahead of you. Lightloom turns your voice into an endless, explorable 3D world — tiny local models listen, direct, paint and give depth to every scene in real time."
|
| 19 |
/>
|
|
|
|
|
|
|
|
|
|
| 20 |
<link rel="stylesheet" href="/frontend/css/tokens.css" />
|
| 21 |
<link rel="stylesheet" href="/frontend/css/immersive.css" />
|
| 22 |
<script type="importmap">
|
|
@@ -31,7 +34,7 @@
|
|
| 31 |
|
| 32 |
<!-- Top HUD: brand + the live orchestra (which tiny model is working now) + counter. -->
|
| 33 |
<header id="hud" class="hud">
|
| 34 |
-
<span class="hud__brand">
|
| 35 |
<nav class="orchestra" aria-label="Model orchestra">
|
| 36 |
<span class="orch" id="orch-asr"><i></i>Voice</span>
|
| 37 |
<span class="orch" id="orch-director"><i></i>Director</span>
|
|
|
|
| 17 |
name="description"
|
| 18 |
content="Speak, and your world unrolls ahead of you. Lightloom turns your voice into an endless, explorable 3D world — tiny local models listen, direct, paint and give depth to every scene in real time."
|
| 19 |
/>
|
| 20 |
+
<!-- Preload the self-hosted hero fonts so the title card / wordmark never FOUT-flash Georgia first. -->
|
| 21 |
+
<link rel="preload" as="font" type="font/woff2" crossorigin href="/assets/fonts/Fraunces-Variable.woff2" />
|
| 22 |
+
<link rel="preload" as="font" type="font/woff2" crossorigin href="/assets/fonts/Inter-Variable.woff2" />
|
| 23 |
<link rel="stylesheet" href="/frontend/css/tokens.css" />
|
| 24 |
<link rel="stylesheet" href="/frontend/css/immersive.css" />
|
| 25 |
<script type="importmap">
|
|
|
|
| 34 |
|
| 35 |
<!-- Top HUD: brand + the live orchestra (which tiny model is working now) + counter. -->
|
| 36 |
<header id="hud" class="hud">
|
| 37 |
+
<span class="hud__brand">LIGHT<b>LOOM</b></span>
|
| 38 |
<nav class="orchestra" aria-label="Model orchestra">
|
| 39 |
<span class="orch" id="orch-asr"><i></i>Voice</span>
|
| 40 |
<span class="orch" id="orch-director"><i></i>Director</span>
|
frontend/js/controller.js
CHANGED
|
@@ -700,7 +700,17 @@ const controller = {
|
|
| 700 |
|
| 701 |
_about(show) {
|
| 702 |
const el = document.getElementById("about");
|
| 703 |
-
if (el)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 704 |
},
|
| 705 |
|
| 706 |
setReducedMotion(on, fromOS) {
|
|
@@ -712,20 +722,12 @@ const controller = {
|
|
| 712 |
if (!fromOS) try { localStorage.setItem("ll.rm", this.reducedMotion ? "1" : "0"); } catch (_) {}
|
| 713 |
},
|
| 714 |
|
| 715 |
-
// English-only app (the hackathon is judged in English): the language is locked to "en".
|
| 716 |
-
setLang() {
|
| 717 |
-
this.lang = "en";
|
| 718 |
-
this._applyLang("en");
|
| 719 |
-
},
|
| 720 |
-
|
| 721 |
_applyLang(lang) {
|
| 722 |
document.documentElement.setAttribute("lang", lang);
|
| 723 |
document.querySelectorAll("[data-en],[data-es]").forEach((n) => {
|
| 724 |
if (n.hasAttribute("data-en") && !n.hasAttribute("data-es")) n.hidden = lang !== "en";
|
| 725 |
else if (n.hasAttribute("data-es") && !n.hasAttribute("data-en")) n.hidden = lang !== "es";
|
| 726 |
});
|
| 727 |
-
const lb = document.getElementById("lang-btn");
|
| 728 |
-
if (lb) lb.textContent = lang.toUpperCase();
|
| 729 |
this._setCount();
|
| 730 |
},
|
| 731 |
|
|
|
|
| 700 |
|
| 701 |
_about(show) {
|
| 702 |
const el = document.getElementById("about");
|
| 703 |
+
if (!el) return;
|
| 704 |
+
el.hidden = !show;
|
| 705 |
+
if (show) {
|
| 706 |
+
this._aboutReturn = document.activeElement;
|
| 707 |
+
const c = document.getElementById("about-close"); if (c) { try { c.focus(); } catch (_) {} }
|
| 708 |
+
this._aboutKey = (e) => { if (e.key === "Escape") this._about(false); };
|
| 709 |
+
document.addEventListener("keydown", this._aboutKey);
|
| 710 |
+
} else {
|
| 711 |
+
if (this._aboutKey) { document.removeEventListener("keydown", this._aboutKey); this._aboutKey = null; }
|
| 712 |
+
const r = this._aboutReturn; if (r && r.focus) { try { r.focus(); } catch (_) {} } // restore focus to the opener
|
| 713 |
+
}
|
| 714 |
},
|
| 715 |
|
| 716 |
setReducedMotion(on, fromOS) {
|
|
|
|
| 722 |
if (!fromOS) try { localStorage.setItem("ll.rm", this.reducedMotion ? "1" : "0"); } catch (_) {}
|
| 723 |
},
|
| 724 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 725 |
_applyLang(lang) {
|
| 726 |
document.documentElement.setAttribute("lang", lang);
|
| 727 |
document.querySelectorAll("[data-en],[data-es]").forEach((n) => {
|
| 728 |
if (n.hasAttribute("data-en") && !n.hasAttribute("data-es")) n.hidden = lang !== "en";
|
| 729 |
else if (n.hasAttribute("data-es") && !n.hasAttribute("data-en")) n.hidden = lang !== "es";
|
| 730 |
});
|
|
|
|
|
|
|
| 731 |
this._setCount();
|
| 732 |
},
|
| 733 |
|