Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>TuringDNA Β· Assistant</title> | |
| <meta name="description" content="TuringDNA Assistant β a conversational orchestrator for the Design β Build β Edit β Learn loop. Talk to the engine; it runs your tools." /> | |
| <meta name="theme-color" content="#1b1a17" /> | |
| <meta name="color-scheme" content="dark light" /> | |
| <meta name="referrer" content="strict-origin-when-cross-origin" /> | |
| <meta name="robots" content="noindex,follow" /> | |
| <!-- Theme bootstrap β external (not inline) so it clears the site CSP | |
| (script-src 'self' 'nonce-β¦' refuses un-nonced inline scripts on a | |
| statically-served page). Loaded synchronously in <head> BEFORE app.css | |
| so <html data-theme> is set before first paint (no flash). --> | |
| <script src="/static/assistant-boot.js?v=20260707-repaint2"></script> | |
| <!-- Reuse the engine's editorial design system verbatim: same tokens, same | |
| quad font stack (Playfair / Source Serif / Inter / JetBrains Mono), same | |
| warm-paper Γ graphite palette + dark theme. This page only ADDS a | |
| chat-workspace layout scoped under `.asst`. --> | |
| <link rel="stylesheet" href="/static/app.css?v=20260707-paintkick" /> | |
| <link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=2" /> | |
| <link rel="apple-touch-icon" href="/static/favicon.svg?v=2" /> | |
| <style> | |
| /* ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| TuringDNA Β· Assistant β a conversational orchestrator surface. | |
| Structure mirrors a professional coding agent (Claude Code): a left rail | |
| of recent conversations, a phase-aware header, a reading-width thread of | |
| turns with inline tool-execution cards, and a docked composer. | |
| Every value below is drawn from app.css tokens so it stays in lockstep | |
| with the rest of the OS. | |
| ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ */ | |
| * { box-sizing: border-box; } | |
| html, body { height: 100%; } | |
| body { | |
| margin: 0; | |
| background: var(--bg-app); | |
| color: var(--ink); | |
| font-family: var(--font-ui); | |
| -webkit-font-smoothing: antialiased; | |
| } | |
| .asst { | |
| display: flex; | |
| /* --vh (set in assistant-boot.js, before first paint) is | |
| window.innerHeight-based rather than the vh/dvh unit itself β | |
| Safari 15.6.1 was resolving 100dvh taller than this iframe's real | |
| visible box, clipping the composer at the bottom. var(..., 1vh) | |
| falls back to the native unit for the one frame before that | |
| script runs. */ | |
| height: calc(var(--vh, 1vh) * 100); | |
| position: relative; | |
| z-index: 1; | |
| } | |
| /* βββββββββββββββββββββ HISTORY (dropdown, not a rail) βββββββββββββββββ | |
| Recent conversations used to live in a permanent left rail β a second | |
| sidebar sitting right next to the engine's own tool nav. Now they're a | |
| compact popover anchored under the conversation title: one nav rail | |
| total (the engine's), and switching chats is one click away instead of | |
| a permanent fixture. */ | |
| .asst-back { | |
| display: inline-flex; align-items: center; justify-content: center; | |
| width: 26px; height: 26px; flex: none; | |
| color: var(--ink-soft); border-radius: var(--r-2); | |
| transition: background var(--t-fast), color var(--t-fast); | |
| } | |
| .asst-back:hover { color: var(--ink-strong); background: var(--bg-hover); } | |
| .asst-title-btn { | |
| display: flex; align-items: center; gap: 6px; min-width: 0; | |
| padding: 4px 8px 4px 6px; margin-left: -6px; | |
| background: none; border: none; border-radius: var(--r-2); | |
| cursor: pointer; text-align: left; | |
| transition: background var(--t-fast); | |
| } | |
| .asst-title-btn:hover { background: var(--bg-hover); } | |
| .asst-title-chev { | |
| width: 13px; height: 13px; flex: none; color: var(--ink-faint); | |
| transition: transform var(--t-fast) var(--ease); | |
| } | |
| .asst-top-lead.open .asst-title-chev { transform: rotate(180deg); } | |
| .asst-history { | |
| position: absolute; top: calc(100% + 8px); left: 0; | |
| width: 300px; max-width: min(320px, 90vw); | |
| max-height: min(560px, 70vh); | |
| display: flex; flex-direction: column; | |
| background: var(--bg-card); | |
| border: 1px solid var(--line-strong); | |
| border-radius: var(--r-4); | |
| box-shadow: var(--elev-4); | |
| z-index: 30; | |
| opacity: 0; transform: translateY(-6px) scale(0.98); | |
| pointer-events: none; | |
| transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); | |
| } | |
| .asst-top-lead.open .asst-history { | |
| opacity: 1; transform: translateY(0) scale(1); | |
| pointer-events: auto; | |
| } | |
| .asst-new { | |
| margin: 8px 8px 4px; | |
| display: flex; align-items: center; gap: 9px; | |
| padding: 9px 11px; | |
| font-family: var(--font-ui); | |
| font-size: 13px; font-weight: 600; | |
| color: var(--ink-strong); | |
| background: var(--bg-app); | |
| border: 1px solid var(--line-strong); | |
| border-radius: var(--r-3); | |
| cursor: pointer; | |
| flex: none; | |
| transition: background var(--t-fast) var(--ease-snap), color var(--t-fast), border-color var(--t-fast); | |
| } | |
| .asst-new:hover { background: var(--hover-fill); color: var(--hover-text); border-color: var(--hover-fill); } | |
| .asst-new svg { width: 15px; height: 15px; } | |
| .asst-chats { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 2px 8px 10px; } | |
| .asst-group-label { | |
| font-family: var(--font-mono); | |
| font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase; | |
| color: var(--ink-faint); | |
| padding: 14px 10px 6px; | |
| } | |
| .asst-chat { | |
| position: relative; | |
| display: block; | |
| padding: 8px 30px 8px 11px; | |
| border-radius: var(--r-2); | |
| cursor: pointer; | |
| color: var(--ink-soft); | |
| text-decoration: none; | |
| transition: background var(--t-fast); | |
| } | |
| .asst-chat:hover { background: var(--bg-hover); } | |
| .asst-chat.active { background: var(--bg-hover); } | |
| .asst-chat.active::before { | |
| content: ""; position: absolute; left: 0; top: 7px; bottom: 7px; | |
| width: 2px; border-radius: 2px; background: var(--brand); | |
| } | |
| .asst-chat-title { | |
| font-size: 12.5px; font-weight: 500; color: var(--ink); | |
| white-space: nowrap; overflow: hidden; text-overflow: ellipsis; line-height: 1.35; | |
| } | |
| .asst-chat.active .asst-chat-title { color: var(--ink-strong); } | |
| .asst-chat-meta { | |
| font-family: var(--font-mono); | |
| font-size: 9.5px; color: var(--ink-faint); margin-top: 2px; | |
| letter-spacing: 0.02em; | |
| } | |
| .asst-chat-del { | |
| position: absolute; right: 6px; top: 50%; transform: translateY(-50%); | |
| width: 22px; height: 22px; display: none; place-items: center; | |
| border: none; background: transparent; color: var(--ink-faint); | |
| border-radius: var(--r-1); cursor: pointer; | |
| } | |
| .asst-chat:hover .asst-chat-del { display: grid; } | |
| .asst-chat-del:hover { color: var(--danger); background: var(--bg-card); } | |
| /* βββββββββββββββββββββββββββ MAIN βββββββββββββββββββββββββββ */ | |
| .asst-main { display: flex; flex-direction: column; flex: 1; min-width: 0; min-height: 0; } | |
| .asst-top { | |
| display: flex; align-items: center; justify-content: space-between; gap: 16px; | |
| padding: 13px 24px; | |
| border-bottom: 1px solid var(--line); | |
| background: color-mix(in srgb, var(--bg-app) 82%, transparent); | |
| backdrop-filter: blur(8px); | |
| } | |
| .asst-top-lead { display: flex; align-items: center; gap: 2px; min-width: 0; position: relative; } | |
| .asst-top-title { min-width: 0; } | |
| .asst-top-title h1 { | |
| margin: 0; font-family: var(--font-ui); font-weight: 600; font-size: 14px; color: var(--ink-strong); | |
| white-space: nowrap; overflow: hidden; text-overflow: ellipsis; | |
| } | |
| .asst-top-sub { | |
| font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.04em; color: var(--ink-faint); margin-top: 2px; | |
| } | |
| .asst-top-trail { display: flex; align-items: center; gap: 16px; flex: none; } | |
| .asst-context[hidden] { display: none; } | |
| .asst-context { display: flex; align-items: center; gap: 6px; } | |
| .asst-context-bar { | |
| width: 34px; height: 4px; border-radius: 2px; background: var(--line-strong); | |
| overflow: hidden; display: block; | |
| } | |
| .asst-context-fill { display: block; height: 100%; background: var(--ink-faint); border-radius: 2px; transition: width 0.3s ease; } | |
| .asst-context-pct { font-family: var(--font-mono); font-size: 10px; color: var(--ink-faint); white-space: nowrap; } | |
| /* Escalation as a conversation fills the model's context window β set | |
| on #contextMeter by renderThread(). Under ~70% stays the quiet | |
| default styling above; nothing to warn about yet. */ | |
| .asst-context.warn .asst-context-fill { background: var(--warning); } | |
| .asst-context.warn .asst-context-pct { color: var(--warning); } | |
| .asst-context.crit .asst-context-fill { background: var(--danger); } | |
| .asst-context.crit .asst-context-pct { color: var(--danger); } | |
| /* Preview banner β flags that Turing isn't wired to a backend yet. | |
| [hidden] override needed: plain author-stylesheet display:flex below | |
| otherwise beats the UA stylesheet's [hidden]{display:none} regardless | |
| of selector specificity, since author origin always wins over UA | |
| origin in the cascade β the hidden attribute alone would silently | |
| do nothing here. */ | |
| .asst-preview[hidden] { display: none; } | |
| .asst-preview { | |
| display: flex; align-items: center; gap: 9px; | |
| padding: 8px 24px; | |
| background: color-mix(in srgb, var(--brand) 9%, var(--bg-app)); | |
| border-bottom: 1px solid var(--line); | |
| font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.01em; | |
| color: var(--ink-soft); | |
| } | |
| .asst-preview b { color: var(--ink-strong); font-weight: 600; } | |
| .asst-preview-dot { | |
| width: 6px; height: 6px; border-radius: 50%; flex: none; | |
| background: var(--brand); | |
| box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand) 22%, transparent); | |
| } | |
| .asst-preview-txt { min-width: 0; } | |
| /* Embed mode (?embed=1) β panel lives inside the engine's #turing view, so | |
| its own ambient backdrop + "back to engine" link are dropped (the engine | |
| already provides brand, nav, topbar, and a way back β it's the page | |
| you're already on). Standalone mode keeps them for direct-URL access. */ | |
| html.asst-embed .bg-atmos { display: none; } | |
| html.asst-embed .asst-back { display: none; } | |
| /* Phase rail β the Design β Build β Edit β Learn loop, always visible. */ | |
| .asst-phases { display: flex; align-items: center; gap: 0; flex: none; } | |
| .asst-phase { | |
| display: flex; flex-direction: column; align-items: center; gap: 2px; | |
| padding: 4px 13px; position: relative; | |
| } | |
| .asst-phase + .asst-phase::before { | |
| content: ""; position: absolute; left: -1px; top: 50%; width: 14px; height: 1px; | |
| background: var(--line-strong); transform: translate(-50%, 6px); | |
| } | |
| .asst-phase-num { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.1em; color: var(--ink-faint); } | |
| .asst-phase-name { | |
| font-family: var(--font-ui); font-size: 11px; font-weight: 600; color: var(--ink-faint); | |
| padding: 2px 8px; border-radius: 999px; transition: all var(--t-base) var(--ease); | |
| } | |
| .asst-phase.on .asst-phase-name { color: var(--hover-text); background: var(--brand); } | |
| .asst-phase.on .asst-phase-num { color: var(--ink); } | |
| /* Thread */ | |
| .asst-thread { flex: 1 1 auto; min-height: 0; overflow-y: auto; scroll-behavior: smooth; } | |
| .asst-thread-inner { max-width: 48rem; margin: 0 auto; padding: 30px 24px 20px; } | |
| .asst-turn { margin-bottom: 26px; } | |
| .asst-role { | |
| font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.16em; text-transform: uppercase; | |
| color: var(--ink-faint); margin-bottom: 8px; display: flex; align-items: center; gap: 7px; | |
| } | |
| .asst-role .dot { width: 6px; height: 6px; border-radius: 50%; background: var(--brand); } | |
| .asst-body { | |
| font-family: var(--font-body); font-size: 15.5px; line-height: 1.62; color: var(--ink); | |
| } | |
| .asst-body p { margin: 0 0 0.7em; } | |
| .asst-body p:last-child { margin-bottom: 0; } | |
| .asst-body p.asst-h { font-weight: 600; color: var(--ink-strong); margin: 0.9em 0 0.35em; } | |
| .asst-body p.asst-h:first-child { margin-top: 0; } | |
| .asst-body ul { margin: 0 0 0.7em; padding-left: 1.3em; } | |
| .asst-body ul:last-child { margin-bottom: 0; } | |
| .asst-body li { margin: 0 0 0.25em; } | |
| .asst-body code { font-family: var(--font-mono); font-size: 0.86em; background: var(--bg-hover); padding: 1px 5px; border-radius: var(--r-1); overflow-wrap: anywhere; word-break: break-word; } | |
| /* Fenced code block that wasn't long enough to collapse into .asst-seq | |
| (see fencedBlock() in assistant.js) β still wraps instead of forcing | |
| the message wide, same fix as the .asst-body code rule above. */ | |
| .asst-code { | |
| margin: 6px 0; padding: 10px 12px; | |
| background: var(--bg-hover); border: 1px solid var(--line); border-radius: var(--r-2); | |
| font-family: var(--font-mono); font-size: 12.5px; line-height: 1.55; color: var(--ink-soft); | |
| white-space: pre-wrap; word-break: break-word; overflow-wrap: anywhere; | |
| } | |
| /* User turn β a discrete right-offset card, ink-forward. */ | |
| .asst-turn.user { display: flex; justify-content: flex-end; } | |
| .asst-turn.user .asst-bubble { | |
| max-width: 84%; | |
| background: var(--bg-card); | |
| border: 1px solid var(--line); | |
| border-radius: var(--r-4); | |
| padding: 11px 15px; | |
| font-family: var(--font-body); font-size: 15px; line-height: 1.55; color: var(--ink-strong); | |
| box-shadow: var(--elev-1); | |
| } | |
| /* A long pasted sequence collapses into this instead of running | |
| straight into the surrounding prose as one unreadable wall of text | |
| (reported 2026-07-11). */ | |
| .asst-seq { | |
| margin: 6px 0; padding: 8px 10px; | |
| background: var(--bg-hover); border: 1px solid var(--line); border-radius: var(--r-2); | |
| display: flex; align-items: center; gap: 8px; flex-wrap: wrap; | |
| } | |
| .asst-seq.expanded { flex-direction: column; align-items: stretch; } | |
| .asst-seq-preview { | |
| font-family: var(--font-mono); font-size: 12.5px; line-height: 1.5; | |
| color: var(--ink-soft); word-break: break-all; flex: 1; min-width: 0; | |
| } | |
| /* The mutated codon(s) in a generated variant sequence β NOT a raw diff | |
| against WT (see the long comment on mutated_positions in | |
| agent_tools.py), so this only ever appears on real, known changes. */ | |
| .asst-seq-mut { | |
| background: none; color: var(--danger); font-weight: 700; | |
| } | |
| .asst-seq-meta { font-size: 10.5px; font-family: var(--font-mono); color: var(--ink-faint); white-space: nowrap; } | |
| .asst-seq-toggle, .asst-seq-copy, .asst-seq-dl { | |
| font-family: var(--font-mono); font-size: 10.5px; letter-spacing: 0.03em; | |
| color: var(--brand-light); background: none; border: none; cursor: pointer; | |
| padding: 2px 6px; border-radius: var(--r-1); white-space: nowrap; | |
| } | |
| .asst-seq-toggle:hover, .asst-seq-copy:hover, .asst-seq-dl:hover { background: var(--bg-card); color: var(--ink-strong); } | |
| /* Copy/download sit slightly quieter than the primary Show-full toggle | |
| until hovered β three same-weight actions in a row reads noisier | |
| than one primary + two secondary. */ | |
| .asst-seq-copy, .asst-seq-dl { color: var(--ink-faint); } | |
| /* Tool-execution card β the "it runs your tools" moment. */ | |
| .asst-tool { | |
| margin: 12px 0 2px; | |
| border: 1px solid var(--line-strong); | |
| border-left: 2px solid var(--brand); | |
| border-radius: var(--r-3); | |
| background: var(--bg-card); | |
| overflow: hidden; | |
| } | |
| .asst-tool-head { | |
| display: flex; align-items: center; gap: 9px; | |
| padding: 9px 13px; | |
| font-family: var(--font-mono); font-size: 11px; color: var(--ink-strong); | |
| border-bottom: 1px solid var(--line); | |
| } | |
| .asst-tool-head .glyph { color: var(--brand-light); } | |
| .asst-tool-name { font-weight: 600; letter-spacing: 0.02em; } | |
| .asst-tool-status { | |
| margin-left: auto; font-size: 9.5px; letter-spacing: 0.12em; text-transform: uppercase; | |
| color: var(--success); display: inline-flex; align-items: center; gap: 5px; | |
| } | |
| .asst-tool-status .tick { width: 5px; height: 5px; border-radius: 50%; background: var(--success); } | |
| .asst-tool-status.failed { color: var(--danger); } | |
| .asst-tool-status.failed .tick { background: var(--danger); } | |
| .asst-tool-body { padding: 10px 13px; font-family: var(--font-mono); font-size: 11.5px; line-height: 1.6; color: var(--ink-soft); } | |
| .asst-tool-body b { color: var(--ink-strong); font-weight: 600; } | |
| .asst-tool-body .kv { display: flex; justify-content: space-between; gap: 16px; padding: 1px 0; } | |
| /* Compact preview list under the summary stats β top guides/pairs/ | |
| variants a tool actually produced, so "the engineering" reads as a | |
| real result, not just aggregate numbers. */ | |
| .asst-tool-items { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); } | |
| .asst-tool-item { display: flex; justify-content: space-between; gap: 12px; padding: 2px 0; } | |
| .asst-tool-item span:first-child { color: var(--ink-strong); } | |
| /* One per real, orderable sequence a tool produced (e.g. each DE | |
| variant's DNA) β a label line + the same seqBlock used for pasted | |
| sequences elsewhere, so copy/download/expand all work identically. */ | |
| .asst-tool-seq { margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--line); } | |
| .asst-tool-seq:first-child { margin-top: 0; padding-top: 0; border-top: none; } | |
| .asst-tool-seq-label { color: var(--ink-strong); font-size: 11px; margin-bottom: 4px; } | |
| .asst-tool-seq .asst-seq { margin: 0; } | |
| /* System / offline notice, rendered as a quiet inline line. */ | |
| .asst-sys { | |
| font-family: var(--font-mono); font-size: 11px; color: var(--ink-faint); | |
| border: 1px dashed var(--line-strong); border-radius: var(--r-3); | |
| padding: 9px 13px; margin: 4px 0; line-height: 1.55; | |
| } | |
| /* Daily-limit / upgrade card β shown when the message budget is spent. */ | |
| .asst-limit { | |
| border: 1px solid var(--line-strong); border-radius: var(--r-4); | |
| background: var(--bg-card); padding: 15px 16px; box-shadow: var(--elev-1); | |
| } | |
| .asst-limit-t { font-family: var(--font-body); font-size: 15px; color: var(--ink-strong); } | |
| .asst-limit-s { font-family: var(--font-mono); font-size: 10.5px; color: var(--ink-faint); margin-top: 4px; letter-spacing: 0.02em; } | |
| .asst-limit-cta { | |
| display: inline-block; margin-top: 12px; padding: 8px 14px; | |
| font-family: var(--font-ui); font-size: 12.5px; font-weight: 600; text-decoration: none; | |
| color: var(--hover-text); background: var(--brand); border-radius: var(--r-3); | |
| transition: background var(--t-fast); | |
| } | |
| .asst-limit-cta:hover { background: var(--hover-fill); } | |
| /* Thinking indicator β a caption cycles through workflow-flavored steps | |
| (guessTrack() in assistant.js) alongside the pulse dots, so the wait | |
| reads as "the engine is working through Design/Build/Edit/Learn" | |
| instead of a content-free spinner. */ | |
| .asst-thinking { display: flex; align-items: center; gap: 9px; } | |
| .asst-think { display: inline-flex; gap: 5px; align-items: center; padding: 2px 0; flex: none; } | |
| .asst-think span { width: 5px; height: 5px; border-radius: 50%; background: var(--ink-faint); animation: asstpulse 1.1s var(--ease) infinite; } | |
| .asst-think span:nth-child(2) { animation-delay: 0.15s; } | |
| .asst-think span:nth-child(3) { animation-delay: 0.30s; } | |
| .asst-think-caption { | |
| font-family: var(--font-mono); font-size: 11.5px; color: var(--ink-faint); letter-spacing: 0.01em; | |
| } | |
| @keyframes asstpulse { 0%,100% { opacity: 0.25; transform: translateY(0); } 50% { opacity: 1; transform: translateY(-2px); } } | |
| @media (prefers-reduced-motion: reduce) { .asst-think span { animation: none; opacity: 0.6; } } | |
| /* Pulses the currently-active phase pill while a request is in flight β | |
| the existing Design β Build β Edit β Learn rail doubling as a live | |
| workflow indicator instead of only reflecting the last-completed | |
| phase. */ | |
| #phaseRail.busy .asst-phase.on .asst-phase-name { animation: asstphasebusy 1.3s var(--ease) infinite; } | |
| @keyframes asstphasebusy { 0%,100% { opacity: 1; } 50% { opacity: 0.55; } } | |
| @media (prefers-reduced-motion: reduce) { #phaseRail.busy .asst-phase.on .asst-phase-name { animation: none; } } | |
| /* Empty / welcome state */ | |
| .asst-empty { max-width: 44rem; margin: 0 auto; padding: 8vh 24px 0; text-align: center; } | |
| .asst-empty h2 { | |
| font-family: var(--font-display); font-style: italic; font-weight: 400; | |
| font-size: clamp(26px, 4.4vw, 40px); line-height: 1.1; color: var(--ink-strong); margin: 0 0 10px; | |
| } | |
| .asst-empty p { | |
| font-family: var(--font-mono); font-size: 11.5px; letter-spacing: 0.02em; color: var(--ink-faint); | |
| margin: 0 auto 30px; max-width: 30rem; line-height: 1.7; | |
| } | |
| .asst-chips { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; max-width: 34rem; margin: 0 auto; } | |
| .asst-chip { | |
| text-align: left; cursor: pointer; | |
| background: var(--bg-card); border: 1px solid var(--line); border-radius: var(--r-4); | |
| padding: 14px 15px; transition: border-color var(--t-base), transform var(--t-base) var(--ease), box-shadow var(--t-base); | |
| } | |
| .asst-chip:hover { border-color: var(--line-bold); transform: translateY(-1px); box-shadow: var(--elev-2); } | |
| .asst-chip .k { font-family: var(--font-mono); font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--brand-light); } | |
| .asst-chip .t { display: block; font-family: var(--font-body); font-size: 14px; color: var(--ink-strong); margin-top: 5px; line-height: 1.35; } | |
| .sr-only { | |
| position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; | |
| overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; | |
| } | |
| /* Composer */ | |
| .asst-composer { border-top: 1px solid var(--line); padding: 14px 24px 16px; background: var(--bg-app); } | |
| .asst-composer-inner { max-width: 48rem; margin: 0 auto; } | |
| .asst-field { | |
| display: flex; align-items: flex-end; gap: 10px; | |
| background: var(--bg-card); border: 1px solid var(--line-strong); border-radius: var(--r-4); | |
| padding: 8px 8px 8px 14px; box-shadow: var(--elev-1); | |
| transition: border-color var(--t-fast), box-shadow var(--t-fast); | |
| } | |
| .asst-field:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-ring); } | |
| .asst-input { | |
| flex: 1 1 auto; resize: none; border: none; outline: none; background: transparent; | |
| font-family: var(--font-body); font-size: 15px; line-height: 1.5; color: var(--ink-strong); | |
| max-height: 180px; padding: 6px 0; min-height: 24px; | |
| } | |
| .asst-input::placeholder { color: var(--ink-faint); } | |
| .asst-send { | |
| flex: none; width: 34px; height: 34px; border-radius: var(--r-3); border: none; cursor: pointer; | |
| background: var(--brand); color: var(--hover-text); display: grid; place-items: center; | |
| transition: background var(--t-fast), opacity var(--t-fast); | |
| } | |
| .asst-send:hover { background: var(--hover-fill); } | |
| .asst-send:disabled { opacity: 0.4; cursor: default; } | |
| .asst-send svg { width: 16px; height: 16px; } | |
| .asst-hint { | |
| font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.05em; color: var(--ink-faint); | |
| text-align: center; margin-top: 9px; | |
| } | |
| .asst-hint b { color: var(--ink-soft); font-weight: 600; } | |
| .asst-char-count[hidden] { display: none; } | |
| .asst-char-count { | |
| font-family: var(--font-mono); font-size: 9.5px; letter-spacing: 0.05em; color: var(--ink-faint); | |
| text-align: right; margin-top: 6px; | |
| } | |
| .asst-char-count.warn { color: var(--warning); } | |
| .asst-char-count.over { color: var(--danger); font-weight: 600; } | |
| /* Narrow widths: the phase rail is dropped (no room) and the thread/ | |
| composer get side padding back (no rail to butt up against). The | |
| history dropdown needs no special-casing β its max-width already caps | |
| at 90vw, so it never overflows a narrow screen. */ | |
| @media (max-width: 720px) { | |
| .asst-phases { display: none; } | |
| .asst-thread-inner, .asst-composer-inner { padding-left: 16px; padding-right: 16px; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| .asst-history, .asst-title-chev { transition: none; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Same ambient backdrop as the engine (dark-mode only), for continuity. --> | |
| <div class="bg-atmos lit" aria-hidden="true"></div> | |
| <div class="asst" id="asst"> | |
| <!-- ββββββββββββββββββββββ MAIN ββββββββββββββββββββββ --> | |
| <section class="asst-main"> | |
| <!-- Preview banner β hidden unless assistant.js's PREVIEW_MODE is | |
| true (it un-hides this itself; see the Boot section there). --> | |
| <div class="asst-preview" id="previewBar" role="status" hidden> | |
| <span class="asst-preview-dot" aria-hidden="true"></span> | |
| <span class="asst-preview-txt"><b>Preview</b> β Turing isn't live yet. Browse the example sessions to see how it runs your tools; the composer is disabled for now.</span> | |
| </div> | |
| <header class="asst-top"> | |
| <div class="asst-top-lead" id="topLead"> | |
| <!-- Standalone-mode only (embed hides this β the engine IS | |
| the way back). --> | |
| <a class="asst-back" href="/" aria-label="Back to engine"> | |
| <svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M15 18l-6-6 6-6"/></svg> | |
| </a> | |
| <!-- Conversation title doubles as the history trigger β recent | |
| chats used to live in a permanent rail beside this one; | |
| now they're a popover so there's only ONE nav rail | |
| total (the engine's) instead of two side by side. --> | |
| <button class="asst-title-btn" id="titleBtn" type="button" aria-haspopup="true" aria-expanded="false" aria-controls="historyMenu"> | |
| <div class="asst-top-title"> | |
| <h1 id="topTitle">New conversation</h1> | |
| <div class="asst-top-sub" id="topSub">Turing Β· the engine runs your tools</div> | |
| </div> | |
| <svg class="asst-title-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="6 9 12 15 18 9"/></svg> | |
| </button> | |
| <div class="asst-history" id="historyMenu" role="menu" aria-label="Conversations"> | |
| <button class="asst-new" id="newChat" type="button"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M12 5v14M5 12h14"/></svg> | |
| New conversation | |
| </button> | |
| <nav class="asst-chats" id="chatList" aria-label="Recent conversations"></nav> | |
| </div> | |
| </div> | |
| <!-- .asst-top is a 2-item space-between flex row (lead / this | |
| group) β phase rail + context meter share this ONE | |
| wrapper so adding the meter doesn't turn it into a | |
| 3-item row (which would break the existing spacing, | |
| and .asst-phases is display:none on mobile with no | |
| fallback layout for a stray third child). --> | |
| <div class="asst-top-trail"> | |
| <div class="asst-phases" id="phaseRail" aria-label="Design, Build, Edit, Learn loop"> | |
| <div class="asst-phase" data-phase="Design"><span class="asst-phase-num">Β§01</span><span class="asst-phase-name">Design</span></div> | |
| <div class="asst-phase" data-phase="Build"><span class="asst-phase-num">Β§02</span><span class="asst-phase-name">Build</span></div> | |
| <div class="asst-phase" data-phase="Edit"><span class="asst-phase-num">Β§03</span><span class="asst-phase-name">Edit</span></div> | |
| <div class="asst-phase" data-phase="Learn"><span class="asst-phase-num">Β§04</span><span class="asst-phase-name">Learn</span></div> | |
| </div> | |
| <!-- Hidden until a chat has an actual token count (i.e. | |
| after the first real reply). Escalates color via | |
| .warn/.crit classes as usage climbs, set in | |
| renderThread(). --> | |
| <div class="asst-context" id="contextMeter" title="How much of the model's context window this conversation has used" hidden> | |
| <span class="asst-context-bar"><span class="asst-context-fill" id="contextFill"></span></span> | |
| <span class="asst-context-pct" id="contextPct"></span> | |
| </div> | |
| </div> | |
| </header> | |
| <div class="asst-thread" id="thread"><div class="asst-thread-inner" id="threadInner"></div></div> | |
| <!-- Deliberately NOT aria-live on #thread itself β it fully | |
| rebuilds via innerHTML on every render, including every | |
| tick of the typewriter reveal, which would have a screen | |
| reader re-announce the whole conversation dozens of times | |
| per reply. This is a separate, silent-until-used region | |
| announced once, when a reply finishes revealing. --> | |
| <div class="sr-only" id="liveRegion" role="status" aria-live="polite"></div> | |
| <footer class="asst-composer"> | |
| <div class="asst-composer-inner"> | |
| <div class="asst-field"> | |
| <textarea class="asst-input" id="input" rows="1" placeholder="Describe what you want to engineer β the assistant will run the right toolsβ¦"></textarea> | |
| <button class="asst-send" id="send" type="button" aria-label="Send" disabled> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 11l5-5 5 5M12 6v13"/></svg> | |
| </button> | |
| </div> | |
| <!-- Server hard-caps a message at 4,000 chars (was a | |
| SILENT truncation β a pasted plasmid could get cut | |
| off mid-sequence with zero indication, which is | |
| exactly the kind of thing that can't happen in a | |
| tool feeding real lab decisions). Hidden until the | |
| user is actually getting close, via autoGrow(). --> | |
| <div class="asst-char-count" id="charCount" hidden></div> | |
| <div class="asst-hint" id="composerHint">Enter to send Β· Shift+Enter for a new line Β· the assistant routes across <b>Design</b> Β· <b>Build</b> Β· <b>Edit</b> Β· <b>Learn</b></div> | |
| </div> | |
| </footer> | |
| </section> | |
| </div> | |
| <!-- Main client β external for CSP (see assistant-boot.js note). `defer` | |
| runs it after the DOM is parsed. --> | |
| <script src="/static/assistant.js?v=20260712-redmut1" defer></script> | |
| </body> | |
| </html> | |