/* TuringDNA · Engine — editorial design system. * * Reference register: a scientific instrument, not an AI wrapper. Warm-paper * canvas, the IBM Plex superfamily throughout — Plex Serif (incl. italic) for * display + long-form prose, Plex Sans for UI, Plex Mono for chrome / labels / * data identifiers. One graphite accent, hairline rules over heavy borders. * * Typography note (2026-07-19): moved off Inter + Source Serif 4 + Playfair. * A reviewing scientist flagged the serif as an "AI tell", and Inter is the * defining AI-app UI font; IBM Plex was designed by IBM for technical/ * scientific communication and reads as a serious engineering org, not a * template. One coherent family de-AIs the whole stack. Playfair is gone * (it was already dead — a later override had repointed --font-display). * * Architecture: * - Layout: sidebar + top bar + content workspace (kept from prior system). * - Color: ink-on-paper neutrals + 1 graphite accent + 4 semantic colors. * - Typography: one family — Plex Serif display+body / Plex Sans UI / Plex Mono chrome. * - Depth: hairline rules + soft shadows (5 tiers, used sparingly). * - Motion: 120–280ms cubic-bezier easing, purposeful only. * * Token names kept from the previous system so component rules pick up the * new families automatically — only the values under the aliases changed. */ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,300;0,400;0,500;0,600;1,400;1,500;1,600&family=IBM+Plex+Serif:ital,wght@0,300;0,400;0,500;0,600;0,700;1,400;1,600&family=IBM+Plex+Mono:wght@400;500;600&display=swap'); /* ─── iOS text-auto-zoom kill switch ─────────────────────────────────── * iOS Safari "helpfully" inflates any text < 16px on a narrow viewport, * which made the engine's editorial 11-12px section headers and chrome * render at ~24-32px on a phone — making the layout look 2-3× zoomed * even though the CSS font-size was correct. The screenshot a user sent * 2026-05-27 ("it's too zoomed in for comfortable use") was this exact * bug. text-size-adjust:100% tells iOS to trust our design and not * auto-scale. Only matters for mobile Safari + old Chrome Mobile; harmless * everywhere else. * * Set on html (so it cascades) with both -webkit- and standard names. */ html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; } :root { /* ── Neutral scale — warm-paper ink, redirected from the old slate scale. gray-0 (was pure white) is now the warmest paper; gray-9 (was near-black) stays as ink. The progression in between is desaturated and warmed so every surface reads as "off-white paper" rather than "tinted slate". */ --gray-0: #FFFFFF; /* card — pure white so it reads as raised off the warm canvas */ --gray-1: #EFEBE2; /* warm paper canvas — darkened so white cards separate clearly */ --gray-2: #EFECE5; /* paper soft */ --gray-3: #D9D3C8; /* hairline-soft — strengthened so card borders are visible in light */ --gray-4: #C8C1B4; /* hairline */ --gray-5: #B8B4AE; /* hairline-strong */ --gray-6: #6E6A65; /* ink faint — darkened from #8A857E for WCAG AA in light */ --gray-7: #6B6862; /* ink soft */ --gray-8: #2A2A29; /* ink */ --gray-9: #0A0A0A; /* ink strong */ /* ── Brand — graphite, matching landing's single accent. Reading these in old rules: --brand replaces the old teal everywhere, so nav highlights, focus rings, primary buttons, and the AlphaFold / ESMFold spinner all shift to one consistent graphite. */ --brand: #3D3A34; /* The one fixed brand colour, shared with the website and the network so the logo is one thing across the product. The rest of this UI is monochrome graphite; this is reserved for the mark and the wordmark. */ --brand-steel: #2B709C; /* THE LANDING AMBER, same hex, so the product has one accent rather than two that nearly match. It is a FILL colour, not a text colour: landing spends it as a background behind ink and as a 3px left rule, never as type on paper, and the contrast is why — #FFE05C on white is 1.3:1, which is unreadable. On ink it is 14.2:1. Use --amber for surfaces and rules; use --amber-ink for anything written ON it. */ --amber: #FFE05C; --amber-ink: #192025; --brand-deep: #1B1A17; /* The DATA accent. This palette is deliberately monochrome — brand is graphite, and every wash in the sequence pane is a grey at some opacity — which leaves nothing that can mean "look here, this is not like the rest". Selection is already a 32% brand wash, so a pending edit painted in brand is indistinguishable from a drag. One colour, reserved for one meaning: bases that are not yet real. Separate from --danger on purpose. Danger means something is wrong; a proposed insert is not wrong, it is UNCOMMITTED, and colouring it with the error colour would teach people to read their own edits as faults. */ --edit: #C0392B; --on-edit: #FFFFFF; --brand-light: #6B6862; --brand-50: #F2F0EB; --brand-100: #E5E2DB; --brand-200: #CFCBC2; --brand-ring: rgba(61, 58, 52, 0.22); /* ── Semantic — kept for functional chips (GC warnings, internal RE-site errors, success toasts). Toned down slightly so they don't shout on the warm canvas. */ --success: #047857; --success-light: #ECFDF5; --warning: #92400E; --warning-light: #FEF3C7; --danger: #991B1B; --danger-light: #FEF2F2; --info: var(--brand); --info-light: var(--brand-50); /* ── Surfaces — paper, not slate. */ --bg-app: var(--gray-1); --bg-card: var(--gray-0); --bg-sidebar: var(--gray-2); --bg-subtle: var(--gray-2); --bg-raised: var(--gray-0); --bg-hover: var(--gray-3); /* ── Ink — direct ports; gray-9 is ink-strong, gray-7/6 carry the secondary hierarchy. Source Serif 4 reads at slightly lower contrast than Inter, so --ink (body text) is the same value as before but visually warmer. */ --ink-strong: var(--gray-9); --ink: var(--gray-8); --ink-soft: var(--gray-7); --ink-faint: var(--gray-6); --ink-disabled: #8A857E; /* decoupled from shared --gray-5; darkened for readability */ /* Button-hover fill/text. Deliberately FIXED (not redefined in the dark block) so an action button always inverts to a near-black rectangle with warm-white text on hover, in BOTH themes — instead of the theme tokens, which in dark mode flip to off-white-on-dark (looked like "nothing happened / flashed back to white"). */ --hover-fill: #0A0A0A; --hover-text: #FBFAF6; /* Glyph/text color that sits ON a solid var(--ink-strong) fill. Unlike --hover-text (fixed above), this FLIPS per theme: --ink-strong is near-black in light mode → --on-ink is warm-white; --ink-strong is near-white in dark mode → --on-ink is near-black. Pairing --hover-text with an --ink-strong background was the dark-mode white-on-white bug (consult chat bubble, command-bar + consult send buttons, construct card CTAs) — those now use --on-ink for their resting state. */ --on-ink: #FBFAF6; /* TEXT THAT SITS ON A FILLED ACCENT — brand, danger or warning. Eleven rules used to hardcode `color: #fff` (or one case, a dark brown) on top of a background that FLIPS with the theme. In the light theme --brand is #3D3A34 and white reads fine; in dark it is #C9C4BB and white-on-near-white measured 1.74:1. The selected "Gibson / overlap" tab was legible in one theme and effectively blank in the other. This token exists because the whole palette inverts together, so a single flipping value is correct on all three accents: measured worst case 6.79:1 across brand/danger/warning x light/dark. Anything filled with an accent takes THIS, never a literal. */ --on-accent: #FBFAF6; --line: var(--gray-3); --line-strong: var(--gray-4); --line-bold: var(--gray-5); /* ── Elevation — softer on warm paper. The old shadows used a cool slate blue (rgba(14,20,27)); the new ones use warm-paper ink so cards don't look like they're casting a fluorescent shadow on butcher paper. */ --elev-1: 0 1px 2px rgba(42, 42, 41, 0.04); --elev-2: 0 1px 3px rgba(42, 42, 41, 0.05), 0 1px 2px rgba(42, 42, 41, 0.03); --elev-3: 0 4px 12px rgba(42, 42, 41, 0.06), 0 2px 4px rgba(42, 42, 41, 0.03); --elev-4: 0 12px 28px rgba(42, 42, 41, 0.08), 0 4px 10px rgba(42, 42, 41, 0.04); --elev-5: 0 24px 48px rgba(42, 42, 41, 0.10), 0 6px 14px rgba(42, 42, 41, 0.05); /* ── Geometry. These are the values that RENDER. A second :root further down used to redefine all five (4/6/8/10/14 over 3/4/6/10/14) and zero two elevations, so this block — the one anybody reads first — stated numbers the browser never used. Folded together; that block now carries only what is genuinely layer-specific. Two steps beyond the ramp, because two real roles exist that a ramp cannot express: --r-hair per-character and swatch scale. Sequence base highlights sit on a ~7px monospace cell; 4px there is a pill and the readout stops looking like sequence. --r-pill fully round. Was 999px, 75px AND 22px — three values for one intention. */ --r-hair: 2px; --r-1: 4px; --r-2: 6px; --r-3: 6px; --r-4: 8px; --r-5: 10px; --r-pill: 999px; /* CONCENTRIC CORNERS — pick from the scale above for a standalone box, but DERIVE when one rounded box sits in the corner of another: inner radius = outer radius − the gap between them Two corners share a centre only at that value. Anywhere else the gap fattens and thins as it travels round the curve, which is the specific wrongness that makes nested rounded boxes look slightly off without anyone being able to name it. Apple shipped this as ConcentricRectangle in SwiftUI at WWDC25; CSS has no equivalent, so it is calc(): border-radius: max(0px, calc(var(--r-5) - 8px)); TWO THINGS IT DOES NOT MEAN, both of which this codebase gets right and an over-eager application would break: It only applies at a CORNER. A child has to be close to the parent's corner in BOTH axes. The compose box sits 12px from the cockpit's sides and 102px above its bottom, so it shares no corner and its radius is an independent choice, not a violation. It does not apply to pills or circles. --r-pill, the send button and the icon buttons are deliberate shapes; squaring them to satisfy the arithmetic would be following the rule off a cliff. */ /* Focus and accent rings. 22 distinct literals turned out to be ONE pattern wearing different colours — `0 0 0 Npx ` at 2px and 3px. The geometry is the token; the colour stays at the call site, because that is the part that actually differs (brand, danger, success, the teal on the atlas). Standardising the width is the point: a 2px ring beside a 3px ring is the same wobble as 12px beside 12.5px type. */ --ring: 0 0 0 3px; /* ── Type — one coherent family (IBM Plex). --font-ui (Plex Sans) drives all working chrome — toolbars, buttons, inputs, tables, labels, chips. --font-body (Plex Serif) is long-form prose (docs, methods text) and --font-display (Plex Serif, set italic in the headline rules) the large editorial view titles + brand wordmark. --font-mono (Plex Mono) is data identifiers + labels. See the header note for why we left Inter/Playfair/ Source Serif. */ --font-ui: 'IBM Plex Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; --font-body: 'IBM Plex Serif', 'Iowan Old Style', Georgia, serif; --font-display: 'IBM Plex Serif', 'Iowan Old Style', Georgia, serif; --font-mono: 'IBM Plex Mono', 'JetBrains Mono', ui-monospace, Menlo, monospace; /* ── THE TYPE SCALE ────────────────────────────────────────────────── Five steps. Before this there were 27 literal px sizes across 766 declarations, and 94% of all type sat between 9px and 14px — thirteen sizes inside a five-pixel band, 184 of them on half-pixels. That is not a scale, it is 184 individual nudges, each made to fix one box on one screen. A reader builds hierarchy from RATIOS: when 12, 12.5 and 13 all appear together none of them reads as a level, they read as the same text slightly wobbling, and the eye files the wobble under "sloppy" long before anyone counts sizes. Half-pixels at this size do not even render as distinct — they round, and round differently by zoom and by display. The steps are ~1.2-1.25 apart, which is the smallest ratio the eye reliably reads as a change. Roles, not sizes: --t-1 micro label uppercase, letter-spaced, mono. Ticks, kickers, badges, table heads. 140 declarations were spread over 7-10.5px doing exactly this one job. --t-2 UI text the workhorse. Nearly everything: nav, buttons, chips, table cells, the sequence itself. ONE size on purpose — dense instruments separate levels with weight and colour, and five sizes for "text that is roughly small" is the amateur pattern this scale exists to end. --t-3 body reading text, controls, anything with a measure. --t-4 title panel and section heads. --t-5 display the one big thing on a page. Fluid display type (clamp()) is deliberately left out: it is responsive by design and answers to the viewport, not to this scale. */ --t-1: 10px; --t-2: 13px; --t-3: 15px; --t-4: 18px; --t-5: 28px; /* Platform behavior token rather than a sixth typography step: iOS zooms focused form controls below this size. */ --mobile-input-floor: 16px; /* Motion */ --ease: cubic-bezier(0.22, 1, 0.36, 1); --ease-snap: cubic-bezier(0.4, 0, 0.2, 1); /* MOTION DURATIONS. This scale already existed and was already used 80 times; what it had not absorbed was the raw values around it. The UI-scale timings had sprawled to 23 distinct numbers across 46 declarations — 140, 150, 160, 180, 200, 220, 240, 260, 280, 300 and 340 all doing one job. Nobody can tell 180ms from 200ms on a single element; everybody can feel a screen where nothing agrees. Anything longer is deliberately left alone: the ambient loops (12s, 18s, 22s, 42s) are background texture, unrelated to UI response, and so are the reduced-motion resets at 0.01ms. Note the two --t- families are different dimensions: --t-1..--t-5 are FONT SIZES, --t-fast/base/slow are DURATIONS. Pre-existing, and renaming ~100 uses to fix it would be churn for its own sake. */ --t-fast: 120ms; /* hover, colour, focus ring */ --t-base: 180ms; /* the default: most state changes */ --t-slow: 280ms; /* panels, reveals, anything that travels */ /* Layout */ --sidebar-w: 232px; --topbar-h: 56px; color-scheme: light; } /* ───────────────────────────── DARK THEME ──────────────────────────────── * The whole system is token-driven: every surface/ink/line semantic alias * resolves through the --gray-0..9 ramp and --brand-* scale. So dark mode is * just a re-paint of those primitives — override the ramp, brand, semantic * tints, and elevation, and all ~4,800 lines of component rules below follow * automatically. No per-component dark rules needed (a few hardcoded legacy * colors are patched individually further down). * * Applied via [data-theme="dark"] on , set by an anti-FOUC inline * script in index.html BEFORE first paint (resolves saved choice, else the * OS prefers-color-scheme). The toggle in the topbar flips + persists it. */ :root[data-theme="dark"] { /* --amber is deliberately NOT redefined here. An accent that changes value between themes stops being an identity and becomes decoration. */ color-scheme: dark; /* Inverted warm-neutral ramp. gray-0 (lightest paper in light mode) is * now the card surface; gray-9 (ink) becomes a warm off-white. The middle * stops keep a faint warm cast so dark mode reads as "ink on warm slate", * not flat cold gray — same editorial register as the light theme. */ --gray-0: #1B1A18; /* card surface */ --gray-1: #121110; /* app canvas (darkest) */ --gray-2: #211F1C; /* sidebar / subtle */ --gray-3: #2B2926; /* hairline-soft / hover */ --gray-4: #3A3733; /* hairline */ --gray-5: #514D47; /* hairline-strong */ --gray-6: #908A80; /* ink faint */ --gray-7: #B2ACA2; /* ink soft */ --gray-8: #D9D4CA; /* ink */ --gray-9: #F4F1E9; /* ink strong (off-white)*/ --ink-disabled: #8C877F; /* decoupled from --gray-5 (#514D47) so meta/disabled text is legible on dark paper */ /* Dark glyphs read on the off-white --ink-strong fill (see light block). */ --on-ink: #17150F; /* Inverts with everything else — see the light block for why one token covers brand, danger and warning. */ --on-accent: #17150F; /* Brand lifted for legibility on dark paper. Used both as primary-button * fill (white text still reads) and as text/links/accents (now bright * enough to see), so a single lift serves both. */ --brand: #C9C4BB; --brand-steel: #4E96C4; /* lifted for contrast on the dark canvas */ --brand-deep: #B4AEA4; /* Lifted for dark paper, same as brand above: #C0392B goes muddy against #121110, and a mark that has to be hunted for is not a mark. */ --edit: #E2574A; --on-edit: #17150F; --brand-light: #DAD5CC; --brand-50: rgba(201, 196, 187, 0.10); --brand-100: rgba(201, 196, 187, 0.18); --brand-200: rgba(201, 196, 187, 0.30); --brand-ring: rgba(201, 196, 187, 0.40); /* Bio-luminous accent — a GFP / aequorin cyan-teal, used ONLY as light (ambient glow, the living mark's halo, the active-nav marker, hairline blooms) — never as a fill, so the monochrome + button system are intact. Dark-mode only; the light theme stays clean paper. */ --glow: #6FE3CB; --glow-rgb: 111, 227, 203; /* Semantic — brighter foregrounds + translucent tints over dark paper. */ --success: #34D399; --success-light: rgba(52, 211, 153, 0.14); --warning: #F4C77E; --warning-light: rgba(180, 120, 40, 0.20); --danger: #F4796F; --danger-light: rgba(220, 70, 60, 0.16); --info: var(--brand); --info-light: var(--brand-50); /* Elevation — true-black shadows read on dark paper (the warm-ink shadows * used in light mode are invisible against a dark canvas). */ --elev-1: 0 1px 2px rgba(0, 0, 0, 0.40); --elev-2: 0 1px 3px rgba(0, 0, 0, 0.46), 0 1px 2px rgba(0, 0, 0, 0.30); --elev-3: 0 4px 12px rgba(0, 0, 0, 0.50), 0 2px 4px rgba(0, 0, 0, 0.32); --elev-4: 0 12px 28px rgba(0, 0, 0, 0.55), 0 4px 10px rgba(0, 0, 0, 0.36); --elev-5: 0 24px 48px rgba(0, 0, 0, 0.62), 0 6px 14px rgba(0, 0, 0, 0.42); } * { box-sizing: border-box; margin: 0; padding: 0; } html, body { background: var(--bg-app); color: var(--ink); font-family: var(--font-ui); font-size: var(--t-3); line-height: 1.55; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; font-feature-settings: 'cv02', 'cv03', 'cv04', 'cv11'; /* Kill the horizontal rubber-band everywhere (safe — doesn't establish a scroll container, so it won't break the desktop sticky sidebar). The actual horizontal CLIP is applied only on mobile below, because overflow-x:hidden here WOULD break position:sticky. */ overscroll-behavior-x: none; } /* dvh, with a vh fallback FIRST so old browsers get something sane. On iOS Safari the URL bar collapses as you scroll and 100vh keeps reporting the TALL value, so a 100vh page is permanently ~60-90px taller than the screen — a phantom scroll and a gap under fixed footers. dvh tracks the real visible height. Order matters: a browser without dvh support drops the second declaration and keeps the first. */ body { min-height: 100vh; min-height: 100dvh; } a { color: inherit; text-decoration: none; } button, input, select, textarea { font-family: inherit; font-size: inherit; color: inherit; } button { cursor: pointer; border: none; background: none; } kbd { font-family: var(--font-mono); font-size: var(--t-2); padding: 2px 5px; background: var(--gray-3); border: 1px solid var(--line-strong); border-radius: var(--r-1); color: var(--ink-soft); box-shadow: 0 1px 0 var(--line-strong); } ::selection { background: var(--brand-100); color: var(--brand-deep); } ::-webkit-scrollbar { width: 10px; height: 10px; } ::-webkit-scrollbar-track { background: transparent; } ::-webkit-scrollbar-thumb { background: var(--gray-4); border-radius: var(--r-1); border: 2px solid transparent; background-clip: padding-box; } ::-webkit-scrollbar-thumb:hover { background: var(--gray-5); background-clip: padding-box; } /* ====================================================================== APP */ .app { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; min-height: 100dvh; /* see the note on body — vh first, dvh wins */ } /* ================================================================== SIDEBAR */ .sidebar { /* Bug history (root-caused via direct Safari testing on the user's machine, not guessed): sticky→fixed, vh-computed height→top/bottom anchoring, no-transform→permanent GPU layer — none of it held. Then the decisive fact: the STANDALONE engine (not embedded) always painted this correctly, on desktop AND mobile, private AND regular — confirmed directly via elementFromPoint(). Only cross-origin-iframe- embedded desktop fails. Mobile's sidebar is ALSO iframe-embedded but starts hidden and only appears via a real animated slide-in on tap; desktop's is visible from frame 1, no animation, ever. That's the actual difference: content that becomes visible through a genuine CSS animation forces WebKit through real paint frames; content that's simply "there" from initial load can get stuck on a stale composite specifically inside a cross-origin embed. Giving desktop the same kind of real (if imperceptible) entrance animation forces the same correct paint pass mobile gets for free. */ animation: sidebarPaintKick 0.5s ease-out; transform: translateZ(0); position: fixed; top: 0; left: 0; bottom: 0; width: var(--sidebar-w); background: var(--bg-sidebar); border-right: 1px solid var(--line); display: flex; flex-direction: column; /* No top padding: .sb-head is the first child and owns the header band, so the sidebar's hairline lands at exactly --topbar-h — the same y as the cockpit's and the workspace's. */ padding: 0 14px 18px; gap: 24px; } /* The sidebar's share of the shared header band. Full-bleeds the rule across the rail by cancelling the sidebar's horizontal padding, then re-applying it inside so the brand pill keeps its original x. */ .sb-head { flex: 0 0 auto; height: var(--topbar-h); display: flex; align-items: center; margin: 0 -14px; padding: 0 14px; border-bottom: 1px solid var(--line); } .sb-head .brand { flex: 1 1 auto; min-width: 0; } @keyframes sidebarPaintKick { /* Imperceptible (0.4px shift, 0.3% opacity) — this exists purely to force real animation frames through WebKit's paint pipeline, not to be seen. See the .sidebar comment above for why. */ from { opacity: 0.997; transform: translateZ(0) translateY(0.4px); } to { opacity: 1; transform: translateZ(0); } } @media (prefers-reduced-motion: reduce) { .sidebar { animation: none; } } .brand { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border-radius: var(--r-2); transition: background var(--t-fast) var(--ease); } .brand:hover { background: var(--bg-hover); } /* The mark is 2.09:1 (722x345). Boxed square it wasted half its width and read as a squiggle; sized to its real proportion and painted the brand steel via currentColor, it matches the website header and the network — one logo across the product. Fluid up to 56px so it fills the wide classic sidebar yet shrinks into the slim cockpit rail (~38px) instead of clipping off its right edge. */ .brand-mark { width: 56px; max-width: 100%; height: auto; aspect-ratio: 722 / 345; flex-shrink: 1; color: var(--brand-steel); } .brand-mark img, .brand-mark svg { width: 100%; height: 100%; object-fit: contain; display: block; } .brand-word { font-weight: 700; font-size: var(--t-3); letter-spacing: -0.01em; color: var(--ink-strong); } /* The DNA in the brand steel, the way the website wordmark sets it. */ .brand-word em { font-style: normal; font-weight: 800; color: var(--brand-steel); } .nav { display: flex; flex-direction: column; gap: 2px; flex: 1; min-height: 0; /* Safari: without this, a flex:1 + overflow child won't shrink below its content, so it pushes the sign-in foot off-screen. Chrome tolerates the omission; Safari doesn't. */ overflow-y: auto; } .nav-section { font-size: var(--t-1); font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint); padding: 14px 10px 4px; } .nav-item { display: flex; align-items: center; gap: 10px; padding: 7px 10px; border-radius: var(--r-2); color: var(--ink-soft); font-size: var(--t-2); font-weight: 500; /* Explicit, not `all`. `all` animates every property that ever changes on this element — including layout ones — and motion.css adds a press transform here, which under `all` would have been a lottery. Naming the three also means a later override can extend the list instead of silently replacing a fade nobody knew was there. */ transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease), transform var(--t-fast) var(--ease), /* The collapsed rail animates this one. #279 converted this rule off `transition: all` and dropped it, so the nav snapped instead of easing when the cockpit opened. */ gap var(--t-fast) var(--ease); position: relative; } .nav-icon { width: 14px; height: 14px; flex-shrink: 0; color: var(--ink-faint); display: inline-flex; } .nav-icon svg { width: 100%; height: 100%; display: block; } /* hrefless action nav items (e.g. "My designs") still get the link cursor */ .nav-item[role="button"] { cursor: pointer; } .nav-item:hover { background: var(--bg-hover); color: var(--ink); } .nav-item:hover .nav-icon { color: var(--ink-soft); } .nav-item.active { background: var(--brand-50); color: var(--brand-deep); font-weight: 600; } .nav-item.active .nav-icon { color: var(--brand); } .nav-tag { margin-left: auto; font-size: var(--t-2); color: var(--ink-faint); background: var(--gray-3); padding: 1px 6px; border-radius: var(--r-pill); font-variant-numeric: tabular-nums; font-weight: 500; } .nav-item.active .nav-tag { background: var(--brand-100); color: var(--brand-deep); } /* "soon" pill on teaser nav items (e.g. Orchestrator) — a capability that's visible in the nav but not yet live. Editorial mono kicker, brand-tinted. */ .nav-soon { margin-left: auto; font-family: var(--font-mono); font-size: var(--t-1); letter-spacing: 0.14em; text-transform: uppercase; color: var(--brand-deep); background: var(--brand-100); padding: 2px 6px; border-radius: var(--r-pill); font-weight: 600; } .nav-item-soon { color: var(--ink-faint); } /* label sits a touch quieter */ .nav-item-soon:hover { color: var(--ink); } /* ── Rail hierarchy (2026-07-13) ───────────────────────────────────────── * Three tiers so the rail reads as a workflow instead of a flat scattered * list (founder note: "looks like a vibecoded AI platform"): Turing as the * lead / front-door, the four tools as ONE connected loop, utilities muted at * the foot. See the nav comment in index.html for the full rationale. */ /* PRIMARY — Turing as a bordered "front-door" block that visibly outranks the * tools. Monochrome, on the editorial-elite system: a hairline box (borders do * the work here, per the makeover layer) + a size/descriptor bump, NOT a * coloured fill. The shared inset ink left-bar (base .nav-item.active) still * marks it when you're in Turing; the border keeps it reading as the way-in in * every other route too. */ .nav-lead { flex-direction: row; align-items: center; gap: 11px; padding: 10px 12px; margin: 2px 0 14px; border: 1px solid var(--line-strong); background: var(--bg-raised); } .nav-lead-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; } .nav-lead .nav-icon { width: 18px; height: 18px; color: var(--ink-soft); } .nav-lead-word { font-size: var(--t-3); font-weight: 650; letter-spacing: -0.01em; line-height: 1.15; color: var(--ink-strong); } .nav-lead-sub { font-size: var(--t-2); font-weight: 500; color: var(--ink-faint); } .nav-lead:hover { background: var(--bg-hover); border-color: var(--line-bold); } /* Beat the makeover's `.nav-item.active{background:transparent}` + the paper * layer's `.nav-item.active{border-left:2px}` (both equal or lower specificity) * so the front-door card keeps its surface AND a clean uniform hairline while * active; the shared inset ink bar (box-shadow, 6501) still marks "you're in * Turing". Extra class raises specificity to win. */ .nav-lead.nav-item.active { background: var(--bg-raised); border: 1px solid var(--line-strong); padding-left: 12px; } .nav-lead.active .nav-lead-sub { color: var(--ink-soft); } /* THE LOOP — four tools tied by a hairline spine into one ordered sequence. * The spine is the anti-scatter device: it says "these belong together, in * this order," so they read as a pipeline, not four islands. */ .nav-loop { position: relative; margin: 0 0 6px; padding-left: 12px; } .nav-loop::before { content: ""; position: absolute; left: 4px; top: 24px; /* start just under the "The loop" label */ bottom: 7px; width: 1px; background: var(--line-strong); } .nav-loop-lbl { margin: 0; padding: 2px 0 5px 2px; font-family: var(--font-mono); font-size: var(--t-1); font-weight: 600; letter-spacing: 0.18em; text-transform: uppercase; color: var(--ink-faint); } .nav-step { gap: 10px; } .nav-step-nm { font-size: var(--t-2); font-weight: 500; } .nav-step-ph { flex-shrink: 0; margin-left: auto; font-family: var(--font-mono); font-size: var(--t-1); font-weight: 500; letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-disabled); } .nav-step:hover .nav-step-ph { color: var(--ink-faint); } .nav-step.active .nav-step-nm { font-weight: 600; } .nav-step.active .nav-step-ph { color: var(--ink-soft); } /* UTILITIES — muted tier pinned to the foot of the nav (above the account row). */ .nav-utils { margin-top: auto; padding-top: 12px; border-top: 1px solid var(--line); display: flex; flex-direction: column; gap: 1px; } .nav-util { font-size: var(--t-2); font-weight: 500; color: var(--ink-faint); padding: 6px 10px; } .nav-util:hover { color: var(--ink); } .sidebar-footer { display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--line); padding-top: 14px; } /* ── Account row at the foot of the sidebar (Claude-style) ────────── * .nav above is flex:1, so this block pins to the bottom of the rail. * Built into the sidebar — not a floating chip over the workspace. */ .sidebar-account { position: relative; margin-top: 4px; padding-top: 10px; border-top: 1px solid var(--line); } .sidebar-account[hidden] { display: none; } /* Belt-and-suspenders: the foot rows must never be flex-shrunk to zero. */ .sidebar-account, .sidebar-signin, .sidebar-footer { flex-shrink: 0; } /* Anonymous foot state — a quiet "Sign in" link occupying the same slot the account row takes once signed in. Same separator + avatar geometry; reads as a link (color-shift on hover) rather than the account button's filled hover. */ .sidebar-signin { display: flex; align-items: center; gap: 10px; margin-top: 4px; padding: 14px 8px 8px; border-top: 1px solid var(--line); text-decoration: none; color: inherit; } .sidebar-signin[hidden] { display: none; } .acct-avatar--ghost { background: transparent; border: 1px dashed var(--line); color: var(--ink-faint); transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease); } .acct-avatar--ghost svg { width: 15px; height: 15px; } .sidebar-signin:hover .acct-name { color: var(--brand); } .sidebar-signin:hover .acct-avatar--ghost { border-color: var(--brand); color: var(--brand); } .acct-trigger { width: 100%; display: flex; align-items: center; gap: 10px; padding: 7px 8px; border: 1px solid transparent; border-radius: var(--r-2); background: transparent; cursor: pointer; text-align: left; transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); } .acct-trigger:hover { background: var(--bg-hover); } .acct-trigger[aria-expanded="true"] { background: var(--bg-hover); border-color: var(--line); } .acct-avatar { width: 28px; height: 28px; flex-shrink: 0; border-radius: 50%; overflow: hidden; background: var(--brand); color: var(--gray-0); display: inline-flex; align-items: center; justify-content: center; font-size: var(--t-2); font-weight: 700; } .acct-avatar img { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; display: block; } .acct-meta { display: flex; flex-direction: column; gap: 1px; min-width: 0; flex: 1; } .acct-name { font-size: var(--t-2); font-weight: 600; color: var(--ink-strong); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%; } .acct-plan { font-size: var(--t-1); color: var(--ink-faint); letter-spacing: 0.02em; } .acct-chev { width: 15px; height: 15px; flex-shrink: 0; color: var(--ink-faint); transition: transform var(--t-fast) var(--ease); } .acct-trigger[aria-expanded="true"] .acct-chev { transform: rotate(180deg); } .acct-menu { position: absolute; left: 0; right: 0; bottom: calc(100% + 6px); background: var(--gray-0); border: 1px solid var(--line); border-radius: var(--r-3); box-shadow: 0 8px 28px rgba(0, 0, 0, 0.18); padding: 8px; z-index: 30; } .acct-menu[hidden] { display: none; } /* Plan + usage rows in the account menu (2026-07-27). Quiet key/value pairs — this is reference information someone goes looking for, not a pitch. */ .acct-usage { padding: 8px 12px 10px; border-bottom: 1px solid var(--line); } .acct-usage[hidden] { display: none; } .acct-usage-r { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 3px 0; font-size: var(--t-2); color: var(--ink-soft); } /* The label gives way, never the number: a value broken across lines ("0 /" over "30") reads as two different figures. */ .acct-usage-r > span { min-width: 0; } .acct-usage-r b { font-weight: 600; color: var(--ink-strong); font-variant-numeric: tabular-nums; white-space: nowrap; flex-shrink: 0; } /* How much of the month's credits are gone, at a glance. Hairline-quiet like the rest of this block; the fill is the only ink in it. */ .acct-meter { height: 4px; margin: 3px 0 7px; background: var(--line); border-radius: var(--r-hair); overflow: hidden; } .acct-meter-fill { display: block; height: 100%; width: 0; background: var(--ink-strong); border-radius: inherit; } .acct-menu-email { margin: 0 0 8px; padding: 2px 8px 8px; font-size: var(--t-2); line-height: 1.4; color: var(--ink-soft); border-bottom: 1px solid var(--line); word-break: break-all; } /* The menu holds buttons (memory, sign out) and links (profile, network). A link brought its own display, colour and underline from the document defaults, so the two kinds sat at different heights in one list. */ a.acct-menu-item { text-decoration: none; } a.acct-menu-item:hover { text-decoration: none; } .acct-menu-item { width: 100%; display: block; text-align: left; padding: 8px; border: 0; border-radius: var(--r-2); background: transparent; color: var(--ink); font-size: var(--t-2); font-weight: 500; cursor: pointer; transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease); } .acct-menu-item:hover { background: var(--ink-strong); color: var(--gray-0); } /* Upgrade-to-Pro CTA in the account menu (billing.js, free accounts only). A FILLED button, not a transparent row — it is the one action here you'd pay for, so it reads as the primary thing. Reuses the menu-item hover's ink/gray-0 pairing, which is already proven to contrast in both themes. */ .acct-upsell { margin: 4px 0 2px; } .acct-upgrade { background: var(--ink-strong); color: var(--gray-0); text-align: center; font-weight: 600; } .acct-upgrade:hover { background: var(--ink-strong); color: var(--gray-0); opacity: .9; } .model-card { background: var(--gray-0); border: 1px solid var(--line); border-radius: var(--r-3); padding: 10px 12px; } .model-card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px; } .model-card-label { font-size: var(--t-1); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-faint); font-weight: 600; } .status-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--gray-5); display: inline-block; } .status-dot.status-on { background: var(--success); box-shadow: var(--ring) rgba(4, 120, 87, 0.18); } .model-card-name { font-size: var(--t-2); font-weight: 600; color: var(--ink-strong); margin-bottom: 1px; } .model-card-meta { font-family: var(--font-mono); font-size: var(--t-1); color: var(--ink-faint); letter-spacing: 0.01em; } /* ==================================================================== MAIN */ .content { /* Explicit, now that .sidebar is position:fixed (out of grid flow): without this, CSS Grid auto-placement would slot .content into the now-empty first track instead of the second, sliding it under the sidebar. */ grid-column: 2; min-width: 0; display: flex; flex-direction: column; } .topbar { height: var(--topbar-h); /* .content is a column flex container, so the topbar is a flex ITEM — and flex items shrink below their stated height when the column overflows. In the floating-panel layout (.content becomes a fixed, scrolling card) that made a "56px" bar render at 45px, and the height drifted with the length of the page. The hairline under it is one of the three panel-header rules that must sit at the same y as .cp-head's and .sb-head's, so it cannot be content-dependent. */ flex: 0 0 auto; /* Fully opaque so rows scrolling underneath don't show through. The * previous semi-transparent + blur combination caused visual bleeding * with the sticky table header positioned just below. */ background: var(--bg-app); border-bottom: 1px solid var(--line); padding: 0 32px; display: flex; align-items: center; /* Left-aligned cluster (hamburger · title · theme toggle). Was * space-between, which parked the theme toggle in the top-right corner — * where the landing's iframe wrapper overlays its fixed "Sign in" pill, * so the two collided on the /app/ embed. Keeping the toggle on the left * frees the top-right corner for that pill. */ justify-content: flex-start; gap: 14px; position: sticky; top: 0; z-index: 10; } .breadcrumb { display: flex; align-items: center; gap: 6px; font-size: var(--t-2); } .crumb { color: var(--ink-faint); } .crumb-sep { color: var(--ink-disabled); } .crumb-active { color: var(--ink-strong); font-weight: 500; } .topbar-actions { display: flex; gap: 8px; align-items: center; } /* .topbar-net, .topbar-theme: see definitions below the base .theme-toggle rule — same specificity, so they have to come AFTER it in the cascade to actually win. Defining the override here (textually close to the other topbar-actions rules) had it lose silently to the later .theme-toggle block and render at 44px instead of 34px. */ /* Network as a top-bar destination — a labelled pill, deliberately not one of the left-rail tools, sitting beside the notifications it generates. */ .topbar-net { display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 13px; border: 1px solid var(--line-strong); border-radius: var(--r-pill); background: var(--bg-raised); color: var(--ink-soft); font-size: var(--t-2); font-weight: 500; text-decoration: none; white-space: nowrap; -webkit-tap-highlight-color: transparent; transition: border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), background var(--t-fast) var(--ease); } .topbar-net svg { width: 16px; height: 16px; flex-shrink: 0; } .topbar-net:hover { border-color: var(--brand); color: var(--brand); background: var(--brand-50); } .cmd-trigger { display: flex; align-items: center; gap: 8px; padding: 6px 10px 6px 10px; border: 1px solid var(--line-strong); background: var(--gray-0); border-radius: var(--r-2); color: var(--ink-faint); font-size: var(--t-2); box-shadow: var(--elev-1); transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); } .cmd-trigger svg { width: 14px; height: 14px; color: var(--ink-faint); } .cmd-trigger:hover { border-color: var(--line-bold); color: var(--ink-soft); box-shadow: var(--elev-2); } .cmd-trigger span { color: var(--ink-soft); } .cmd-trigger kbd { margin-left: 16px; } /* =============================================================== WORKSPACE */ .workspace { flex: 1; padding: 28px 32px 40px; display: flex; flex-direction: column; gap: 20px; max-width: 1180px; width: 100%; } footer { padding: 20px 32px 24px; text-align: center; border-top: 1px solid var(--line); margin-top: auto; } .footer-note { font-size: var(--t-2); color: var(--ink-faint); letter-spacing: 0.02em; } /* =================================================================== HERO * Editorial register matching the landing's chapter pattern: § kicker + * italic display headline + lede. No more radial gradient, no more * card-with-border, no more grid columns hiding a 4-step decoration * panel on the right. Just a clean opening like the first page of a * publication. */ /* A VIEW HEADER, not a magazine opener. This block ran 185px tall — decorative kicker, 2.6rem italic serif headline, lede — before the canvas showed anything you could act on. Measured: the first control a scientist could touch sat 316px down, roughly 40% of the visible canvas, with 8 of 12 controls below the fold. That is landing-page composition sitting where a tool puts its instrument. SnapGene and Benchling open on the construct. The serif stays — the editorial voice was never the problem. It is a TITLE now rather than a HEADLINE: one step above body, not five. */ .hero { background: transparent; border: 0; border-radius: 0; padding: 14px 0 12px; display: block; box-shadow: none; border-bottom: 1px solid var(--line); margin-bottom: 12px; } /* "§ Begin" named the top of a page you were already looking at — decoration wearing the costume of structure. The numbered card kickers below it, "§ 1 · Sequence" and "§ 2 · Search", stay: an ordinal in a real sequence is information. */ .hero-kicker { display: none; } /* Family, italic and weight come from the display-headline rule further down (`h1, .hero h1, .hero-title, …`), which is later and would win anyway — declaring them here again would just be a dead declaration. At --t-4 the italic serif stops being a magazine headline and reads as a quiet editorial label, which is the voice worth keeping. Only size and metrics here. */ .hero h1 { font-size: var(--t-4); letter-spacing: -0.005em; margin: 0 0 4px; line-height: 1.25; max-width: 40ch; } .hero-lede { font-family: var(--font-body); font-size: var(--t-3); color: var(--ink-soft); max-width: 56ch; line-height: 1.55; margin: 0; } /* Legacy classes hidden in case any stale DOM is still rendered while * the cache busts — they have no business in the new editorial layout. */ .eyebrow, .hero-pipeline, .hero-left, .hero-right, .pipe-step, .pipe-arrow, .pipe-num, .pipe-label { display: none !important; } .hero-pipeline { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } .pipe-step { display: flex; flex-direction: column; gap: 2px; padding: 8px 12px; border: 1px solid var(--line); border-radius: var(--r-2); background: var(--gray-0); min-width: 92px; } .pipe-num { font-family: var(--font-mono); font-size: var(--t-1); color: var(--ink-faint); font-weight: 600; letter-spacing: 0.04em; } .pipe-label { font-size: var(--t-2); color: var(--ink); font-weight: 500; white-space: nowrap; } .pipe-arrow { color: var(--ink-disabled); font-size: var(--t-3); } @media (max-width: 980px) { .hero { grid-template-columns: 1fr; } .hero-pipeline { gap: 6px; } .pipe-step { min-width: 0; padding: 6px 9px; } .pipe-label { font-size: var(--t-2); } } /* =================================================================== CARDS */ .card { background: var(--bg-card); border: 1px solid var(--line); border-radius: var(--r-4); padding: 24px 28px; box-shadow: var(--elev-1); } /* Card header — editorial chapter anatomy: § N kicker + italic display * h2 + lede sub. Replaces the previous boxy "step-num badge + h2 + sub" * pattern that read as Stripe Checkout scaffolding. */ .card-header { display: flex; align-items: flex-end; justify-content: space-between; gap: 20px; margin-bottom: 22px; padding-bottom: 14px; border-bottom: 1px solid var(--line); } .card-header.results-header { padding-bottom: 18px; } .card-header-left { flex: 1 1 0; min-width: 0; } /* Results-header action cluster (Save / My designs / Download / Export). * Without an explicit desktop rule this was an unstyled flex *item* with no * internal layout, so its children claimed their full preferred width and * shrank .card-header-left down to min-content — wrapping the sub-line one * word per line. flex-shrink:0 keeps the buttons as a tidy right-aligned * group that never steals width from the title. */ .results-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; flex-wrap: wrap; flex-shrink: 0; } .card-kicker { font-family: var(--font-mono); font-size: var(--t-2); color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.16em; margin: 0 0 4px; } .card-header h2 { font-family: var(--font-display); font-style: italic; font-weight: 700; font-size: clamp(1.25rem, 2vw, 1.5rem); letter-spacing: -0.005em; color: var(--ink-strong); margin: 0 0 4px; line-height: 1.15; } .card-sub { font-family: var(--font-body); font-size: var(--t-3); color: var(--ink-soft); line-height: 1.55; max-width: 56ch; } /* Step-num badges removed from the UI. Class hidden in case any stale * DOM still renders one while caches bust. */ .step-num { display: none !important; } /* ==================================================================== TABS */ .input-tabs { display: flex; gap: 2px; margin-bottom: 18px; padding: 3px; background: var(--gray-2); border-radius: var(--r-3); width: fit-content; border: 1px solid var(--line); } .tab { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: var(--r-2); font-size: var(--t-2); font-weight: 500; color: var(--ink-soft); transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); } .tab svg { width: 12px; height: 12px; color: currentColor; opacity: 0.8; } .tab:hover { color: var(--ink); } .tab.active { background: var(--gray-0); color: var(--ink-strong); box-shadow: var(--elev-1); font-weight: 600; } .tab-panel { display: none; } .tab-panel.active { display: block; } /* ================================================================ DROPZONE */ /* 205px of empty box for a drop target. It is an affordance, not a hero. */ .dropzone { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 8px; padding: 20px 24px; border: 1.5px dashed var(--line-strong); border-radius: var(--r-3); background: var(--gray-1); cursor: pointer; transition: background var(--t-base) var(--ease), border-color var(--t-base) var(--ease), color var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease), opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease); text-align: center; } .dropzone:hover, .dropzone.drag { border-color: var(--brand); background: var(--brand-50); border-style: solid; } .dz-icon { width: 48px; height: 48px; border-radius: var(--r-3); background: var(--gray-0); border: 1px solid var(--line-strong); display: flex; align-items: center; justify-content: center; color: var(--ink-soft); box-shadow: var(--elev-1); transition: background var(--t-base) var(--ease), border-color var(--t-base) var(--ease), color var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease), opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease); } .dz-icon svg { width: 22px; height: 22px; } .dropzone:hover .dz-icon, .dropzone.drag .dz-icon { color: var(--brand); border-color: var(--brand-200); } .dz-copy { display: flex; flex-direction: column; gap: 2px; } .dz-copy strong { font-size: var(--t-3); font-weight: 600; color: var(--ink-strong); } .dz-copy span { font-size: var(--t-2); color: var(--ink-soft); } .dz-formats { font-size: var(--t-2); color: var(--ink-faint); margin-top: 6px; display: flex; flex-wrap: wrap; gap: 4px; justify-content: center; letter-spacing: 0.02em; } /* ================================================================== INPUTS */ textarea, input[type="text"], input[type="number"], select { width: 100%; padding: 8px 12px; background: var(--gray-0); border: 1px solid var(--line-strong); border-radius: var(--r-2); transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); font-family: var(--font-mono); font-size: var(--t-2); line-height: 1.5; color: var(--ink-strong); box-shadow: var(--elev-1); } textarea { resize: vertical; min-height: 160px; } select { font-family: var(--font-ui); cursor: pointer; appearance: none; background-image: url("data:image/svg+xml;utf8,"); background-repeat: no-repeat; background-position: right 12px center; background-size: 10px; padding-right: 32px; } input:focus, textarea:focus, select:focus { outline: none; border-color: var(--brand); box-shadow: var(--ring) var(--brand-ring); } /* ============================================================= PASTE SHELL */ .paste-shell { display: flex; border: 1px solid var(--line-strong); border-radius: var(--r-2); background: var(--gray-0); overflow: hidden; /* Cap the editor height so a long sequence (e.g. a 1,128-nt CDS) scrolls INSIDE the box instead of stretching it down the page. The gutter + textarea scroll in sync (overflow:hidden gutter + overflow:auto textarea, scrollTop mirrored in JS). */ max-height: 340px; transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); box-shadow: var(--elev-1); } .paste-shell:focus-within { border-color: var(--brand); box-shadow: var(--ring) var(--brand-ring); } .paste-gutter { flex-shrink: 0; padding: 10px 10px 10px 14px; background: var(--gray-2); border-right: 1px solid var(--line); color: var(--ink-faint); font-family: var(--font-mono); font-size: var(--t-2); line-height: 1.5; text-align: right; min-width: 46px; overflow: hidden; user-select: none; white-space: pre; font-variant-numeric: tabular-nums; } .paste-gutter span { display: block; height: calc(1.5em); } .paste-gutter span.error-line { color: var(--danger); font-weight: 600; background: var(--danger-light); } .paste-shell textarea { border: none !important; border-radius: 0; flex: 1; background: transparent; box-shadow: none !important; overflow: auto; white-space: pre; /* The shell owns the height now (max-height + scroll); a user-drag resize would desync the gutter, so disable it. */ resize: none; min-height: 0; } .paste-actions { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 12px; } .paste-stats { font-size: var(--t-2); color: var(--ink-faint); font-variant-numeric: tabular-nums; letter-spacing: 0.04em; text-transform: uppercase; font-weight: 500; } /* ============================================================== BUTTONS */ .primary { display: inline-flex; align-items: center; justify-content: center; gap: 10px; padding: 12px 22px; border-radius: var(--r-2); background: var(--ink-strong); color: var(--gray-0); font-weight: 600; font-size: var(--t-2); letter-spacing: -0.005em; transition: background var(--t-base) var(--ease), border-color var(--t-base) var(--ease), color var(--t-base) var(--ease), box-shadow var(--t-base) var(--ease), opacity var(--t-base) var(--ease), transform var(--t-base) var(--ease); border: 1px solid var(--ink-strong); box-shadow: var(--elev-1); } .primary:not(:disabled):hover { background: var(--hover-fill); border-color: var(--hover-fill); color: var(--hover-text); box-shadow: var(--elev-3); } .primary:not(:disabled):active { transform: translateY(0); box-shadow: var(--elev-2); } .primary:disabled { background: var(--gray-3); color: var(--ink-disabled); border-color: var(--line-strong); cursor: not-allowed; box-shadow: none; } .primary-lg { width: 100%; padding: 16px 24px; font-size: var(--t-3); /* Ink fill — charcoal on light paper, parchment on dark (adapts via tokens), matching the base .primary. Monochrome, no hue. */ background: linear-gradient(180deg, var(--ink) 0%, var(--ink-strong) 100%); color: var(--gray-0); border-color: var(--ink-strong); box-shadow: 0 1px 2px rgba(20, 19, 16, 0.20), inset 0 1px 0 rgba(255,255,255,0.10); } .primary-lg:not(:disabled):hover { background: var(--hover-fill); color: var(--hover-text); border-color: var(--hover-fill); box-shadow: 0 6px 16px rgba(20, 19, 16, 0.22), inset 0 1px 0 rgba(255,255,255,0.10); } .primary-lg:disabled { background: var(--gray-3); color: var(--ink-disabled); } .primary-content { display: flex; flex-direction: column; align-items: flex-start; gap: 1px; } .primary-label { font-weight: 600; } .primary-sub { font-size: var(--t-2); opacity: 0.78; font-weight: 400; letter-spacing: 0; } .primary:disabled .primary-sub { opacity: 1; } .primary-ghost { padding: 8px 16px; border-radius: var(--r-2); border: 1px solid var(--line-strong); background: var(--gray-0); color: var(--ink); font-size: var(--t-2); font-weight: 500; transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); box-shadow: var(--elev-1); } .primary-ghost:hover { background: var(--gray-1); border-color: var(--line-bold); } .ghost { display: inline-flex; align-items: center; gap: 6px; padding: 7px 12px; border-radius: var(--r-2); border: 1px solid var(--line-strong); background: var(--gray-0); color: var(--ink-soft); font-size: var(--t-2); font-weight: 500; transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); box-shadow: var(--elev-1); } .ghost svg { width: 14px; height: 14px; color: currentColor; } .ghost:hover { border-color: var(--brand); color: var(--brand-deep); background: var(--brand-50); } .ghost-sm { padding: 5px 10px; font-size: var(--t-2); } /* ============================================================= PREVIEW */ .preview { margin-top: 20px; padding: 18px 20px; border-radius: var(--r-3); background: var(--gray-1); border: 1px solid var(--line); } .preview-meta { display: flex; gap: 32px; flex-wrap: wrap; margin-bottom: 14px; } .preview-meta > div { display: flex; flex-direction: column; gap: 3px; } .meta-label { font-size: var(--t-1); color: var(--ink-faint); text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600; } .meta-val { font-size: var(--t-3); font-weight: 600; color: var(--ink-strong); font-variant-numeric: tabular-nums; } .pill { padding: 2px 9px; border-radius: var(--r-pill); background: var(--brand-50); color: var(--brand-deep); font-size: var(--t-2); font-weight: 600; width: fit-content; text-transform: capitalize; border: 1px solid var(--brand-100); } .preview-seq { font-family: var(--font-mono); font-size: var(--t-2); color: var(--ink); word-break: break-all; padding: 10px 12px; background: var(--gray-0); border-radius: var(--r-2); border: 1px solid var(--line); letter-spacing: 0.02em; line-height: 1.6; } /* ============================================================ IDENTIFY */ .identify-row { margin-top: 14px; padding-top: 14px; border-top: 1px dashed var(--line-strong); display: flex; align-items: center; gap: 12px; flex-wrap: wrap; } .identify-btn { display: inline-flex; align-items: center; gap: 6px; padding: 7px 12px; border-radius: var(--r-2); border: 1px solid var(--line-strong); background: var(--gray-0); color: var(--ink); font-size: var(--t-2); font-weight: 500; transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); box-shadow: var(--elev-1); } .identify-btn svg { width: 13px; height: 13px; color: currentColor; } .identify-btn:hover { border-color: var(--brand); color: var(--brand-deep); background: var(--brand-50); } .identify-btn:disabled { cursor: progress; opacity: 0.8; } .identify-hint { font-size: var(--t-2); color: var(--ink-faint); line-height: 1.5; } .identify-panel { margin-top: 14px; padding: 14px 16px; background: var(--gray-0); border: 1px solid var(--line); border-radius: var(--r-3); box-shadow: var(--elev-1); } .identify-panel[hidden] { display: none; } .identify-status { display: flex; align-items: center; gap: 10px; font-size: var(--t-2); color: var(--ink-soft); } .identify-spinner { width: 14px; height: 14px; border: 2px solid var(--gray-3); border-top-color: var(--brand); border-radius: 50%; animation: spin 0.75s linear infinite; flex-shrink: 0; } @keyframes spin { to { transform: rotate(360deg); } } .identify-headline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; padding-bottom: 10px; margin-bottom: 12px; border-bottom: 1px solid var(--line); } .identify-headline strong { font-size: var(--t-3); color: var(--ink-strong); font-weight: 600; } .identify-headline .organism { font-size: var(--t-2); color: var(--ink-soft); font-style: italic; } .identify-headline .id-pct { margin-left: auto; padding: 2px 9px; border-radius: var(--r-pill); background: var(--success-light); color: var(--success); font-size: var(--t-2); font-weight: 600; font-variant-numeric: tabular-nums; border: 1px solid rgba(4, 120, 87, 0.22); } .identify-headline .id-pct.id-mid { background: var(--warning-light); color: var(--warning); border-color: var(--on-accent); } .identify-headline .id-pct.id-low { background: var(--gray-2); color: var(--ink-soft); border-color: var(--line-strong); } .identify-hits { display: flex; flex-direction: column; gap: 8px; } .identify-hit { display: grid; grid-template-columns: auto 1fr auto; gap: 12px; padding: 9px 12px; border: 1px solid var(--line); border-radius: var(--r-2); background: var(--gray-1); font-size: var(--t-2); } .identify-hit-acc { font-family: var(--font-mono); color: var(--brand-deep); font-weight: 600; font-size: var(--t-2); } .identify-hit-desc { color: var(--ink); line-height: 1.5; } .identify-hit-org { color: var(--ink-soft); font-style: italic; } .identify-hit-stats { font-family: var(--font-mono); color: var(--ink-faint); font-size: var(--t-2); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; } .identify-actions { display: flex; gap: 8px; margin-top: 12px; flex-wrap: wrap; } .identify-error { color: var(--danger); font-size: var(--t-2); line-height: 1.5; } /* AlphaFold structure viewer (Mol*). Shown below the BLAST hits when the top hit has a UniProt accession. Aspect-ratio'd box so the viewer canvas has a stable size regardless of structure dimensions. */ .alphafold-wrap { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--line); } .alphafold-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; gap: 12px; } .alphafold-head h4 { font-size: var(--t-2); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-soft); font-weight: 700; margin: 0; } .alphafold-meta { font-size: var(--t-2); color: var(--ink-faint); font-family: var(--font-mono); } .alphafold-viewer { width: 100%; /* THE SPECIMEN GETS THE STAGE. This was a flat 360px — the most distinctive thing in the product, the user's own folded protein, boxed smaller than half the viewport while list rows above it took the rest. It now scales with the window and is floored at the old value, so no screen gets less than it had. Capped at 640px because beyond that the model stops gaining detail and starts pushing the controls below the fold. */ height: clamp(360px, 56dvh, 640px); border-radius: var(--r-3); border: 1px solid var(--line); background: #0E141B; overflow: hidden; position: relative; } /* The same overrides apply to the Structure tab's host (.struct-viewer), which * shares Mol*'s default light control theme and would otherwise show dark-brown * icons on our dark canvas. */ .alphafold-viewer .msp-plugin, .struct-viewer .msp-plugin { background: #0E141B !important; } /* Dark-theme Mol*'s own floating viewport controls. Mol* ships a LIGHT control * theme — a cream "semi-transparent" backing strip with dark-brown icons — * which clashes with (and renders nearly invisible on) our dark canvas. Recolor * the strip dark and the icons light so the whole viewer reads as one dark * surface that matches the site. */ .alphafold-viewer .msp-semi-transparent-background, .struct-viewer .msp-semi-transparent-background { background: rgba(14, 20, 27, 0.72) !important; } .alphafold-viewer .msp-viewport-controls-buttons .msp-btn-icon, .struct-viewer .msp-viewport-controls-buttons .msp-btn-icon { color: rgba(233, 237, 243, 0.80) !important; } .alphafold-viewer .msp-viewport-controls-buttons .msp-btn-icon:hover, .struct-viewer .msp-viewport-controls-buttons .msp-btn-icon:hover { color: #fff !important; background: rgba(255, 255, 255, 0.10) !important; } /* ── Mol*'s CONTROLS PANEL ─────────────────────────────────────────────── Not just the viewport strip: the "Structure Tools / Measurements / Quick Styles" column down the right of the viewer. It is where representation lives — cartoon, spacefill, surface, illustrative, per-component visibility — so it is the difference between a picture of a protein and an instrument, and app.js pins it open on any host wide enough for it. It was pinned once before and unpinned for two fair reasons: it arrived in Mol*'s own LIGHT theme, a cream slab on a dark canvas that read as somebody else's software bolted on, and Mol* hands it a flat 300px, which on a narrow bench is a third of the viewer. The theme is fixed HERE. The width is fixed in app.js instead, by only pinning the panel where there is room for it: capping it below 300px was tried first and Quick Styles' four-up row clips "Spacefill" to "Spacefil" at 284px, so the panel keeps Mol*'s width and the canvas keeps a floor. Mol* ships one compiled theme with no CSS variables to swap, so every surface that isn't transparent has to be named individually. The list below is not guesswork: it came from walking the open panel and collecting every computed background above mid-luminance, then re-walking until the collection came back empty. If a Mol* upgrade re-lightens something, run that walk again rather than adding rules by eye. */ .struct-viewer .msp-layout-right, .alphafold-viewer .msp-layout-right, .struct-viewer .msp-layout-standard .msp-layout-right, .alphafold-viewer .msp-layout-standard .msp-layout-right { background: #11171F !important; border-left: 1px solid rgba(233, 237, 243, 0.12) !important; } .struct-viewer .msp-plugin .msp-control-group-header, .alphafold-viewer .msp-plugin .msp-control-group-header { background: #161D26 !important; } /* Every remaining light surface inside the panel: the section header, the flex rows, the control rows and their right-hand control cells, and the unclassed group wrappers Mol* uses for "Apply Representation" / "Apply Style" captions. */ .struct-viewer .msp-layout-right .msp-section-header, .struct-viewer .msp-layout-right .msp-flex-row, .struct-viewer .msp-layout-right .msp-control-row, .struct-viewer .msp-layout-right .msp-control-group-wrapper > div, .struct-viewer .msp-layout-right .msp-control-group-header > div, .alphafold-viewer .msp-layout-right .msp-section-header, .alphafold-viewer .msp-layout-right .msp-flex-row, .alphafold-viewer .msp-layout-right .msp-control-row, .alphafold-viewer .msp-layout-right .msp-control-group-wrapper > div, .alphafold-viewer .msp-layout-right .msp-control-group-header > div { background: #161D26 !important; } .struct-viewer .msp-layout-right .msp-control-row-ctrl, .alphafold-viewer .msp-layout-right .msp-control-row-ctrl { background: #1C242E !important; } .struct-viewer .msp-plugin .msp-control-group-header > button, .struct-viewer .msp-plugin .msp-control-group-header, .struct-viewer .msp-plugin .msp-control-row > span, .struct-viewer .msp-plugin .msp-control-row-label, .struct-viewer .msp-plugin .msp-section-header, .struct-viewer .msp-layout-right .msp-control-row-ctrl, .struct-viewer .msp-layout-right .msp-flex-row, .struct-viewer .msp-layout-right .msp-control-group-wrapper > div, .struct-viewer .msp-layout-right .msp-control-group-header > div, .alphafold-viewer .msp-plugin .msp-control-group-header > button, .alphafold-viewer .msp-plugin .msp-control-row > span, .alphafold-viewer .msp-plugin .msp-control-row-label, .alphafold-viewer .msp-plugin .msp-section-header, .alphafold-viewer .msp-layout-right .msp-control-row-ctrl, .alphafold-viewer .msp-layout-right .msp-flex-row, .alphafold-viewer .msp-layout-right .msp-control-group-wrapper > div, .alphafold-viewer .msp-layout-right .msp-control-group-header > div { color: rgba(233, 237, 243, 0.86) !important; } .struct-viewer .msp-plugin .msp-btn, .struct-viewer .msp-plugin .msp-control-row input[type="text"], .struct-viewer .msp-plugin select, .alphafold-viewer .msp-plugin .msp-btn, .alphafold-viewer .msp-plugin .msp-control-row input[type="text"], .alphafold-viewer .msp-plugin select { background: #1C242E !important; color: rgba(233, 237, 243, 0.86) !important; border-color: rgba(233, 237, 243, 0.14) !important; } .struct-viewer .msp-plugin .msp-btn:hover, .alphafold-viewer .msp-plugin .msp-btn:hover { background: #263140 !important; color: #fff !important; } /* The panel scrolls, and a default light scrollbar down its edge undoes the rest of this. Firefox gets the standard property, Chromium the -webkit one. */ .struct-viewer .msp-layout-right *, .alphafold-viewer .msp-layout-right * { scrollbar-color: rgba(233, 237, 243, 0.24) transparent; scrollbar-width: thin; } .struct-viewer .msp-layout-right *::-webkit-scrollbar, .alphafold-viewer .msp-layout-right *::-webkit-scrollbar { width: 9px; height: 9px; background: transparent; } .struct-viewer .msp-layout-right *::-webkit-scrollbar-thumb, .alphafold-viewer .msp-layout-right *::-webkit-scrollbar-thumb { background: rgba(233, 237, 243, 0.20); border-radius: var(--r-1); } .struct-viewer .msp-layout-right *::-webkit-scrollbar-thumb:hover, .alphafold-viewer .msp-layout-right *::-webkit-scrollbar-thumb:hover { background: rgba(233, 237, 243, 0.34); } .alphafold-loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; color: var(--gray-3); font-size: var(--t-2); gap: 10px; background: #0E141B; z-index: 2; } .alphafold-loading .identify-spinner { border-color: rgba(255,255,255,0.18); border-top-color: var(--brand-light); } .alphafold-disclaimer { margin-top: 8px; font-size: var(--t-2); color: var(--ink-faint); line-height: 1.5; } .alphafold-disclaimer a { color: var(--brand-deep); text-decoration: underline; text-underline-offset: 2px; } /* ============================================================ CDS PICKER */ .cds-picker { margin-top: 20px; padding-top: 20px; border-top: 1px solid var(--line); } .cds-picker h3 { font-size: var(--t-3); font-weight: 600; color: var(--ink-strong); margin-bottom: 4px; } .muted { color: var(--ink-faint); font-size: var(--t-2); line-height: 1.5; margin-bottom: 12px; } .cds-list { display: flex; flex-direction: column; gap: 6px; } .cds-item { display: flex; align-items: center; gap: 14px; padding: 11px 16px; border-radius: var(--r-2); border: 1px solid var(--line); cursor: pointer; transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease), color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); background: var(--gray-0); } .cds-item:hover { border-color: var(--line-bold); background: var(--gray-1); } .cds-item.selected { border-color: var(--brand); background: var(--brand-50); box-shadow: var(--ring) var(--brand); } .cds-name { flex: 1; font-weight: 500; font-family: var(--font-mono); font-size: var(--t-2); color: var(--ink-strong); } .cds-length { color: var(--ink-faint); font-size: var(--t-2); font-variant-numeric: tabular-nums; } .cds-item.marker .cds-name { color: var(--ink-faint); } .cds-item.marker::after { content: 'marker'; font-size: var(--t-1); font-weight: 600; color: var(--warning); padding: 2px 8px; background: var(--warning-light); border-radius: var(--r-pill); border: 1px solid #FDE68A; letter-spacing: 0.02em; } /* ============================================================ SETTINGS */ .setting-grid { /* 6-column grid so we can place 3 narrow number fields across row 1 (each spans 2) and 2 wider dropdown fields across row 2 (each spans 3). Result is a 3-up / 2-up arrangement where both rows sum to the full width — no orphan field on the second row. */ display: grid; grid-template-columns: repeat(6, 1fr); gap: 18px 22px; } .setting-grid > .setting:nth-child(1), .setting-grid > .setting:nth-child(2), .setting-grid > .setting:nth-child(3) { grid-column: span 2; } .setting-grid > .setting:nth-child(4), .setting-grid > .setting:nth-child(5) { grid-column: span 3; } @media (max-width: 880px) { /* On narrower viewports the 6-track grid leaves the dropdown labels (e.g. "Saccharomyces cerevisiae") truncated. Collapse to a single column — each label/input pair stacks cleanly, no orphan row. */ .setting-grid { grid-template-columns: 1fr; gap: 14px; } .setting-grid > .setting:nth-child(n) { grid-column: auto; } } .setting { display: flex; flex-direction: column; gap: 5px; } .setting-name { font-size: var(--t-2); font-weight: 600; color: var(--ink-strong); } .setting-hint { font-size: var(--t-2); color: var(--ink-faint); margin-bottom: 6px; line-height: 1.5; } /* ── Model picker ──────────────────────────────────────────────────────── Two cards instead of a has long option labels ("Homo sapiens (GRCh38) · coding regions …"); with a plain 1fr the track grows to fit the selected option and pushes the whole page wider than the viewport (horizontal scroll on mobile). */ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px 22px; padding: 10px 0 6px; border-top: 1px solid var(--line); } .crispr-context-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; /* allow the field (and its laid out inside a 52px spine. It showed up in the collapsed rail looking like a stray widget dropped on the workspace. */ body[data-cockpit="min"] .cp-transcript, body[data-cockpit="min"] .cp-jump, body[data-cockpit="min"] .cp-model, body[data-cockpit="min"] .cp-compose { display: none; } } /* Tool INPUTS, shown next to the verb. Mono because it's literal machine input — the organism here is the one actually sent, not a paraphrase. */ .cp-tool-args { font-family: var(--font-mono); font-size: var(--t-1); color: var(--ink-soft); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; padding-left: 6px; border-left: 1px solid var(--line-strong); /* Do NOT shrink. `TP53 · human` truncated to `TP…` is worse than useless: the organism actually sent is the single most consequential thing on the row (a mouse gene folded against the human proteome is a confident wrong answer), and it was the first thing the old single-line row threw away. 0 0 auto means it wraps to its own line rather than being crushed; max-width keeps a pathologically long arg list inside the box. */ flex: 0 0 auto; max-width: 100%; } /* A message typed while the agent was working. It is no longer held — it goes straight through as a course correction — but it stays tagged, so reading a run back later answers "why did it switch to mouse at this point?". */ .cp-user { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; } /* THE CUT-OFF TEXT. Making the user's turn a flex row (for the STEER tag) turned its text into a flex item, and a flex item's default `min-width: auto` refuses to shrink below its MIN-CONTENT width. `overflow-wrap: break-word` on .cp-turn does not help here: break-word permits a break at render time but deliberately does not reduce the intrinsic min-content size, so the box still asks for the width of the longest unbroken token. Paste the 286-residue TEM-1 protein from the review and that token is the whole sequence: measured at 2,389px of text inside a 391px rail — 2,024px running off the right edge, unwrapped and unreachable. `overflow-wrap: anywhere` is the one that DOES shrink min-content, and min-width:0 removes the floor as well. Ordinary prose is unaffected: like break-word, `anywhere` only breaks a word that would otherwise overflow. Note this is the fallback, not the good outcome. A bare sequence still reads better as a .cp-seq card (mono, FASTA-wrapped, copyable). md() routes nucleotide lines there already; its bare-sequence test is `/^[ACGTUN]+$/i`, so protein never matches. Widening that test is the real fix. */ .cp-user > div { min-width: 0; overflow-wrap: anywhere; } .cp-q-tag { font-family: var(--font-mono); font-size: var(--t-1); letter-spacing: 0.08em; text-transform: uppercase; color: var(--ink-faint); flex: 0 0 auto; } /* The agent's plan. One node per run, rewritten in place as steps land — the thing that lets a scientist tell at step 1 whether it understood them, instead of finding out at step 9. */ .cp-plan { /* --line / --bg-app, matching .cp-tool. An earlier draft used --rule and --surface-2, which do not exist in this stylesheet: the border silently computed to `none` (an invalid var() poisons the whole shorthand) and the panel rendered as floating text. */ border: 1px solid var(--line); border-radius: var(--r-3); padding: 10px 12px; background: var(--bg-app); } .cp-plan-h { font-family: var(--font-mono); font-size: var(--t-1); letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-faint); margin-bottom: 7px; } /* .cp-turn ul (0,1,1) out-specifies a bare .cp-plan-list (0,1,0), so the generic transcript list styling won — disc bullets next to our own glyphs, indented 20px. Qualify it to win. */ .cp-turn ul.cp-plan-list { list-style: none; margin: 0; padding: 0; } .cp-turn ul.cp-plan-list > li { margin: 0; padding-left: 0; } .cp-plan-i { display: flex; gap: 8px; align-items: baseline; font-size: var(--t-2); line-height: 1.5; padding: 1.5px 0; } .cp-plan-g { flex: 0 0 12px; font-family: var(--font-mono); font-size: var(--t-2); } .cp-plan--done { color: var(--ink-faint); } .cp-plan--done span:last-child { text-decoration: line-through; } .cp-plan--active { color: var(--ink-strong); font-weight: 500; } .cp-plan--pending { color: var(--ink-soft); } .cp-plan--skipped { color: var(--ink-faint); opacity: .7; } .cp-plan--skipped span:last-child { text-decoration: line-through; } /* Context was compacted. Stated plainly rather than hidden — a transcript with a silent gap in it is worse than one that admits the gap. */ .cp-note { font-family: var(--font-mono); font-size: var(--t-1); letter-spacing: 0.05em; color: var(--ink-faint); text-transform: uppercase; border-top: 1px solid var(--line); padding-top: 8px; } @media (min-width: 901px) { /* COLLAPSED IS THE SAME SHELL WITH A NARROWER RAIL. Turing is a SIDEBAR. Retracting it should slide it into the left edge and hand the width to the workspace — the same thing the nav sidebar already does. Two early attempts made it a 236px card pinned first to the top-left (which buried the Structure/Library/Map tabs) and then to the bottom-left (which still floated over the canvas and read as a stray widget). Both were wrong in the same way: a card is not a retracted sidebar. The third attempt got the spine right but kept a SECOND, hand-written geometry for it, because every desktop shell rule above was gated on [data-cockpit="open"]. Collapsing therefore dropped the floating panels, the 56px icon sidebar and .content's fixed positioning all at once and swapped in that other layout — which is the state the founder screenshot showed: the nav snapping back to 232px, the spine stranded mid-screen, the composer's model picker spilling across the workspace, and the content card starting hundreds of pixels right of where either layout intended. Those rules are now gated on [data-cockpit] alone, so open and collapsed are ONE layout. Which leaves this: --bench-rail is the one width the whole thing keys off — the cockpit's width, the `left` of .bench-canvashead and .workspace, and the margin/left of .app and .content in the shell above. Shrinking it IS the collapse. One state change changes one number. (cockpit.js clears the drag handle's inline --bench-rail on the way in; an inline custom property beats this rule, so without that a rail you had ever dragged could not collapse at all.) */ body[data-cockpit="min"] { --bench-rail: var(--cp-spine); } /* Nothing pokes out of a 52px column. The transcript and composer are display:none at this width already (see the rule above this block); the clip is the backstop for anything later added to the rail that forgets to be — the model picker under the composer was exactly that. */ body[data-cockpit="min"] .cockpit { overflow: hidden; } /* THE SPINE'S CONTENTS: the expander, then the wordmark. Nothing else. The header is a horizontal row of five controls at 392px and none of it survives 52px — the first version left the +, history and export buttons in, plus the context meter, which is why it looked like a column of broken widgets rather than a retracted panel. Squeezing a horizontal layout into a vertical one is the "empty boxes" mistake in a new costume: replace it, do not compress it. */ body[data-cockpit="min"] .cp-head { flex-direction: column; padding: 8px 0 0; height: auto; gap: 14px; border-bottom: 0; justify-content: flex-start; align-items: center; } /* Everything that belongs to the expanded panel, gone. */ body[data-cockpit="min"] .cp-state, body[data-cockpit="min"] .cp-stop, body[data-cockpit="min"] .cp-meter, body[data-cockpit="min"] .cp-menu { display: none; } /* The expander first, so the way out is the top thing you see, at a full 44px target rather than the 26px icon the expanded header uses. */ body[data-cockpit="min"] #cpCollapse { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; flex: 0 0 auto; border: 1px solid var(--line); border-radius: var(--r-2); background: transparent; color: var(--ink-soft); font-size: var(--t-2); line-height: 1; transform: scaleX(-1); /* the chevron points OUT, not in */ } body[data-cockpit="min"] #cpCollapse:hover { color: var(--ink-strong); border-color: var(--line-strong); } body[data-cockpit="min"] .cp-mark { writing-mode: vertical-rl; text-orientation: mixed; letter-spacing: 0.24em; font-size: var(--t-1); color: var(--ink-faint); order: 2; } } @media (max-width: 900px) { /* Collapsed on a phone: a single bar pinned to the bottom, so the app is usable again. This is the fix for the rail covering 70% of the viewport with no way to dismiss it. */ body[data-cockpit="min"] .cockpit { height: auto; top: auto; border-radius: var(--r-4) var(--r-4) 0 0; bottom: var(--kb-inset, 0px); /* see the note on the open sheet */ /* Keep clear of the iOS home indicator. */ padding-bottom: env(safe-area-inset-bottom, 0px); } } /* "Jump to latest" — appears only when the user has scrolled away from the tail while the agent is still writing. The alternative (auto-scrolling regardless) made the transcript unreadable mid-run: you could not hold your place on an earlier result, which is exactly when you want to. */ .cp-jump { position: absolute; left: 50%; transform: translateX(-50%); bottom: 76px; z-index: 2; font-family: var(--font-mono); font-size: var(--t-1); letter-spacing: 0.04em; padding: 6px 13px; border-radius: var(--r-pill); cursor: pointer; background: var(--ink-strong); color: var(--on-ink); border: 1px solid var(--ink-strong); box-shadow: var(--elev-2, 0 2px 10px rgba(0,0,0,0.18)); } .cp-jump:hover { opacity: 0.88; } /* Hand-off to the full tool. Shown when the bench can't display a result itself — the agent's tools are chat-sized, so this loads the INPUT into the real panel rather than pretending a truncated run is a complete one. */ .cp-handoff { display: block; width: 100%; margin-top: 8px; font-family: var(--font-mono); font-size: var(--t-1); letter-spacing: 0.03em; padding: 7px 10px; text-align: left; cursor: pointer; background: transparent; color: var(--ink-strong); border: 1px solid var(--line-strong); border-radius: var(--r-2); } .cp-handoff:hover { border-color: var(--ink-strong); background: var(--bg-card); } /* Network handoff row — "Share to Network" / "Ask the Network" side by side. Same weight as .cp-handoff (a quiet, considered exit into the social layer), but two actions share the row rather than each spanning full width. */ .cp-net-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; } .cp-net-share { flex: 1 1 auto; min-width: 0; font-family: var(--font-mono); font-size: var(--t-1); letter-spacing: 0.03em; padding: 7px 10px; text-align: center; cursor: pointer; background: transparent; color: var(--ink-strong); border: 1px solid var(--line-strong); border-radius: var(--r-2); } .cp-net-share:hover { border-color: var(--ink-strong); background: var(--bg-card); } .cp-net-share:disabled { cursor: wait; opacity: .62; } .cp-net-error { flex-basis: 100%; color: var(--danger); font-size: var(--t-1); line-height: 1.45; } /* ── "From your conversation with Turing" ─────────────────────────────── The banner on a panel the agent painted rather than the user ran. It is load-bearing, not decoration: the table below it is the real table with real numbers, but a chat run is capped and skips the opt-in network steps, and a ten-row library with no caption reads as a full search. Red left edge because it qualifies what's under it — same role the research-use strip plays at the top of the workspace. */ .agent-run-note { margin: 0 0 16px; padding: 11px 14px; background: var(--bg-subtle, var(--bg-card)); border: 1px solid var(--line); border-left: 2px solid var(--danger); border-radius: var(--r-3); font-size: var(--t-2); line-height: 1.55; color: var(--ink-soft); } .agent-run-note strong { color: var(--ink-strong); font-weight: 600; } /* A painted Directed Evolution library has no server-side job behind it, so the controls that address one by id would 404. Hide them rather than let them fail; the banner says to run the full tool for both. */ body.de-agent-run #downloadMenu, body.de-agent-run .dna-edit-actions { display: none; } /* ══════════════════════════════════════════════ hidden must mean hidden ══ Eight elements in this app carried the `hidden` attribute and rendered anyway. One cause, and it looks like correct CSS in every diff: the UA sheet's `[hidden] { display: none }` is an AUTHOR-vs-UA contest, not a specificity contest. Any author `display:` on the element wins, however low its specificity. So `.run-meta { display: flex }` quietly cancelled `