Spaces:
Running on Zero
Running on Zero
Gate the intro camera descent behind a 'click to start' button
Browse filesThe descent auto-fired HOLD_MS after load; now the camera holds on the sky until the user clicks a 'click to start' button on the title overlay (a glass pill matching the subtitle, revealed once assets + fonts are ready). The same click is the user gesture that also starts the lobby music. Drops the now-unused HOLD_MS and the auto-descent timer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- frontend/index.html +1 -0
- frontend/main.js +6 -2
- frontend/style.css +50 -0
frontend/index.html
CHANGED
|
@@ -70,6 +70,7 @@
|
|
| 70 |
<div class="subtitle-wrap">
|
| 71 |
<p id="subtitle">♪ chill beats, freshly vended</p>
|
| 72 |
</div>
|
|
|
|
| 73 |
</div>
|
| 74 |
<div id="vending-label" class="hover-label"><span>♪ vend a vibe</span></div>
|
| 75 |
<div id="collection-label" class="hover-label">
|
|
|
|
| 70 |
<div class="subtitle-wrap">
|
| 71 |
<p id="subtitle">♪ chill beats, freshly vended</p>
|
| 72 |
</div>
|
| 73 |
+
<button id="start-btn" type="button">▶ click to start</button>
|
| 74 |
</div>
|
| 75 |
<div id="vending-label" class="hover-label"><span>♪ vend a vibe</span></div>
|
| 76 |
<div id="collection-label" class="hover-label">
|
frontend/main.js
CHANGED
|
@@ -539,7 +539,6 @@ import("https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js")
|
|
| 539 |
// Intro: hold on the sky, then ease the gaze down to the scene
|
| 540 |
// ---------------------------------------------------------------------------
|
| 541 |
|
| 542 |
-
const HOLD_MS = 3000; // sky time after everything (fonts included) has loaded
|
| 543 |
const DESCENT_MS = 4600;
|
| 544 |
|
| 545 |
const intro = { phase: "loading", t0: 0, idleStart: 0 };
|
|
@@ -558,9 +557,14 @@ const pageLoaded = new Promise((resolve) =>
|
|
| 558 |
? resolve()
|
| 559 |
: window.addEventListener("load", resolve),
|
| 560 |
);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 561 |
Promise.all([pageLoaded, document.fonts.ready]).then(() =>
|
| 562 |
-
|
| 563 |
);
|
|
|
|
| 564 |
|
| 565 |
const easeInOutCubic = (t) =>
|
| 566 |
t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
|
|
| 539 |
// Intro: hold on the sky, then ease the gaze down to the scene
|
| 540 |
// ---------------------------------------------------------------------------
|
| 541 |
|
|
|
|
| 542 |
const DESCENT_MS = 4600;
|
| 543 |
|
| 544 |
const intro = { phase: "loading", t0: 0, idleStart: 0 };
|
|
|
|
| 557 |
? resolve()
|
| 558 |
: window.addEventListener("load", resolve),
|
| 559 |
);
|
| 560 |
+
// Click-to-start: the camera holds on the sky until the user clicks. The button
|
| 561 |
+
// is revealed (and made clickable) once assets + fonts are ready; the same click
|
| 562 |
+
// also kicks off the lobby music (audio autoplay needs a user gesture).
|
| 563 |
+
const startBtn = document.getElementById("start-btn");
|
| 564 |
Promise.all([pageLoaded, document.fonts.ready]).then(() =>
|
| 565 |
+
startBtn.classList.add("ready"),
|
| 566 |
);
|
| 567 |
+
startBtn.addEventListener("click", startDescent);
|
| 568 |
|
| 569 |
const easeInOutCubic = (t) =>
|
| 570 |
t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
frontend/style.css
CHANGED
|
@@ -109,6 +109,56 @@ body {
|
|
| 109 |
}
|
| 110 |
}
|
| 111 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 112 |
@keyframes title-bob {
|
| 113 |
from {
|
| 114 |
transform: translateY(0);
|
|
|
|
| 109 |
}
|
| 110 |
}
|
| 111 |
|
| 112 |
+
#start-btn {
|
| 113 |
+
margin-top: 2.4rem;
|
| 114 |
+
pointer-events: none; /* the overlay ignores pointers; we opt in once ready */
|
| 115 |
+
cursor: pointer;
|
| 116 |
+
font-family: "Baloo 2", sans-serif;
|
| 117 |
+
font-weight: 700;
|
| 118 |
+
font-size: clamp(0.95rem, 2.2vw, 1.25rem);
|
| 119 |
+
letter-spacing: 0.12em;
|
| 120 |
+
text-transform: lowercase;
|
| 121 |
+
color: #ffffff;
|
| 122 |
+
padding: 0.7em 1.9em;
|
| 123 |
+
border-radius: 999px;
|
| 124 |
+
background: rgba(255, 255, 255, 0.16);
|
| 125 |
+
border: 1px solid rgba(255, 255, 255, 0.4);
|
| 126 |
+
backdrop-filter: blur(6px);
|
| 127 |
+
-webkit-backdrop-filter: blur(6px);
|
| 128 |
+
/* fades in via opacity only (a transform here would kill the backdrop blur,
|
| 129 |
+
per the subtitle note above); the glow uses box-shadow, also transform-free */
|
| 130 |
+
opacity: 0;
|
| 131 |
+
transition:
|
| 132 |
+
opacity 0.8s ease,
|
| 133 |
+
background 0.2s ease,
|
| 134 |
+
border-color 0.2s ease;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
#start-btn.ready {
|
| 138 |
+
opacity: 1;
|
| 139 |
+
pointer-events: auto;
|
| 140 |
+
animation: start-glow 2.6s ease-in-out 0.6s infinite;
|
| 141 |
+
}
|
| 142 |
+
|
| 143 |
+
#start-btn:hover {
|
| 144 |
+
background: rgba(255, 255, 255, 0.3);
|
| 145 |
+
border-color: rgba(255, 255, 255, 0.7);
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
#start-btn:active {
|
| 149 |
+
background: rgba(255, 255, 255, 0.4);
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
@keyframes start-glow {
|
| 153 |
+
0%,
|
| 154 |
+
100% {
|
| 155 |
+
box-shadow: 0 0 0 0 rgba(173, 211, 255, 0);
|
| 156 |
+
}
|
| 157 |
+
50% {
|
| 158 |
+
box-shadow: 0 0 22px 2px rgba(173, 211, 255, 0.5);
|
| 159 |
+
}
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
@keyframes title-bob {
|
| 163 |
from {
|
| 164 |
transform: translateY(0);
|