prototype: slide-to-create copy, no plate, building state + continue mock (#356)
- Header "Slide to create" (mirroring slide-to-unlock), sub "Your new space will be created immediately." - The fader sits directly on the card — the quiet plate behind it is gone. The hint line and the all-caps ELEVEN flourish are cut. - New building state between the snap and the confirmation: a spinner + "Building your space…" for ~5s (the real create time; tweakable via a new build-ms slider) — in production this covers the POST /api/spaces round-trip, which returns only once the space is confirmed reachable. - The confirmation now mocks the real primary action: a full-width "Continue to new space →" above the quiet "Slide it again" reset.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 file changed,
+52
−18
prototypes/space-lever.html+52 −18
diff --git a/prototypes/space-lever.html b/prototypes/space-lever.html| index bb1b2e6..7b1d40c 100644 |
| --- a/prototypes/space-lever.html |
| +++ b/prototypes/space-lever.html |
| @@ -58,11 +58,8 @@ |
| h1 { font-size: 23px; font-weight: 700; line-height: 1.2; letter-spacing: -0.015em; margin: 4px 0 6px; } |
| p.sub { color: var(--muted); font-size: 14px; margin: 0 0 22px; } |
| - /* ---- the channel strip --------------------------------------------------- */ |
| - .strip { |
| - background: var(--quiet); border: 1px solid var(--border); |
| - border-radius: 14px; padding: 16px 20px 12px; |
| - } |
| + /* ---- the channel strip (no plate — the fader sits right on the card) ----- */ |
| + .strip { padding: 4px 2px 10px; } |
| /* VU ladder: one LED per notch. Unlit LEDs are faint wells; lit ones glow in |
| desk colours — green through 7, amber 8–9, red at 10, and ELEVEN is red and |
| furious. A touch of flicker while the signal's hot sells the VU. */ |
| @@ -126,10 +123,29 @@ |
| .fader.dragging .cap { cursor: grabbing; } |
| .cap:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } |
| - .hint { margin: 30px 0 0; font-size: 13px; color: var(--muted); } |
| + /* building pane: shown while the instance provisions (~5s in production — |
| + the API returns only once the space is confirmed reachable) */ |
| + #building { display: none; padding: 18px 0 6px; } |
| + .build-spin { |
| + width: 34px; height: 34px; margin: 4px auto 14px; border-radius: 50%; |
| + border: 3px solid color-mix(in srgb, var(--ink) 12%, transparent); |
| + border-top-color: var(--accent); |
| + animation: spin .7s linear infinite; |
| + } |
| + @keyframes spin { to { transform: rotate(360deg); } } |
| + #building h2 { font-size: 17px; font-weight: 700; margin: 0 0 4px; } |
| + #building p { color: var(--muted); font-size: 14px; margin: 0; } |
| + @media (prefers-reduced-motion: reduce) { .build-spin { animation: none; opacity: .5; } } |
| /* success pane (single-serve confirmation mock) */ |
| #done { display: none; } |
| + a.btn-link { |
| + display: block; font: inherit; font-weight: 600; text-align: center; |
| + text-decoration: none; border-radius: 999px; padding: 11px 18px; |
| + background: var(--accent); color: var(--accent-ink); line-height: 1.2; |
| + margin-bottom: 10px; |
| + } |
| + a.btn-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; } |
| .ok-check { |
| width: 46px; height: 46px; margin: 6px auto 12px; border-radius: 50%; |
| display: grid; place-items: center; background: var(--ok); color: #fff; |
| @@ -160,23 +176,29 @@ |
| <main class="card" id="card"> |
| <div id="pitch"> |
| - <h1>Create a private space in one click.</h1> |
| - <p class="sub">Well… one slide.</p> |
| + <h1>Slide to create</h1> |
| + <p class="sub">Your new space will be created immediately.</p> |
| <div class="strip"> |
| <div class="meter" id="meter"></div> |
| <div class="fader" id="fader"> |
| <div class="slot"></div> |
| - <div class="cap" id="cap" role="slider" aria-label="Slide to eleven" |
| + <div class="cap" id="cap" role="slider" aria-label="Slide to create" |
| aria-valuemin="1" aria-valuemax="11" aria-valuenow="1" tabindex="0"><div class="stripe"></div></div> |
| </div> |
| </div> |
| - <p class="hint" id="hint">Slide the fader to eleven</p> |
| + </div> |
| + |
| + <div id="building"> |
| + <div class="build-spin" aria-hidden="true"></div> |
| + <h2>Building your space…</h2> |
| + <p>Getting it ready takes a few seconds.</p> |
| </div> |
| <div id="done"> |
| <div class="ok-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></div> |
| <h2>Your space is ready</h2> |
| - <p>(Prototype — nothing was provisioned. This is where the confirmation pane goes.)</p> |
| + <p>(Prototype — nothing was provisioned. In production this pane carries the one-time setup link.)</p> |
| + <a class="btn-link" href="#" onclick="return false">Continue to new space →</a> |
| <button class="quiet" id="reset">Slide it again</button> |
| </div> |
| </main> |
| @@ -188,6 +210,7 @@ |
| <label>cranked <input id="p-crank" type="range" min="1" max="5" step="0.1" value="2.3"><span class="v" id="v-crank">2.3</span></label> |
| <label>dust kick <input id="p-kick" type="range" min="0" max="0.6" step="0.02" value="0.2"><span class="v" id="v-kick">0.20</span></label> |
| <label>motes <input id="p-motes" type="range" min="0" max="220" step="10" value="90"><span class="v" id="v-motes">90</span></label> |
| + <label>build ms <input id="p-build" type="range" min="500" max="9000" step="250" value="5000"><span class="v" id="v-build">5000</span></label> |
| <button id="p-reset">Reset fader</button> |
| </div> |
| @@ -199,6 +222,7 @@ const P = { |
| crank: 2.3, // shake multiplier AT eleven — shell.js CRANKED |
| kick: 0.2, // dust kick odds scale — shell.js used level*0.2 |
| motes: 90, // particle count |
| + build: 5000, // ms the mock "building your space" state shows (~real create time) |
| }; |
| const GRAVITY = 0.62; // shell.js DUST_GRAVITY |
| const RUN_END = 0.86; // 1..10 live in the first 86% of the slot; 11 past the gap |
| @@ -342,11 +366,12 @@ function tick(t) { |
| } |
| // The finish: wild for 620ms, bow for 330ms (dust fades mid-flight), then the |
| - // confirmation pane. Timing lifted from clickDialToEleven. |
| + // building state (the ~5s the real create takes) and the confirmation pane. |
| + // Snap timing lifted from clickDialToEleven. |
| if (snapped) { |
| const dtSnap = t - snapT0; |
| if (dust) dust.fade = Math.min(1, Math.max(0, 1 - (dtSnap - 620) / 330)); |
| - if (dtSnap > 950) { showDone(); return; } // stop the loop; pane is static |
| + if (dtSnap > 950) { showBuilding(); return; } // stop the loop; panes are static |
| } |
| paint(); |
| @@ -354,18 +379,26 @@ function tick(t) { |
| } |
| function snapToEleven() { |
| - // No travel, no ease — one frame it's at 10, the next it's AT ELEVEN. |
| + // No travel, no ease — one frame it's at 10, the next it's AT eleven. |
| snapped = true; |
| value = 10; |
| pressure = 0; |
| snapT0 = performance.now(); |
| - $("hint").textContent = "ELEVEN."; |
| } |
| -function showDone() { |
| +// In production this pane covers the POST /api/spaces round-trip (~5s — the API |
| +// returns only once the space is confirmed reachable). Here a timer stands in. |
| +let buildTimer = 0; |
| +function showBuilding() { |
| card.style.transform = ""; |
| if (dust) { dust.fade = 0; stepDust(0); } |
| $("pitch").style.display = "none"; |
| + $("building").style.display = "block"; |
| + buildTimer = setTimeout(showDone, P.build); |
| +} |
| + |
| +function showDone() { |
| + $("building").style.display = "none"; |
| $("done").style.display = "block"; |
| } |
| @@ -411,11 +444,12 @@ cap.addEventListener("keyup", () => { overpull = false; }); |
| // ---- reset + panel --------------------------------------------------------------- |
| function resetLever() { |
| + clearTimeout(buildTimer); |
| snapped = false; value = 1; pressure = 0; overpull = false; |
| if (dust) dust.fade = 1; |
| $("pitch").style.display = ""; |
| + $("building").style.display = "none"; |
| $("done").style.display = "none"; |
| - $("hint").textContent = "Slide the fader to eleven"; |
| lastT = performance.now(); |
| requestAnimationFrame(tick); |
| } |
| @@ -430,7 +464,7 @@ function bind(id, key, fmt) { |
| }); |
| } |
| bind("hold", "hold"); bind("shake", "shake"); bind("crank", "crank"); |
| -bind("kick", "kick", (v) => v.toFixed(2)); bind("motes", "motes"); |
| +bind("kick", "kick", (v) => v.toFixed(2)); bind("motes", "motes"); bind("build", "build"); |
| addEventListener("resize", initDust); |
| initDust(); |