355 lines
28 KiB
HTML
355 lines
28 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
|
|
<title>Quantum Forms — Q&A Session</title>
|
|
<style>
|
|
/* ★ THEME TOKENS — dual light/dark (Tailwind-style token layer, no CDN). Toggle sets [data-theme] on <html>. */
|
|
:root, html[data-theme="dark"] {
|
|
--bg:#0e1013; --header:#16181d; --surface:#1e2127; --input:#0a0b0d; --fg:#eef1f4; --muted:#8b929c;
|
|
--line:#2a2e35; --accent:#4f9bf0; --accent-2:#7c6cf0; --ok:#48bb78; --danger:#e05252;
|
|
--grad:linear-gradient(135deg,#4f9bf0,#7c6cf0); --grad-soft:linear-gradient(135deg,rgba(79,155,240,.14),rgba(124,108,240,.14));
|
|
--clock-bg:#eef1f4; --clock-fg:#0e1013;
|
|
}
|
|
html[data-theme="light"] {
|
|
--bg:#f6f7f9; --header:#ffffff; --surface:#ffffff; --input:#ffffff; --fg:#141821; --muted:#5b636e;
|
|
--line:#e2e5ea; --accent:#2563eb; --accent-2:#7c3aed; --ok:#16a34a; --danger:#dc2626;
|
|
--grad:linear-gradient(135deg,#2563eb,#7c3aed); --grad-soft:linear-gradient(135deg,rgba(37,99,235,.08),rgba(124,58,237,.08));
|
|
--clock-bg:#141821; --clock-fg:#ffffff;
|
|
}
|
|
* { box-sizing:border-box; -webkit-tap-highlight-color:transparent; }
|
|
html,body { margin:0; padding:0; height:100%; overflow:hidden; }
|
|
#app { height:100dvh; background:var(--bg); color:var(--fg); font-family:system-ui,-apple-system,Roboto,sans-serif;
|
|
display:flex; flex-direction:column; overflow:hidden; }
|
|
/* ★ SLIM mobile header (Driver: kill the white gap, ~44px bar). Left = title + neon E2E subheader; right = a
|
|
digital-clock countdown with tiny "Round N of M" + total under it. */
|
|
header { background:var(--header); background-image:var(--grad-soft); border-bottom:1px solid var(--line);
|
|
padding:3px 10px 3px; flex:0 0 auto;
|
|
display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:0; }
|
|
/* ★ FEATURE FLAG: the BROWSER (mobile web) needs NO top gap — the browser handles the status bar. Only a NATIVE
|
|
app wrapper (which can render under the camera/notch) needs the safe-area inset. Add .native-app on <html> to
|
|
re-enable the gap for the packaged mobile app; default (browser) = thin, no wasted space at the top. */
|
|
html.native-app header { padding-top:calc(3px + env(safe-area-inset-top)); }
|
|
.h-left { display:flex; flex-direction:column; min-width:0; }
|
|
.h-title { font-weight:700; font-size:.95rem; line-height:1.15; }
|
|
/* the E2E badge — uses the theme accent so it's visible in BOTH modes (was hardcoded teal, invisible on light) */
|
|
.h-neon { font-size:.6rem; letter-spacing:.4px; color:var(--accent); font-weight:600; line-height:1.2;
|
|
cursor:pointer; text-decoration:underline dotted; }
|
|
/* trust timeline panel (square, thin-line aesthetic) */
|
|
.trust-overlay { position:fixed; inset:0; background:rgba(0,0,0,.7); z-index:50; display:flex; align-items:flex-end;
|
|
justify-content:center; }
|
|
.trust-modal { background:var(--bg); border:1px solid var(--line); width:100%; max-width:520px; max-height:86dvh;
|
|
overflow-y:auto; padding:0; }
|
|
.trust-head { display:flex; justify-content:space-between; align-items:center; padding:12px 14px;
|
|
border-bottom:1px solid var(--line); position:sticky; top:0; background:var(--header); }
|
|
.trust-x { background:none; border:0; color:var(--muted); font-size:1.5rem; cursor:pointer; line-height:1; }
|
|
.trust-live { margin:12px 14px; padding:9px 11px; border:1px solid; font-size:.78rem; line-height:1.4; }
|
|
.trust-timeline { padding:4px 14px 12px; }
|
|
.tl-node { display:flex; gap:10px; align-items:flex-start; }
|
|
.tl-body b { font-size:.9rem; } .tl-sub { display:block; font-size:.76rem; color:var(--muted); margin-top:2px; line-height:1.45; }
|
|
.tl-line { display:flex; gap:10px; padding-left:0; }
|
|
.tl-linebar { width:20px; display:flex; justify-content:center; }
|
|
.tl-linebar::before { content:''; width:2px; background:linear-gradient(#5eead4,#63b3ed); margin:2px 0; }
|
|
.tl-hop { flex:1; border-left:2px solid var(--line); margin-left:9px; padding:6px 0 10px 12px; }
|
|
.tl-proto { font-weight:700; font-size:.82rem; color:#5eead4; }
|
|
.tl-what { font-size:.76rem; color:var(--fg); margin-top:3px; line-height:1.45; }
|
|
.tl-meta { font-size:.72rem; color:var(--muted); margin-top:4px; }
|
|
.tl-link { display:inline-block; margin-top:6px; font-size:.75rem; color:var(--accent); }
|
|
.trust-foot { padding:10px 14px; border-top:1px solid var(--line); font-size:.72rem; color:var(--muted); line-height:1.5; }
|
|
.gear-btn { background:none; border:0; color:var(--muted); cursor:pointer; padding:2px; flex:0 0 auto; order:2; }
|
|
.gear-btn:hover { color:var(--fg); }
|
|
.h-right { display:flex; flex-direction:column; align-items:flex-end; flex:0 0 auto; gap:1px; order:1; }
|
|
/* settings sheet */
|
|
.set-overlay { position:fixed; inset:0; background:rgba(0,0,0,.6); z-index:60; display:flex; align-items:flex-end; justify-content:center; }
|
|
.set-sheet { background:var(--header); color:var(--fg); border:1px solid var(--line); border-radius:10px 10px 0 0; width:100%; max-width:520px; max-height:80dvh; overflow-y:auto; }
|
|
.set-head { display:flex; justify-content:space-between; align-items:center; padding:11px 14px; border-bottom:1px solid var(--line); font-weight:700; }
|
|
.set-x { background:none; border:0; color:var(--muted); font-size:1.4rem; cursor:pointer; line-height:1; }
|
|
.set-body { padding:6px 16px 16px; }
|
|
.set-group { font-size:.66rem; text-transform:uppercase; letter-spacing:1.2px; color:var(--muted); font-weight:700;
|
|
margin:16px 0 4px; }
|
|
.set-group:first-child { margin-top:8px; }
|
|
.set-row { display:flex; justify-content:space-between; align-items:center; gap:14px; padding:12px 0;
|
|
border-bottom:1px solid var(--line); font-size:.9rem; }
|
|
.set-row:last-child { border-bottom:0; }
|
|
.set-row .lbl { min-width:0; } .set-row .lbl b { display:block; margin-bottom:2px; }
|
|
.set-row .lbl span { font-size:.76rem; color:var(--muted); line-height:1.4; }
|
|
.set-toggle { width:44px; height:24px; border-radius:12px; border:1px solid var(--line); background:var(--input); position:relative; cursor:pointer; flex:0 0 auto; }
|
|
.set-toggle::after { content:''; position:absolute; top:2px; left:2px; width:18px; height:18px; border-radius:50%; background:var(--muted); transition:.15s; }
|
|
.set-toggle.on { border-color:var(--accent); } .set-toggle.on::after { left:22px; background:var(--accent); }
|
|
/* ★ SQUARE DIGITAL ALARM CLOCK — a square box, inverse of the theme (white box on dark mode, dark digits),
|
|
with a digital 7-seg-style font. On dark mode: white/light box, dark digital text. */
|
|
.alarm-clock { background:var(--clock-bg); border:1px solid var(--clock-bg); border-radius:4px; padding:1px 7px; display:inline-flex; }
|
|
.digi-timer { font-family:"Courier New",ui-monospace,monospace; font-weight:700; font-size:1rem; letter-spacing:2px; color:var(--clock-fg); line-height:1.2; font-variant-numeric:tabular-nums; }
|
|
.round-line { font-size:.62rem; color:var(--accent); font-weight:700; line-height:1.15; }
|
|
.total-line { font-size:.55rem; color:var(--muted); line-height:1.1; }
|
|
/* light mode: box goes dark, digits go light (the inverse) */
|
|
/* alarm clock is token-driven now */
|
|
/* name + avatar preview on the join gate */
|
|
.name-row { display:flex; align-items:center; gap:8px; margin-top:8px; }
|
|
.name-row #name { flex:1; margin-top:0; }
|
|
.name-row .ident { border-radius:6px; flex:0 0 auto; }
|
|
#gate .hint { font-size:.72rem; color:var(--muted); margin:6px 0 0; }
|
|
.ident { border-radius:5px; flex:0 0 auto; }
|
|
.r-name { flex:1 1 auto; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
.mod-tag { font-size:.6rem; background:var(--accent); color:#fff; padding:1px 5px; border-radius:6px; flex:0 0 auto; }
|
|
/* ★ agent role badge + the GREEN CHECK (submitted = ready) in the roster */
|
|
.role-tag { font-size:.55rem; text-transform:uppercase; letter-spacing:.5px; background:var(--accent-2); color:#fff; padding:1px 5px; border-radius:6px; flex:0 0 auto; }
|
|
.r-item { display:flex; align-items:center; gap:6px; padding:4px 0; }
|
|
/* ★ (Driver) ready = a small green CHECK BADGE overlaid on the corner of the avatar image (not a standalone check) */
|
|
.avatar-wrap { position:relative; display:inline-flex; flex:0 0 auto; }
|
|
.avatar-ready { position:absolute; right:-3px; bottom:-3px; width:14px; height:14px; border-radius:50%;
|
|
background:var(--ok); color:#fff; font-size:.62rem; font-weight:800; line-height:14px; text-align:center;
|
|
border:2px solid var(--bg); box-shadow:0 0 0 1px var(--ok); }
|
|
|
|
/* join gate */
|
|
#gate { flex:1; display:flex; align-items:center; justify-content:center; padding:16px; }
|
|
#gate .box { width:100%; max-width:360px; text-align:center; }
|
|
#gate input { width:100%; padding:9px; border-radius:6px; border:1px solid var(--line); background:var(--input);
|
|
color:var(--fg); font-size:1rem; margin-top:8px; }
|
|
#gate input.code { letter-spacing:3px; text-transform:uppercase; text-align:center; }
|
|
#gate button { width:100%; margin-top:12px; padding:9px; border:0; border-radius:6px; background:var(--grad);
|
|
color:#fff; font-weight:600; cursor:pointer; }
|
|
|
|
/* ── the layout: TOP = questions-as-a-form (full width) · BOTTOM = who's-here + tabbed side panel ── */
|
|
#session { flex:1; display:none; grid-template-rows:1.35fr 1fr; grid-template-columns:1fr 1fr;
|
|
grid-template-areas: "questions questions" "roster sidePanel"; gap:2px; background:var(--line); min-height:0; }
|
|
/* ★ (Driver) solid divider lines between the sections — a clear border on each pane so the edges read as distinct. */
|
|
.pane { background:var(--bg); overflow-y:auto; padding:0 8px 8px; min-height:0; border:1px solid var(--line); } /* slightly more padding (Driver) */
|
|
#questions { border-bottom:2px solid var(--line); } /* the questions section gets a solid line under it */
|
|
.pane h3 { margin:0 -8px 6px; padding:5px 8px 4px; font-size:.68rem; text-transform:uppercase; letter-spacing:1px;
|
|
color:var(--muted); position:sticky; top:0; background:var(--header); border-bottom:1px solid var(--line); z-index:2; }
|
|
#questions { grid-area:questions; }
|
|
#roster { grid-area:roster; }
|
|
#sidePanel { grid-area:sidePanel; display:flex; flex-direction:column; padding:0; }
|
|
|
|
/* ── the tabbed side panel (Chat | Pending Decisions) ── */
|
|
.side-tabs { display:flex; position:sticky; top:0; z-index:2; background:var(--header); border-bottom:1px solid var(--line); }
|
|
/* ★ Fix #3 (Driver): tabs must NOT word-wrap, and the tab row is the SAME HEIGHT as the "Who's here" h3 header
|
|
(font-size + padding match .pane h3: .68rem, 5px 8px 4px). One line each. */
|
|
/* ★ (Driver) tabs size to their CONTENT — Chat isn't stretched to half the row; each tab is only as wide as its
|
|
label needs, sitting left-aligned. One line, same height as the "Who's here" h3. */
|
|
.side-tab { flex:0 0 auto; padding:5px 10px 4px; font-size:.68rem; text-transform:uppercase; letter-spacing:1px;
|
|
white-space:nowrap; background:transparent; color:var(--muted); border:none;
|
|
border-bottom:2px solid transparent; cursor:pointer; }
|
|
.side-tab.active { color:var(--fg); border-bottom-color:var(--accent); font-weight:600; }
|
|
.side-body { flex:1; display:flex; flex-direction:column; min-height:0; padding:6px 8px 8px; }
|
|
/* chat */
|
|
#chatLog { flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:1px; padding-bottom:4px; } /* tighter line spacing (Driver) */
|
|
/* ★ (Driver) smaller chat text + ALTERNATING row shading so each message is visually separated (like most chat apps) */
|
|
.chat-msg { font-size:.72rem; line-height:1.25; padding:2px 7px; border-radius:3px; } /* dense, tight line spacing — more use of space */
|
|
.chat-msg:nth-child(odd) { background:var(--surface); } /* every other message gets a subtle shade */
|
|
.chat-msg:nth-child(even) { background:transparent; }
|
|
.chat-msg .chat-name { font-weight:600; color:var(--accent); margin-right:5px; }
|
|
.chat-msg.mine .chat-name { color:var(--ok); }
|
|
/* ★ Fix #1 (Driver): the chat input is a FULL-WIDTH bar docked at the BOTTOM of the screen, riding right above the
|
|
keyboard (like a messaging app). The viewport meta uses interactive-widget=resizes-content, so when the keyboard
|
|
opens the layout shrinks and this bottom-fixed bar sits directly on top of the keyboard. Full screen width. */
|
|
/* ★ Fix #4 (Driver): the typing window DOCKS at the bottom of the panel and must NOT overlap the messages. It's the
|
|
last flex item in #chatBody (flex-column); #chatLog (flex:1) scrolls above it → no overlap, no floating. */
|
|
.chat-input-row { flex:0 0 auto; display:flex; gap:6px; margin-top:auto;
|
|
padding:8px calc(4px + env(safe-area-inset-left)) calc(8px + env(safe-area-inset-bottom));
|
|
background:var(--header); border-top:1px solid var(--line); }
|
|
/* ★ Fix #2 (Driver): NO send button — the input is FULL WIDTH; you submit with the keyboard's own send/enter key
|
|
(enterkeyhint="send" makes mobile keyboards show a Send key). Gray placeholder tells you what to type when empty. */
|
|
#chatInput { flex:1; width:100%; padding:9px 11px; font-size:.9rem; background:var(--input); color:var(--fg);
|
|
border:1px solid var(--line); border-radius:6px; } /* slim, slightly rounded */
|
|
#chatInput::placeholder { color:var(--muted); } /* gray hint text when empty */
|
|
/* ★ Fix #5 (Driver): when the chat input is FOCUSED, the bar breaks out to FULL SCREEN WIDTH, fixed above the
|
|
keyboard (not confined to the little bottom-right panel). On blur it collapses back into the panel. Slim. */
|
|
#app.kb-open .chat-input-row { position:fixed; left:0; right:0; bottom:0; z-index:45; padding:8px 10px calc(8px + env(safe-area-inset-bottom)); }
|
|
#app.kb-open #chatInput { font-size:1rem; padding:11px 13px; } /* comfortable to type while expanded */
|
|
/* ★ Fix (Driver): when the input is FIXED (kb-open), it's out of flow → it OVERLAPPED the chat log + roster so you
|
|
couldn't scroll to the bottom. Reserve space by padding the scrollable areas so their content clears the bar. */
|
|
#app.kb-open #chatLog { padding-bottom:64px; }
|
|
#app.kb-open #rList { padding-bottom:64px; }
|
|
#app.kb-open #qList { padding-bottom:64px; }
|
|
/* chat log scrolls above the docked input; flex:1 already set below. no padding hack needed now (no overlap). */
|
|
/* decisions */
|
|
#decisionsList { flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:5px; }
|
|
.decision-row { display:flex; justify-content:space-between; align-items:center; padding:8px; background:var(--surface);
|
|
border:1px solid var(--line); border-radius:4px; cursor:pointer; font-size:.86rem; }
|
|
.decision-row:hover { border-color:var(--accent); }
|
|
.decision-status { font-size:.68rem; text-transform:uppercase; letter-spacing:.5px; padding:2px 7px; border-radius:3px; }
|
|
.decision-status.solved { background:rgba(72,187,120,.2); color:var(--ok); }
|
|
.decision-status.open { background:rgba(237,137,54,.2); color:var(--danger); }
|
|
/* the centered mobile-friendly decision modal */
|
|
.modal-overlay { position:fixed; inset:0; background:rgba(0,0,0,.55); display:flex; align-items:center;
|
|
justify-content:center; z-index:50; padding:16px; }
|
|
.modal-card { background:var(--header); color:var(--fg); border:1px solid var(--line); border-radius:10px;
|
|
width:100%; max-width:440px; max-height:85vh; overflow-y:auto; padding:16px; }
|
|
.modal-head { display:flex; justify-content:space-between; align-items:center; font-weight:600; margin-bottom:8px; }
|
|
.modal-x { background:none; border:none; color:var(--muted); font-size:1rem; cursor:pointer; }
|
|
.modal-desc { font-size:.9rem; color:var(--muted); margin-bottom:10px; }
|
|
.modal-answers { margin-bottom:10px; font-size:.86rem; }
|
|
.modal-answers summary { cursor:pointer; color:var(--accent); }
|
|
#dmTake { width:100%; padding:8px; background:var(--input); color:var(--fg); border:1px solid var(--line);
|
|
border-radius:4px; font-family:inherit; margin-bottom:8px; }
|
|
#dmSubmit { width:100%; padding:9px; background:var(--accent); color:var(--bg); border:none; border-radius:4px; cursor:pointer; }
|
|
|
|
/* skribbl-style "submitted" pills above the questions */
|
|
.answered-bar { display:flex; flex-wrap:wrap; gap:5px; margin-bottom:8px; }
|
|
.answered-pill { font-size:.72rem; background:rgba(72,187,120,.2); color:var(--ok); border:1px solid var(--ok);
|
|
border-radius:5px; padding:2px 9px; }
|
|
.q-item { background:var(--surface); border-radius:5px; padding:6px 8px; margin-bottom:4px; font-size:.92rem;
|
|
display:flex; align-items:flex-start; justify-content:space-between; gap:8px; }
|
|
.q-item .q-num { color:var(--accent); font-weight:700; margin-right:6px; }
|
|
/* ★ the answer textarea directly under its question (form layout): full width, 1 row tall by default, resizable,
|
|
slightly rounded (not bubbly) per the moderator's aesthetic. */
|
|
.q-answer { margin:0 0 8px; }
|
|
.q-answer-box { width:100%; box-sizing:border-box; padding:8px; font-family:inherit; font-size:.9rem; resize:vertical;
|
|
background:var(--input); color:var(--fg); border:1px solid var(--line); border-radius:4px; min-height:38px; }
|
|
.q-answer-box:focus { outline:none; border-color:var(--accent); }
|
|
.notes-block { margin-top:8px; border-top:1px dashed var(--line); padding-top:8px; }
|
|
.notes-label { font-size:.72rem; color:var(--muted); margin-bottom:3px; }
|
|
.q-vote { display:flex; gap:4px; flex:0 0 auto; }
|
|
.vote-btn { border:1px solid var(--line); background:var(--input); border-radius:5px; padding:2px 5px; font-size:.95rem;
|
|
cursor:pointer; opacity:.85; color:var(--fg); } /* color:var(--fg) so the SVG (currentColor) is VISIBLE */
|
|
.vote-btn svg { fill:currentColor; }
|
|
.vote-btn.active.up { opacity:1; border-color:var(--ok); background:rgba(72,187,120,.18); color:var(--ok); }
|
|
.vote-btn.active.down { opacity:1; border-color:#e05252; background:rgba(224,82,82,.18); color:#e05252; }
|
|
.vote-btn.spk { opacity:.8; }
|
|
/* "Play all questions" — reads them aloud on-device (TTS, no cloud) */
|
|
.play-all { width:100%; margin-bottom:4px; padding:8px; border:1px solid var(--accent); border-radius:5px;
|
|
background:rgba(49,130,206,.12); color:var(--accent); font-weight:600; font-size:.85rem; cursor:pointer;
|
|
display:flex; align-items:center; justify-content:center; gap:6px; }
|
|
.vote-btn svg, .play-all svg { display:block; }
|
|
/* lightweight Talk/Type tabs */
|
|
.mode-tabs { display:flex; gap:4px; margin-bottom:4px; }
|
|
.mode-tab { flex:1; padding:4px; border:1px solid var(--line); background:var(--input); color:var(--muted);
|
|
border-radius:0; font-size:.8rem; font-weight:600; cursor:pointer; }
|
|
.mode-tab.active { background:var(--grad); color:#fff; border-color:var(--accent); }
|
|
/* talk-mode mic panel (streaming transcription indicator + live text) */
|
|
.talk-panel { display:flex; flex-direction:column; gap:8px; align-items:center; padding:14px 8px; }
|
|
.talk-mic { width:56px; height:56px; border-radius:50%; border:2px solid var(--accent); background:rgba(49,130,206,.12);
|
|
color:var(--accent); display:flex; align-items:center; justify-content:center; cursor:pointer; }
|
|
.talk-mic.live { animation:pulse 1.1s ease-in-out infinite; border-color:var(--ok); color:var(--ok); }
|
|
.talk-transcript { width:100%; min-height:80px; border:1px solid var(--line); border-radius:0; background:var(--input);
|
|
padding:8px 10px; font-size:.9rem; color:var(--fg); }
|
|
.talk-note { font-size:.7rem; color:var(--muted); text-align:center; }
|
|
.r-item { display:flex; align-items:center; gap:8px; padding:5px 6px; border-radius:0; font-size:.9rem; }
|
|
.r-item .dot { width:8px; height:8px; border-radius:50%; background:var(--ok); flex:0 0 auto; }
|
|
.r-item.me { background:rgba(49,130,206,.15); }
|
|
.a-block { margin-bottom:5px; }
|
|
.a-block label { display:block; font-size:.7rem; color:var(--muted); margin-bottom:2px; }
|
|
.a-block textarea { width:100%; min-height:64px; resize:vertical; padding:6px 8px; border-radius:0;
|
|
border:1px solid var(--line); background:var(--input); color:var(--fg); font:inherit; font-size:.9rem; }
|
|
#submitAns { width:100%; margin-top:6px; padding:8px; border:0; border-radius:0; background:var(--grad);
|
|
color:#fff; font-weight:600; cursor:pointer; }
|
|
#submitAns:disabled { opacity:.5; }
|
|
.waiting { font-size:.82rem; color:var(--muted); text-align:center; padding:10px; }
|
|
.agent-thinking { text-align:center; color:var(--accent); font-size:.95rem; padding:24px 10px; line-height:1.5;
|
|
animation:pulse 1.4s ease-in-out infinite; }
|
|
@keyframes pulse { 0%,100%{opacity:.55} 50%{opacity:1} }
|
|
/* ★ QF-29 waiting-on-others dim overlay: a muted panel with the waiting message; makes it clear you're waiting, not broken. */
|
|
.waiting-dim { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:14px;
|
|
min-height:180px; border-radius:10px; background:var(--surface); border:1px dashed var(--line); padding:20px; }
|
|
/* ★ QF-29 review escape: a button to go back and re-read the questions while waiting on others */
|
|
.review-btn { background:transparent; color:var(--accent); border:1px solid var(--accent); border-radius:8px;
|
|
padding:7px 14px; font-size:.82rem; cursor:pointer; }
|
|
.review-btn:hover { background:var(--grad-soft); }
|
|
.review-banner { display:flex; justify-content:space-between; align-items:center; gap:10px; font-size:.78rem;
|
|
color:var(--muted); padding:6px 2px 10px; border-bottom:1px solid var(--line); margin-bottom:8px; }
|
|
.review-q { opacity:.85; }
|
|
/* ★ (Driver) loading spinner for the 'bundling + generating next round' screen (round_complete / QF-24) */
|
|
.spinner { width:34px; height:34px; border-radius:50%; border:3px solid var(--line); border-top-color:var(--accent);
|
|
animation:spin .8s linear infinite; }
|
|
@keyframes spin { to { transform:rotate(360deg); } }
|
|
.toast { position:fixed; bottom:20px; left:50%; transform:translateX(-50%); background:#000c; color:#fff;
|
|
padding:9px 16px; border-radius:20px; font-size:.85rem; display:none; z-index:10; }
|
|
/* the treasure-chest DONE screen */
|
|
/* ★ SCROLL FIX (Driver: 'can't scroll up to see the whole done page'): justify-content:center + overflow-y:auto is a
|
|
flexbox trap — when content is taller than the viewport, centering pushes the TOP above the scroll area so you
|
|
can't reach it. Use flex-start (content starts at top, scrolls fully) with generous vertical padding so a SHORT
|
|
summary still sits nicely; a long one scrolls from the top. */
|
|
.done-wrap { flex:1; display:flex; flex-direction:column; align-items:center; justify-content:flex-start;
|
|
text-align:center; padding:32px 24px 40px; gap:10px; overflow-y:auto; -webkit-overflow-scrolling:touch; }
|
|
.done-chest { font-size:4rem; }
|
|
.done-wrap h2 { margin:0; font-weight:700; }
|
|
.done-stats { color:var(--muted); font-size:.95rem; margin:0; }
|
|
.done-stats b { color:var(--accent); }
|
|
.done-summary { max-width:520px; text-align:left; font-size:.9rem; line-height:1.5; white-space:pre-wrap;
|
|
background:var(--surface); border-radius:12px; padding:14px; margin:6px 0; }
|
|
#dlQA { padding:13px 22px; border:0; border-radius:0; background:var(--grad); color:#062012; font-weight:700;
|
|
font-size:1rem; cursor:pointer; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="app">
|
|
<header>
|
|
<div class="h-left">
|
|
<span class="h-title">Quantum Forms QA</span>
|
|
<span class="h-neon">quantum-resistant · E2E encrypted</span>
|
|
</div>
|
|
<button class="gear-btn" id="gearBtn" title="Settings"><svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor"><path d="M19.4 13a7.8 7.8 0 0 0 0-2l2-1.6a.5.5 0 0 0 .1-.6l-1.9-3.3a.5.5 0 0 0-.6-.2l-2.4 1a7.3 7.3 0 0 0-1.7-1l-.4-2.5a.5.5 0 0 0-.5-.4h-3.8a.5.5 0 0 0-.5.4L9 4.7a7.3 7.3 0 0 0-1.7 1l-2.4-1a.5.5 0 0 0-.6.2L2.4 8.2a.5.5 0 0 0 .1.6L4.6 11a7.8 7.8 0 0 0 0 2l-2 1.6a.5.5 0 0 0-.1.6l1.9 3.3a.5.5 0 0 0 .6.2l2.4-1a7.3 7.3 0 0 0 1.7 1l.4 2.5a.5.5 0 0 0 .5.4h3.8a.5.5 0 0 0 .5-.4l.4-2.5a7.3 7.3 0 0 0 1.7-1l2.4 1a.5.5 0 0 0 .6-.2l1.9-3.3a.5.5 0 0 0-.1-.6zM12 15.5A3.5 3.5 0 1 1 12 8.5a3.5 3.5 0 0 1 0 7z"/></svg></button>
|
|
<div class="h-right">
|
|
<div class="alarm-clock"><span class="digi-timer" id="timer">--:--</span></div>
|
|
<span class="round-line" id="roundNum">Round —</span>
|
|
<span class="total-line" id="totalLine"></span>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- join gate: code + auto-generated name + avatar preview -->
|
|
<div id="gate">
|
|
<div class="box">
|
|
<h2>Join the session</h2>
|
|
<input class="code" id="code" maxlength="12" placeholder="CODE" autocapitalize="characters" autocomplete="off">
|
|
<div class="name-row">
|
|
<span id="namePreview"></span>
|
|
<input id="name" maxlength="40" placeholder="Your name" autocomplete="off">
|
|
</div>
|
|
<p class="hint">A name + avatar are generated for you — edit if you like.</p>
|
|
<button id="joinBtn">Join</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- the session: TOP = questions-as-a-form (each question has its answer box under it) · BOTTOM-LEFT = who's here ·
|
|
BOTTOM-RIGHT = a tabbed panel (Chat | Pending Decisions) -->
|
|
<div id="session">
|
|
<!-- TOP: the form — questions with their answer textareas directly underneath (the form-layout redesign) -->
|
|
<div class="pane" id="questions">
|
|
<h3>Questions</h3>
|
|
<!-- ★ (Driver) Type/Talk tabs removed — the panel just outputs the questions as a form. -->
|
|
<div id="qList"><div class="waiting">Waiting for the first round…</div></div>
|
|
<button id="submitAns" disabled style="display:none">Submit answers</button>
|
|
<div class="waiting" id="waitMsg" style="display:none"></div>
|
|
</div>
|
|
<!-- BOTTOM-LEFT: who's here -->
|
|
<div class="pane" id="roster"><h3>Who's here</h3><div id="rList"></div></div>
|
|
<!-- BOTTOM-RIGHT: the tabbed side panel — Chat + Pending Decisions -->
|
|
<div class="pane" id="sidePanel">
|
|
<div class="side-tabs">
|
|
<button class="side-tab active" id="tabChat" data-side="chat">Chat</button>
|
|
<button class="side-tab" id="tabDecisions" data-side="decisions">Decisions</button>
|
|
</div>
|
|
<!-- Chat tab -->
|
|
<div class="side-body" id="chatBody">
|
|
<div id="chatLog"></div>
|
|
<div class="chat-input-row">
|
|
<input id="chatInput" type="text" enterkeyhint="send" placeholder="Type a message…" maxlength="2000" autocomplete="off" />
|
|
</div>
|
|
</div>
|
|
<!-- Decisions tab (the conflicting-answer decisions table) -->
|
|
<div class="side-body" id="decisionsBody" style="display:none">
|
|
<div id="decisionsList"><div class="waiting">No decisions yet. When your answers to the same question disagree, it shows up here as a decision to resolve.</div></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<!-- decision modal (mobile-friendly, centered) -->
|
|
<div id="decisionModal" class="modal-overlay" style="display:none">
|
|
<div class="modal-card">
|
|
<div class="modal-head"><span id="dmTitle"></span><button id="dmClose" class="modal-x">✕</button></div>
|
|
<div id="dmDesc" class="modal-desc"></div>
|
|
<details class="modal-answers"><summary>Conflicting answers so far</summary><div id="dmAnswers"></div></details>
|
|
<textarea id="dmTake" placeholder="Submit your take on this decision…" rows="3"></textarea>
|
|
<button id="dmSubmit">Submit my take</button>
|
|
</div>
|
|
</div>
|
|
<div class="toast" id="toast"></div>
|
|
</div>
|
|
<script src="./trust-panel.js?v=7"></script>
|
|
<!-- ★ CACHE-BUST: bump this ?v= on EVERY assembly.js change, or browsers keep serving the stale cached bundle even
|
|
with no-cache headers (the moderator kept seeing old code because ?v=37 never changed across ~a dozen ships). -->
|
|
<script type="module" src="./assembly.js?v=60"></script>
|
|
</body>
|
|
</html>
|