eleven / messenger Public

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>
Paul Campbell a7685995f4a08d81e9d67a1e294f018a5e250255 parent 75d32f5
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();