Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | |
| <title>TuringDNA · Engine</title> | |
| <meta name="description" content="TuringDNA — talk to Turing and it runs the lab: directed evolution, CRISPR guide design, primer analysis, and a plasmid editor, all callable from one conversation." /> | |
| <meta name="theme-color" content="#1b1a17" /> | |
| <meta name="color-scheme" content="dark light" /> | |
| <meta name="referrer" content="strict-origin-when-cross-origin" /> | |
| <!-- The canonical product lives at turingdna.com; the raw engine URL isn't indexed. --> | |
| <meta name="robots" content="noindex,follow" /> | |
| <!-- Theme bootstrap — runs BEFORE the stylesheet so <html data-theme> is | |
| set before first paint (no flash on load). Dark is the default; a | |
| saved choice (from the topbar toggle) overrides it. First-party | |
| inline script; touches only our own localStorage + documentElement. --> | |
| <script> | |
| (function () { | |
| try { | |
| var t = localStorage.getItem('td-theme'); | |
| if (t !== 'light' && t !== 'dark') { t = 'dark'; } // dark by default | |
| document.documentElement.setAttribute('data-theme', t); | |
| } catch (e) { | |
| document.documentElement.setAttribute('data-theme', 'dark'); | |
| } | |
| })(); | |
| </script> | |
| <!-- Viewport-height bootstrap — the "--vh" custom-property technique. | |
| `100dvh`/`100vh` alone have shipped genuinely inconsistent across | |
| engine versions (this repo has hit two separate cases of Safari | |
| computing a taller box than the real visible viewport, cutting off | |
| bottom-pinned UI — the sidebar's account chip and the Turing | |
| composer). window.innerHeight has been reliable across every | |
| browser for over a decade, so measuring it directly and feeding it | |
| into CSS as a custom property sidesteps whatever the unit itself | |
| gets wrong. Runs before first paint; --vh isn't set until this runs, | |
| so every consumer uses `var(--vh, 1vh)` — falls back to the native | |
| unit for the one frame before this executes. --> | |
| <!-- Confirmed directly on real Safari 15.6.1: getBoundingClientRect() | |
| already reports the CORRECT, uncut geometry at rest — the layout | |
| is right, but Safari doesn't always repaint the screen to match a | |
| `--vh` change made via a custom property. The stale frame (sidebar | |
| account chip clipped) stays on screen until something forces a | |
| repaint — confirmed a user scroll gesture IS that trigger (the | |
| clipped content flashes correctly into view mid-scroll, then | |
| reverts once scrolling settles). Nudge a repaint ourselves right | |
| after every --vh change instead of waiting for an accidental one. --> | |
| <script> | |
| (function () { | |
| // Nudges the sidebar specifically, not <html>/<body> — transforming | |
| // an ancestor of a position:fixed element (the sign-in scrim, any | |
| // modal) temporarily makes THAT ancestor the fixed element's | |
| // containing block instead of the viewport, which would visibly | |
| // mis-jump anything fixed open at that instant. The sidebar itself | |
| // has no fixed descendants, so it's a safe, targeted repaint kick. | |
| function nudgeRepaint() { | |
| var el = document.querySelector('.sidebar'); | |
| // Also bail if a nudge is already in flight — without this guard, | |
| // an overlapping call captures the OTHER call's in-progress | |
| // "translateZ(0)" as its own "previous" value, so whichever one | |
| // reverts last leaves the transform stuck on permanently. | |
| if (!el || el.dataset.nudging) return; | |
| el.dataset.nudging = '1'; | |
| var prev = el.style.transform; | |
| el.style.transform = 'translateZ(0)'; | |
| void el.offsetHeight; | |
| requestAnimationFrame(function () { | |
| el.style.transform = prev; | |
| delete el.dataset.nudging; | |
| }); | |
| } | |
| function setVH() { | |
| document.documentElement.style.setProperty('--vh', (window.innerHeight * 0.01) + 'px'); | |
| nudgeRepaint(); | |
| } | |
| setVH(); | |
| window.addEventListener('resize', setVH); | |
| // This script runs in <head>, before .sidebar exists, so the very | |
| // first nudgeRepaint() above is a no-op. Fire it again once the | |
| // sidebar is actually in the DOM — the bug reproduces on a plain | |
| // page load, not just after a resize. | |
| document.addEventListener('DOMContentLoaded', nudgeRepaint); | |
| // TEMP diagnostic — every layout measurement taken directly against | |
| // this page (via osascript `do JavaScript` on the user's real Safari) | |
| // has come back correct, every time, even before any of today's | |
| // fixes. The one thing never directly inspected is this exact page | |
| // AS EMBEDDED in turingdna.com's cross-origin iframe wrapper — that | |
| // context can't be scripted into from the parent (same-origin | |
| // policy), so report our own numbers TO the parent instead, where | |
| // they're readable. Safe no-op when not framed. Remove once the | |
| // wrapper-embedding question is settled either way. | |
| document.addEventListener('DOMContentLoaded', function () { | |
| if (window.self === window.top) return; | |
| try { | |
| var sidebar = document.querySelector('.sidebar'); | |
| var signin = document.getElementById('sidebarSignin'); | |
| var sr = signin ? signin.getBoundingClientRect() : null; | |
| window.parent.postMessage({ | |
| type: 'td-diag', | |
| innerHeight: window.innerHeight, | |
| vh: getComputedStyle(document.documentElement).getPropertyValue('--vh'), | |
| sidebarHeight: sidebar ? Math.round(sidebar.getBoundingClientRect().height) : null, | |
| signinBottom: sr ? sr.bottom : null, | |
| signinWithinViewport: sr ? (sr.bottom <= window.innerHeight) : null, | |
| visibilityState: document.visibilityState | |
| }, '*'); | |
| } catch (e) {} | |
| }); | |
| })(); | |
| </script> | |
| <!-- ?v= query bumps invalidate browser + iframe asset caches when app.css / | |
| app.js change. Bump these numbers whenever you ship a frontend update — | |
| without them, users keep getting the stale file for up to a week. --> | |
| <link rel="stylesheet" href="/static/app.css?v=20260801-review" /> | |
| <!-- The work catalog + the draggable rail. Kept out of app.css so two new | |
| self-contained surfaces stay reviewable; every colour is an app.css | |
| token, so both themes work with nothing added. --> | |
| <link rel="stylesheet" href="/static/catalog.css?v=20260801-review" /> | |
| <!-- Units, the run strip and the decision trace. Same reasoning as | |
| catalog.css: kept out of app.css so a self-contained surface stays | |
| reviewable, and every colour is an app.css token so both themes | |
| work with nothing added. --> | |
| <link rel="stylesheet" href="/static/trace.css?v=20260801-review" /> | |
| <!-- Disclosure surfaces added in the 2026-08 scientific review (CRISPR | |
| scope strip, provenance "Assumes" line). Separate file for the same | |
| reason as catalog.css: small, self-contained, and app.css's cascade | |
| is hostile. Uses app.css tokens only, so both themes work. --> | |
| <link rel="stylesheet" href="/static/science.css?v=20260801-review" /> | |
| <link rel="icon" type="image/svg+xml" href="/static/favicon.svg?v=2" /> | |
| <link rel="apple-touch-icon" href="/static/favicon.svg?v=2" /> | |
| <!-- Mol* (PDBe) 3-D viewer is ~4.9 MB. We do NOT eager-load it on every | |
| page view — app.js ensureMolstar() loads it lazily the first time a | |
| Fold / AlphaFold viewer is opened, with a jsDelivr → unpkg fallback and | |
| no fixed timeout (the old eager `defer` + 8 s wait was why both viewers | |
| "failed to load" so often on slower links). Preconnect just warms the | |
| CDNs so that first lazy load is fast. --> | |
| <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin /> | |
| <link rel="preconnect" href="https://unpkg.com" crossorigin /> | |
| </head> | |
| <body> | |
| <!-- Bio-luminous atmospheric backdrop (ambient glow + vignette + grain), | |
| painted under the app; dark-mode only. Fades in on load. --> | |
| <div class="bg-atmos" id="bgAtmos" aria-hidden="true"></div> | |
| <!-- (Cursor-following light removed — gimmick. Only the static ambient | |
| backdrop remains; fade it in once painted.) --> | |
| <script> | |
| (function(){ | |
| var a=document.getElementById('bgAtmos'); if(a) requestAnimationFrame(function(){a.classList.add('lit');}); | |
| })(); | |
| </script> | |
| <!-- One-time material-change notice (Privacy/Terms v2.0). Dismiss persists in localStorage. --> | |
| <div id="policyNotice" class="policy-notice" hidden> | |
| <span class="policy-notice-text">We've updated our <a href="https://turingdna.com/privacy/" target="_blank" rel="noopener">Privacy Policy</a> & <a href="https://turingdna.com/terms/" target="_blank" rel="noopener">Terms</a> (v2.0, effective 8 Jul 2026): we now use <strong>anonymous, aggregated</strong> signals to improve our models. Your individual sequences stay private — never shared, exposed, or reproduced. Prefer not to continue? You can export or delete your data before then (<a href="https://turingdna.com/contact/" target="_blank" rel="noopener">contact us</a>).</span> | |
| <button type="button" id="policyNoticeClose" class="policy-notice-close">Got it</button> | |
| </div> | |
| <!-- | |
| ⌘K finder. Navigation only — constructs, past runs, tools. It is | |
| deliberately NOT a chat entry: the Turing rail is the only place you | |
| talk to the agent, so this can never become a second composer again. | |
| --> | |
| <div class="finder" id="finder" hidden> | |
| <div class="finder-scrim" id="finderScrim"></div> | |
| <div class="finder-box" role="dialog" aria-modal="true" aria-label="Find"> | |
| <div class="finder-in"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg> | |
| <input id="finderQ" type="text" autocomplete="off" spellcheck="false" | |
| placeholder="Find a construct, run, or tool…" aria-label="Find" /> | |
| <kbd>esc</kbd> | |
| </div> | |
| <div class="finder-list" id="finderList" role="listbox"></div> | |
| </div> | |
| </div> | |
| <div class="app"> | |
| <!-- ============================== SIDEBAR ============================== --> | |
| <aside class="sidebar" id="sidebar"> | |
| <!-- The brand sits in a header BAND, not loose at the top of the | |
| rail: the band is exactly --topbar-h tall and carries the | |
| hairline, so the sidebar's rule lands on the same y as the | |
| cockpit's (.cp-head) and the workspace's (.topbar). Without | |
| it the three panels each started their content at a | |
| different height and the lines stepped. --> | |
| <div class="sb-head"> | |
| <a class="brand" href="/" title="TuringDNA"> | |
| <span class="brand-mark" aria-hidden="true"> | |
| <!-- Living logo: perpetually-writhing supercoiled plasmid | |
| (Canvas, animated by initPlasmidLogo() in app.js). --> | |
| <canvas class="brand-mark-canvas" aria-hidden="true"></canvas> | |
| </span> | |
| <span class="brand-word">TuringDNA</span> | |
| </a> | |
| </div> | |
| <!-- | |
| Sidebar nav — restructured 2026-07-13 to stop reading as a flat, | |
| scattered pile of eight coequal links (the founder's note: "looks | |
| like a vibecoded AI platform"). The cure is HIERARCHY, not more | |
| chrome: | |
| • Turing is the FRONT DOOR — a lead block set above everything, | |
| the way in (it drives the whole loop in plain language). | |
| • The four tools are ONE connected sequence — the Design→Build→ | |
| Edit→Verify loop — tied together by a hairline "spine" so they | |
| read as a workflow, not four islands. Each carries its loop | |
| phase as a quiet caption. | |
| • Utilities (Start here / My designs / Docs) drop to a muted | |
| tier pinned above the account foot — present, not competing. | |
| Still type-driven and icon-free (the earlier debug widget + SVG | |
| icons + "Workspace/Reference" bucket labels were removed for | |
| reading as SaaS scaffolding); the structure now carries the | |
| meaning the flat list couldn't. | |
| --> | |
| <nav class="nav"> | |
| <!-- Mission control — bench-mode home (construct portfolio + loop). | |
| Home sits first; hidden in classic UI via CSS (.nav-item--mission). --> | |
| <a class="nav-item nav-item--mission" href="#mission" title="Mission control"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="3.5" y="3.5" width="7" height="7" rx="1"/> | |
| <rect x="13.5" y="3.5" width="7" height="7" rx="1"/> | |
| <rect x="3.5" y="13.5" width="7" height="7" rx="1"/> | |
| <rect x="13.5" y="13.5" width="7" height="7" rx="1"/> | |
| </svg> | |
| </span> | |
| <span>Mission control</span> | |
| </a> | |
| <!-- | |
| Turing — the LLM agent that drives the whole loop in plain | |
| language, and the workbench HUB + DEFAULT view (2026-07-12, | |
| superseding the 2026-06-04 Plasmid-Editor-as-hub decision made | |
| before tool-calling existed). Embeds the chat panel | |
| (assistant.html?embed=1) so the chrome stays. Full tool-calling | |
| backend — fetch_sequence, design_variant_library, | |
| design_crispr_guides, design_primers — drives the four tools | |
| below; they're its verbs, not its peers. Rendered as the rail's | |
| lead block to make that hierarchy visible. | |
| --> | |
| <a class="nav-item nav-lead active" id="navTuring" href="#turing" title="Turing"> | |
| <span class="nav-icon nav-lead-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M21 11.5a8.5 8.5 0 0 1-12.1 7.7L3.5 20.5l1.3-5.4A8.5 8.5 0 1 1 21 11.5z"/> | |
| <path d="M9.2 10.6l1.6 2 2.8-3.2"/> | |
| </svg> | |
| </span> | |
| <span class="nav-lead-text"> | |
| <span class="nav-lead-word">Turing</span> | |
| <span class="nav-lead-sub">Engineer in plain language</span> | |
| </span> | |
| </a> | |
| <!-- | |
| THE LOOP — the four tools as one ordered workflow, not a | |
| scattered tab-list. A hairline spine (.nav-loop::before) ties | |
| them together; each name carries its loop phase on the right. | |
| · Directed Evolution — ESM-2 zero-shot variant libraries (Design) | |
| · Plasmid Editor — map / annotate / clone a construct (Build) | |
| · CRISPR — knockout & base-edit guide design (Edit) | |
| · Primer Analysis — score & rank candidate primers (Verify) | |
| All sign-in gated like the rest; hrefs unchanged so showRoute's | |
| active-state + the mobile drawer-close still key off .nav-item. | |
| --> | |
| <div class="nav-loop"> | |
| <p class="nav-loop-lbl">The loop</p> | |
| <a class="nav-item nav-step" href="#design" data-analytics="nav-design" title="Directed Evolution"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M7 3c0 4.5 10 6 10 9s-10 4.5-10 9"/> | |
| <path d="M17 3c0 4.5-10 6-10 9s10 4.5 10 9"/> | |
| <path d="M8.5 6.5h7M8.5 17.5h7M7.6 9.5h8.8M7.6 14.5h8.8"/> | |
| </svg> | |
| </span> | |
| <span class="nav-step-nm">Directed Evolution</span> | |
| <span class="nav-step-ph">Design</span> | |
| </a> | |
| <a class="nav-item nav-step" href="#plasmid" data-analytics="nav-plasmid" title="Plasmid Editor"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="12" cy="12" r="8"/> | |
| <path d="M12 4v2.6M20 12h-2.6M12 20v-2.6M4 12h2.6"/> | |
| </svg> | |
| </span> | |
| <span class="nav-step-nm">Plasmid Editor</span> | |
| <span class="nav-step-ph">Build</span> | |
| </a> | |
| <a class="nav-item nav-step" href="#crispr" data-analytics="nav-crispr" title="CRISPR"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="6" cy="6" r="2.6"/> | |
| <circle cx="6" cy="18" r="2.6"/> | |
| <path d="M8.4 7.8L20 18M8.4 16.2L20 6M11 12l-2.6 1.6"/> | |
| </svg> | |
| </span> | |
| <span class="nav-step-nm">CRISPR</span> | |
| <span class="nav-step-ph">Edit</span> | |
| </a> | |
| <a class="nav-item nav-step" href="#primers" data-analytics="nav-primers" title="Primer Analysis"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="10.5" cy="10.5" r="6.5"/> | |
| <path d="M20 20l-4.7-4.7"/> | |
| </svg> | |
| </span> | |
| <span class="nav-step-nm">Primer Analysis</span> | |
| <span class="nav-step-ph">Verify</span> | |
| </a> | |
| </div> | |
| <!-- | |
| Utilities — quiet secondary tier, pushed to the foot of the nav | |
| (margin-top:auto) so it sits just above the account row without | |
| competing with the workflow. | |
| · Start here — goal router modal (role=button, not a route). | |
| · My designs — saved-work hub modal (2026-05-30); anonymous | |
| click routes to sign-in via the shared guard. | |
| · Documentation — how it all works. | |
| (Library/History tabs were removed 2026-05-27 — they read the | |
| Space-shared output dir, a single-user relic; the per-user | |
| dashboard on the landing site replaces them.) | |
| --> | |
| <div class="nav-utils"> | |
| <a class="nav-item nav-util" id="navStartHere" role="button" tabindex="0" title="Start here"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <circle cx="12" cy="12" r="9"/> | |
| <path d="M15.5 8.5l-2.1 4.9-4.9 2.1 2.1-4.9 4.9-2.1z"/> | |
| </svg> | |
| </span> | |
| <span>Start here</span> | |
| </a> | |
| <a class="nav-item nav-util" id="navMyDesigns" role="button" tabindex="0" title="My designs"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M3 7a2 2 0 0 1 2-2h3.5l2 2H19a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V7z"/> | |
| </svg> | |
| </span> | |
| <span>My designs</span> | |
| </a> | |
| <a class="nav-item nav-util" id="navAtlas" role="button" tabindex="0" title="Field atlas"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <rect x="3.5" y="3.5" width="17" height="17" rx="1"/> | |
| <path d="M9 3.5v17M15 3.5v17M3.5 9h17M3.5 15h17"/> | |
| </svg> | |
| </span> | |
| <span>Field atlas</span> | |
| </a> | |
| <a class="nav-item nav-util" href="#docs" title="Documentation"> | |
| <span class="nav-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M6 3.5h8L18.5 8v12.5H6z"/> | |
| <path d="M13.5 3.5V8h4.5M9 12.5h6M9 16h4"/> | |
| </svg> | |
| </span> | |
| <span>Documentation</span> | |
| </a> | |
| </div> | |
| </nav> | |
| <!-- | |
| Account footer — docked at the FOOT of the sidebar (the .nav above | |
| is flex:1, so this pins to the bottom) the way Claude's account | |
| area sits at the bottom of its rail. Two mutually-exclusive states, | |
| toggled by auth.js (_renderSidebarAccount): | |
| • Anonymous → the "Sign in" row (#sidebarSignin), shown by default | |
| so it renders even before auth.js runs (and with JS disabled). | |
| • Signed in → the account row (#sidebarAccount) with the user's email. | |
| The sign-in link uses target="_top" so it leaves the iframe and loads | |
| /signin on turingdna.com rather than framing it inside the engine. | |
| The account menu opens upward; "Sign out" asks the wrapper to drop the | |
| Supabase session, then lands on /signin. | |
| --> | |
| <button class="theme-toggle sidebar-theme" id="themeToggle" type="button" aria-label="Toggle light and dark theme" aria-pressed="false" title="Toggle light / dark"> | |
| <svg class="theme-icon-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/> | |
| </svg> | |
| <svg class="theme-icon-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <circle cx="12" cy="12" r="4.5"/> | |
| <path d="M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M4.93 19.07l1.41-1.41M17.66 6.34l1.41-1.41"/> | |
| </svg> | |
| </button> | |
| <a class="sidebar-signin" id="sidebarSignin" href="https://turingdna.com/signin/?from=app" target="_top"> | |
| <span class="acct-avatar acct-avatar--ghost" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"/> | |
| <polyline points="10 17 15 12 10 7"/> | |
| <line x1="15" y1="12" x2="3" y2="12"/> | |
| </svg> | |
| </span> | |
| <span class="acct-meta"> | |
| <span class="acct-name">Sign in</span> | |
| <span class="acct-plan">Save your designs</span> | |
| </span> | |
| </a> | |
| <div class="sidebar-account" id="sidebarAccount" hidden> | |
| <button type="button" class="acct-trigger" id="acctTrigger" aria-haspopup="menu" aria-expanded="false"> | |
| <span class="acct-avatar" id="acctAvatar" aria-hidden="true"></span> | |
| <span class="acct-meta"> | |
| <span class="acct-name" id="acctName"> </span> | |
| <span class="acct-plan">Signed in</span> | |
| </span> | |
| <svg class="acct-chev" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <polyline points="18 15 12 9 6 15"/> | |
| </svg> | |
| </button> | |
| <div class="acct-menu" id="acctMenu" hidden role="menu" aria-label="Account"> | |
| <p class="acct-menu-email" id="acctMenuEmail"></p> | |
| <!-- | |
| Plan + usage. Nothing in the app said what plan you were | |
| on, what it included, or what you'd used — someone who | |
| had decided to pay could not find out how. Filled by | |
| auth.js from /api/usage; stays hidden if the numbers | |
| aren't available rather than showing a confident zero. | |
| --> | |
| <div class="acct-usage" id="acctUsage" hidden></div> | |
| <button type="button" class="acct-menu-item" id="acctSignOut" role="menuitem">Sign out</button> | |
| </div> | |
| </div> | |
| </aside> | |
| <!-- ============================== MAIN ============================== --> | |
| <main class="content"> | |
| <!-- | |
| Topbar trimmed to just the mobile hamburger. Was carrying | |
| a breadcrumb to nowhere (Design › New library, when there's | |
| nothing above "Design" to navigate back to) and a Linear- | |
| clone ⌘K search trigger that wasn't wired to anything | |
| useful. Both read as SaaS scaffolding and have been removed. | |
| The hamburger stays for the mobile drawer toggle. | |
| --> | |
| <header class="topbar"> | |
| <button class="nav-toggle" id="navToggle" type="button" aria-label="Open navigation" aria-controls="sidebar" aria-expanded="false"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <line x1="3" y1="6" x2="21" y2="6"/> | |
| <line x1="3" y1="12" x2="21" y2="12"/> | |
| <line x1="3" y1="18" x2="21" y2="18"/> | |
| </svg> | |
| </button> | |
| <div class="topbar-titles"> | |
| <h1 class="topbar-title" id="topbarTitle">Plasmid Editor</h1> | |
| <span class="topbar-sub" id="topbarSub">Map, annotate & clone your construct</span> | |
| </div> | |
| <!-- Theme toggle relocated to the sidebar footer, by the account chip | |
| (see #themeToggle in the aside) — it now stays reachable in the bench | |
| view, where this topbar is hidden. --> | |
| </header> | |
| <!-- ================================================================= | |
| Research-use-only strip — always visible immediately under the | |
| topbar so the framing is in front of the user during every | |
| action (paste, edit, synthesize, download). The "Why?" button | |
| re-opens the first-use disclaimer modal so the user can re-read | |
| the full text at any time. This strip pairs with the | |
| click-through modal in #firstUseModal below; together they're | |
| the in-UI half of the "research use only" legal narrative. | |
| ================================================================= --> | |
| <div class="research-strip" role="status" aria-label="Research use disclaimer"> | |
| <span class="research-strip-dot" aria-hidden="true">●</span> | |
| <!-- .ruo-long is hidden once the user has ACCEPTED the same | |
| disclaimer in the first-use modal (body[data-ruo=accepted]), | |
| so the strip shrinks to a quiet mark instead of repeating | |
| the full sentence on every page forever. --> | |
| <span class="research-strip-text"> | |
| <strong>Research use only.</strong> | |
| <span class="ruo-long">Computational predictions — visually verify every sequence before ordering DNA.</span> | |
| </span> | |
| <button class="research-strip-link" id="researchStripWhy" type="button">Why?</button> | |
| </div> | |
| <!-- Scrim covering the workspace while the sidebar drawer is open | |
| on mobile. Tap to dismiss. Hidden on tablet+ via CSS. --> | |
| <div class="sidebar-scrim" id="sidebarScrim" hidden></div> | |
| <!-- =================================================================== | |
| BENCH chrome (Phase 2 of the construct re-architecture, 2026-07-13). | |
| A construct-centric two-pane workspace. All elements below are | |
| inert until data-ui="bench" AND data-bench="open" (set by | |
| openConstruct in app.js); classic UI never shows them. The tool | |
| views themselves are NOT moved — CSS repositions Turing into a | |
| fixed left rail and the workspace into the right canvas, so every | |
| existing controller keeps working untouched. | |
| =================================================================== --> | |
| <div class="bench-strip" id="benchStrip" hidden> | |
| <button class="bench-back" id="benchBack" type="button" aria-label="Back to mission control">← Mission</button> | |
| <div class="bench-specimen"> | |
| <div class="bench-spec-thumb" aria-hidden="true"><svg viewBox="0 0 26 26" fill="none" stroke="currentColor" stroke-width="1.2"><path d="M6 4c8 4 6 14 14 18M6 22c8-4 6-14 14-18"/></svg></div> | |
| <div><div class="bench-spec-nm" id="benchSpecName">Construct</div><div class="bench-spec-sub" id="benchSpecSub"></div></div> | |
| </div> | |
| <div class="bench-spine" id="benchSpine" role="list" aria-label="Loop phase"> | |
| <span class="bench-ph" data-ph="0" role="listitem"><span class="bench-ph-n">01</span><span class="bench-ph-t">Design</span></span> | |
| <span class="bench-ph" data-ph="1" role="listitem"><span class="bench-ph-n">02</span><span class="bench-ph-t">Build</span></span> | |
| <span class="bench-ph" data-ph="2" role="listitem"><span class="bench-ph-n">03</span><span class="bench-ph-t">Edit</span></span> | |
| <span class="bench-ph" data-ph="3" role="listitem"><span class="bench-ph-n">04</span><span class="bench-ph-t">Learn</span></span> | |
| </div> | |
| </div> | |
| <div class="bench-canvashead" id="benchCanvasHead" hidden> | |
| <nav class="bench-tabs" id="benchTabs" aria-label="Construct artifacts"> | |
| <button class="bench-tab" data-route="structure" data-analytics="bench-tab-structure" type="button">Structure</button> | |
| <button class="bench-tab" data-route="design" data-analytics="bench-tab-design" type="button">Library</button> | |
| <button class="bench-tab" data-route="plasmid" data-analytics="bench-tab-plasmid" type="button">Map</button> | |
| <button class="bench-tab" data-route="crispr" data-analytics="bench-tab-crispr" type="button">Guides</button> | |
| <button class="bench-tab" data-route="primers" data-analytics="bench-tab-primers" type="button">Primers</button> | |
| </nav> | |
| </div> | |
| <div class="workspace"> | |
| <!-- =============================== MISSION CONTROL (bench-mode home) | |
| The construct portfolio + loop flywheel + a Turing entry. | |
| Only reachable in the opt-in `bench` UI (data-ui="bench"); | |
| populated lazily by initMission() in app.js from the same | |
| saved-work list endpoints "My designs" uses. --> | |
| <section class="view view--mission" data-view="mission" hidden aria-label="Mission control"> | |
| <div class="mc"> | |
| <p class="mc-hello micro" id="mcHello">Mission Control</p> | |
| <h1 class="mc-h">What are we engineering today?</h1> | |
| <!-- | |
| ONE CHAT. There used to be a second composer here, under | |
| the headline, four hundred pixels from the Turing rail's | |
| own — two text fields that did the same thing, with | |
| nothing on screen saying which one you were in. Typing | |
| here made words appear over there. | |
| The rail is the conversation now, everywhere, always. All | |
| this needs to be is the door to it: the headline, and one | |
| unmistakable way in. Search moved to ⌘K, which is a real | |
| overlay reachable from every route rather than an input | |
| that only existed on this page. | |
| --> | |
| <div class="mc-enter"> | |
| <button type="button" class="mc-enter-cta" id="mcStartChat"> | |
| Tell Turing what you're engineering | |
| <span class="mc-enter-ar" aria-hidden="true">→</span> | |
| </button> | |
| <button type="button" class="mc-enter-find" id="mcOpenFinder"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg> | |
| Find a construct, run, or tool | |
| <kbd>⌘K</kbd> | |
| </button> | |
| </div> | |
| <!-- constructs as intelligent status cards — the operator's | |
| own work LEADS the home (moved above telemetry+loop | |
| 2026-07-21 audit fix: a returning user comes back for | |
| THEIR constructs, not the engine's receipts, which now | |
| sit below). --> | |
| <section class="mc-sec"> | |
| <div class="mc-sec-h"><p class="micro" id="mcCxCount">Your constructs</p><a class="mc-sec-r" id="mcAllDesigns" role="button" tabindex="0">Open “My designs” →</a></div> | |
| <div class="mc-grid" id="mcPortfolio"><p class="muted">Loading…</p></div> | |
| </section> | |
| <!-- | |
| "Try another target" — the worked examples, kept reachable. | |
| They used to render only in the cold-start empty state, so | |
| the three most persuasive things on the page vanished the | |
| moment a user had any work of their own. Populated (and | |
| rotated) by TDMission.load(); left empty on a cold start, | |
| where the portfolio slot is already leading with them. | |
| --> | |
| <section class="mc-sec" id="mcTryOne"></section> | |
| <!-- the loop, as real per-user telemetry (Learn is the hero) --> | |
| <section class="mc-sec"> | |
| <div class="mc-sec-h"><p class="micro">Your loop — the flywheel turning</p><a class="mc-sec-r" href="#docs" role="button">How it works →</a></div> | |
| <div class="mc-loop" id="mcFlywheel"></div> | |
| </section> | |
| <!-- engine telemetry: the PUBLIC receipts + commons. The | |
| trust block sits BELOW the user's own work (was first; | |
| moved down 2026-07-21). Rendered by JS from /api/mission. --> | |
| <div class="mc-tele" id="mcTele" aria-label="Engine status"></div> | |
| </div> | |
| </section> | |
| <!-- =============================== STRUCTURE view | |
| The Bench's Structure tab. Populated automatically when | |
| Turing resolves a named protein (fold_structure → a PUBLIC | |
| AlphaFold-DB model; only the gene symbol ever leaves the | |
| Space, never a sequence). Mounts the same resilient Mol* | |
| viewer the identify/AlphaFold embeds use. De-novo folding of | |
| a user's own VARIANT stays an explicit, separately-consented | |
| action (it would POST their sequence to a third party). --> | |
| <section class="view view--structure2" data-view="structure" hidden aria-label="Predicted structure"> | |
| <div class="struct"> | |
| <header class="struct-head"> | |
| <div> | |
| <p class="card-kicker" id="structKicker">§ Structure</p> | |
| <h2 id="structTitle">Predicted structure</h2> | |
| <p class="card-sub" id="structSub">Turing loads the wild-type model as soon as you name a protein.</p> | |
| </div> | |
| <div class="struct-head-actions"> | |
| <a class="ghost" id="structEntryLink" target="_blank" rel="noopener" hidden>Open AlphaFold entry</a> | |
| <button class="ghost" id="structFullBtn" type="button" aria-pressed="false">Fullscreen</button> | |
| </div> | |
| </header> | |
| <div class="struct-viewer" id="structViewer"> | |
| <div class="struct-empty" id="structEmpty"> | |
| Name a protein in the conversation — “I want to engineer MC1R” — and its | |
| predicted structure appears here. | |
| </div> | |
| </div> | |
| <p class="struct-note" id="structNote" hidden></p> | |
| </div> | |
| </section> | |
| <!-- =============================== DESIGN view (default) --> | |
| <section class="view view--design" data-view="design"> | |
| <!-- | |
| Hero. Was carrying a generic eyebrow kicker | |
| ("PLM-GUIDED DIRECTED EVOLUTION") AND a 4-step | |
| pipeline-cards row (01 Parse & translate → 02 Zero-shot | |
| score → 03 Combinatorial search → 04 Codon optimize) | |
| on the right. Both were AI-generated SaaS bloat. The | |
| eyebrow said nothing the headline doesn't; the pipeline | |
| chips were decorative "look how complete I am" boxes. | |
| Replaced with a § Begin kicker matching the landing's | |
| editorial register and a single-column layout. | |
| --> | |
| <section class="hero" id="hero"> | |
| <p class="hero-kicker">§ Begin</p> | |
| <h1>Design a smart mutation library</h1> | |
| <p class="hero-lede"> | |
| Provide a wild-type sequence — get a library of multi-mutant variants | |
| ranked by predicted fitness, codon-optimized for your expression host, | |
| and ready to order from your synthesis vendor. From there: build the | |
| construct in the Plasmid Editor, edit it with CRISPR if the project | |
| calls for it, and log what you measure to design a sharper round two. | |
| </p> | |
| </section> | |
| <!-- | |
| Input section. Card headers throughout used to carry a | |
| boxy step-num badge (1, 2, 3 + done-checkmark) treating | |
| a research tool like Stripe Checkout. Replaced with the | |
| same § N kicker pattern as the landing chapters so the | |
| app reads as the next chapter of the same publication. | |
| --> | |
| <section class="card" id="inputCard"> | |
| <header class="card-header"> | |
| <div class="card-header-left"> | |
| <p class="card-kicker">§ 1 · Sequence</p> | |
| <h2>Provide a wild-type</h2> | |
| <p class="card-sub">FASTA, SnapGene, GenBank, raw DNA, or raw protein. Auto-detected.</p> | |
| </div> | |
| <button class="ghost" type="button" id="deExampleBtn" data-analytics="de-example" | |
| title="Loads GFP — a classic directed-evolution target">Try an example</button> | |
| </header> | |
| <div class="input-tabs" role="tablist"> | |
| <button class="tab active" data-tab="upload" role="tab"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/> | |
| </svg> | |
| Upload file | |
| </button> | |
| <button class="tab" data-tab="paste" role="tab"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/> | |
| </svg> | |
| Paste | |
| </button> | |
| </div> | |
| <div class="tab-panel active" data-panel="upload"> | |
| <label class="dropzone" id="dropzone"> | |
| <input type="file" id="fileInput" hidden | |
| accept=".fa,.fasta,.fna,.faa,.pep,.dna,.gb,.gbk,.genbank,.embl,.txt" /> | |
| <div class="dz-icon"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <path d="M4 14.899A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.5 8.242"/><path d="M12 12v9"/><path d="m16 16-4-4-4 4"/> | |
| </svg> | |
| </div> | |
| <div class="dz-copy"> | |
| <strong>Drop a sequence file</strong> | |
| <span>or click to browse</span> | |
| </div> | |
| <!-- The format chip row that used to live here | |
| ("FASTA · SnapGene · GenBank · EMBL · plain | |
| DNA / protein") was redundant — the same | |
| list is already in the card-sub directly | |
| above this dropzone. Removed. --> | |
| </label> | |
| </div> | |
| <div class="tab-panel" data-panel="paste"> | |
| <div class="paste-shell"> | |
| <div class="paste-gutter" id="pasteGutter"><span>1</span></div> | |
| <textarea id="pasteArea" rows="9" spellcheck="false" wrap="off" | |
| autocorrect="off" autocapitalize="off" autocomplete="off" inputmode="text" | |
| placeholder="Paste FASTA, raw DNA (ATGC…), or raw protein (MVSKG…). FASTA headers, whitespace, and stop asterisks all OK."></textarea> | |
| </div> | |
| <div class="paste-actions"> | |
| <span class="paste-stats" id="pasteStats">0 chars</span> | |
| <button id="pasteSubmit" class="primary-ghost">Use this sequence</button> | |
| </div> | |
| </div> | |
| <div class="preview" id="preview" hidden> | |
| <div class="preview-meta"> | |
| <div> | |
| <span class="meta-label">Identifier</span> | |
| <span class="meta-val" id="metaIdent"></span> | |
| </div> | |
| <div> | |
| <span class="meta-label">Detected</span> | |
| <span class="meta-val pill" id="metaKind"></span> | |
| </div> | |
| <div> | |
| <span class="meta-label">Length</span> | |
| <span class="meta-val" id="metaLen"></span> | |
| </div> | |
| </div> | |
| <div class="preview-seq" id="metaPreview"></div> | |
| <!-- NCBI BLAST identification (opt-in). Sequence leaves | |
| this machine and goes to NCBI's public BLAST | |
| servers. Cached by SHA256(protein) — same input | |
| returns instantly. --> | |
| <div class="identify-row" id="identifyRow"> | |
| <button class="ghost identify-btn" id="identifyBtn"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/> | |
| </svg> | |
| Identify via NCBI BLAST | |
| </button> | |
| <span class="identify-hint">Sends your sequence to NCBI · 30–90 s · skip if your sequence is proprietary</span> | |
| </div> | |
| <!-- BLAST result panel (lives inline below the | |
| Identify button). The first-use CONSENT panel | |
| is no longer rendered here — it pops as a | |
| centered modal (#blastConsentModal at the | |
| bottom of the page) so it stays in view | |
| regardless of scroll position. --> | |
| <div class="identify-panel" id="identifyPanel" hidden></div> | |
| </div> | |
| <div class="cds-picker" id="cdsPicker" hidden> | |
| <h3>Multiple CDS features detected — pick the gene to evolve</h3> | |
| <p class="muted">The longest CDS in a plasmid is usually the antibiotic resistance gene. Pick your gene of interest.</p> | |
| <div class="cds-list" id="cdsList"></div> | |
| </div> | |
| </section> | |
| <section class="card" id="settingsCard"> | |
| <header class="card-header"> | |
| <div class="card-header-left"> | |
| <p class="card-kicker">§ 2 · Search</p> | |
| <h2>Tune the parameters</h2> | |
| <p class="card-sub">Sensible defaults — change if you know what you're after.</p> | |
| </div> | |
| </header> | |
| <div class="setting-grid"> | |
| <label class="setting"> | |
| <span class="setting-name">Variants to generate</span> | |
| <span class="setting-hint">Top-K multi-mutants returned in the library.</span> | |
| <input type="number" id="settingK" value="30" min="5" max="500" step="5" /> | |
| <!-- The cap disclosed WHERE THE NUMBER IS TYPED. A | |
| reviewer set 30 here, asked Turing for a library | |
| and got 10 rows; the only notice was one clause | |
| in a banner in a different section of the page. | |
| The numbers are the real ones from | |
| dee/core/agent_tools.py (k clamped to 1–20, | |
| default 10) and tests/test_chat_library_cap.py | |
| fails if this text and that clamp drift apart. --> | |
| <p class="setting-note">This box drives the <strong>Directed Evolution</strong> run | |
| below, which honours it up to 500. A library designed <strong>in conversation | |
| with Turing</strong> does not read this box: that path runs inside a single | |
| reply rather than as a background job, so it returns <strong>10 variants by | |
| default and at most 20</strong>.</p> | |
| </label> | |
| <label class="setting"> | |
| <span class="setting-name">Max mutations / variant</span> | |
| <span class="setting-hint">Cap on simultaneous substitutions per variant.</span> | |
| <input type="number" id="settingMax" value="5" min="1" max="20" /> | |
| </label> | |
| <label class="setting"> | |
| <span class="setting-name">Top-percentile pool</span> | |
| <span class="setting-hint">Keep mutations above this ΔLL percentile.</span> | |
| <input type="number" id="settingPct" value="85" min="50" max="99" step="1" /> | |
| </label> | |
| <label class="setting"> | |
| <span class="setting-name">Expression host</span> | |
| <span class="setting-hint">Codon-usage table for reverse translation.</span> | |
| <select id="settingHost"> | |
| <option value="e_coli">Escherichia coli</option> | |
| <option value="yeast">Saccharomyces cerevisiae</option> | |
| <option value="human">Homo sapiens</option> | |
| </select> | |
| </label> | |
| <label class="setting"> | |
| <span class="setting-name">PLM model</span> | |
| <span class="setting-hint">Larger = sharper scoring, slower on CPU.</span> | |
| <select id="settingModel"> | |
| <option value="small">ESM-2 · 35M (default, fast)</option> | |
| <option value="medium" disabled>ESM-2 · 650M — Pro · GPU (coming soon)</option> | |
| <option value="large" disabled>ESM-2 · 3B — Pro · GPU (coming soon)</option> | |
| </select> | |
| </label> | |
| </div> | |
| </section> | |
| <section class="card run-card" id="runCard"> | |
| <header class="card-header"> | |
| <div class="card-header-left"> | |
| <p class="card-kicker">§ 3 · Generate</p> | |
| <h2>Run the engine</h2> | |
| <p class="card-sub">Scoring runs on our GPU. Your individual sequences are never shared, exposed, or reproduced; only anonymous, aggregated signals improve our models. <a href="https://turingdna.com/privacy/" target="_blank" rel="noopener">Privacy</a>.</p> | |
| </div> | |
| </header> | |
| <button id="runBtn" type="button" class="primary primary-lg" data-analytics="de-run" disabled> | |
| <span class="primary-icon" aria-hidden="true"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> | |
| <polygon points="6 3 20 12 6 21 6 3" fill="currentColor"/> | |
| </svg> | |
| </span> | |
| <span class="primary-content"> | |
| <span class="primary-label">Generate smart library</span> | |
| <span class="primary-sub">Load a sequence to enable</span> | |
| </span> | |
| </button> | |
| <div class="progress-shell" id="progressShell" hidden> | |
| <div class="progress-bar"> | |
| <div class="progress-fill" id="progressFill"></div> | |
| </div> | |
| <div class="progress-meta"> | |
| <span id="progressStatus">Starting…</span> | |
| <span id="progressElapsed">0.0s</span> | |
| </div> | |
| <div class="progress-message" id="progressMessage"></div> | |
| </div> | |
| </section> | |
| <!-- | |
| Empty state. Was carrying an unDraw-style placeholder | |
| SVG (rectangles + check-circle) that read as the AI-y | |
| "empty state illustration" cliché found on every SaaS | |
| landing screen. Replaced with a quiet editorial block: | |
| § kicker + italic display title + lede. No illustration. | |
| --> | |
| <section class="empty-state" id="emptyState"> | |
| <p class="empty-kicker">§ Awaiting input</p> | |
| <h3 class="empty-title">The library appears here.</h3> | |
| <p class="empty-sub">Provide a sequence above and click Generate. Results render as a sortable variant table with mutation map, PCR primers, and one-click synthesis ordering.</p> | |
| </section> | |
| <!-- | |
| Results section. Was carrying a step-num done-checkmark | |
| badge (treating results as "step 4 completed") and | |
| download-menu items each had a tautological subtitle | |
| (CSV — "Spreadsheet · universal"). Both removed. Format | |
| names alone carry the meaning; users who don't know | |
| what CSV is aren't the audience. | |
| --> | |
| <section class="card results-card" id="resultsCard" hidden> | |
| <header class="card-header results-header"> | |
| <div class="card-header-left"> | |
| <p class="card-kicker">§ 4 · Library</p> | |
| <h2>Variants</h2> | |
| <p class="card-sub" id="resultSummary"></p> | |
| </div> | |
| <div class="results-actions"> | |
| <div class="download-menu" id="downloadMenu"> | |
| <button class="ghost download-trigger" type="button" id="downloadTrigger"> | |
| Export | |
| <span class="caret">▾</span> | |
| </button> | |
| <div class="download-menu-items" id="downloadMenuItems" hidden> | |
| <a class="download-item" data-format="csv" data-analytics="export-csv">CSV</a> | |
| <a class="download-item" data-format="xlsx" data-analytics="export-xlsx">Excel</a> | |
| <a class="download-item" data-format="fasta" data-analytics="export-fasta">FASTA · protein</a> | |
| <a class="download-item" data-format="fasta-dna" data-analytics="export-fasta-dna">FASTA · DNA</a> | |
| <a class="download-item" data-format="gb" data-analytics="export-gb">GenBank</a> | |
| <a class="download-item" data-format="json" data-analytics="export-json">JSON</a> | |
| <a class="download-item" data-format="tsv" data-analytics="export-tsv">TSV</a> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <details class="how-to-read"> | |
| <summary>How to read this</summary> | |
| <div class="how-to-read-body"> | |
| <p><strong>Fitness</strong> — the summed ESM-2 log-likelihood gain over wild-type (ΣΔLL) across the variant’s mutations. Higher means the model finds that combination more evolutionarily plausible. It’s a prior, not a verdict — screen experimentally.</p> | |
| <p><strong>Mutations</strong> — substitutions vs. wild-type (WT·position·new, e.g. <code>W58L</code>). <strong>GC %</strong> — GC of the codon-optimized DNA (40–60% is PCR-friendly). <strong>Tm</strong> — primer melting temp. <strong>bp</strong> — amplicon length.</p> | |
| <p><strong>Filter box:</strong> <code>C49</code> = variants mutating residue 49 · <code>W58L</code> = that exact substitution · <code>gc>50</code>, <code>tm>58</code>, <code>fitness>2</code>, <code>bp<800</code> = numeric ranges.</p> | |
| <p class="how-to-read-note">Stay shallow — cap at 3–4 mutations per variant unless you have a structural reason.</p> | |
| </div> | |
| </details> | |
| <!-- | |
| Stats-strip (5 KPI tiles: Top fitness / Position | |
| coverage / Mean GC / Cooler primer Tm / Amplicon) | |
| removed. It was THE single most "SaaS dashboard" | |
| element in the entire app — variant-level stats | |
| belong in the table; aggregate run quality belongs | |
| in the methods paragraph below. | |
| --> | |
| <!-- Run metadata + auto-generated methods text. Filled by JS. --> | |
| <div class="run-meta" id="runMeta" hidden></div> | |
| <!-- Round-2 banner: set when a library was proposed from logged results. --> | |
| <div class="round2-banner" id="round2Banner" hidden></div> | |
| <!-- "What Turing learned" — the round-2 re-rank + plain-language | |
| reasons (dee.core.active_learning components/narrate, surfaced | |
| via surrogate.pool_deltas). Only populated + shown when the | |
| surrogate actually learned something (enough measurements + | |
| real spread) — see _renderLearnedPanel in app.js. --> | |
| <section class="learned-panel" id="learnedPanel" hidden> | |
| <p class="card-kicker">§ What Turing learned</p> | |
| <!-- These are SINGLE-substitution scores (dee/server.py | |
| _de_round2_library reads m.delta_ll per mutation), | |
| so the unit is ΔLL — not the table's summed ΣΔLL. | |
| Labelling them the same would imply the two columns | |
| are on one scale, and they are not. --> | |
| <p class="unit-legend unit-legend--tight">Per-substitution score, <strong>ΔLL</strong> — ESM-2’s prior for that one mutation, before → after your measured results were folded in. Not the same scale as the table’s summed ΣΔLL.</p> | |
| <div class="learned-list" id="learnedList"></div> | |
| </section> | |
| <!-- Predicted-vs-measured calibration — client-side only, built | |
| from the round-1 Predicted_Fitness_Score already in the table | |
| matched against what the user just typed into the round-2 | |
| panel. See _renderCalibration in app.js. --> | |
| <section class="calib-panel" id="calibPanel" hidden> | |
| <p class="card-kicker">§ How well round 1 predicted your results</p> | |
| <div class="calib-body" id="calibBody"></div> | |
| </section> | |
| <!-- Mutation map (lollipop chart). | |
| The chip legend that used to sit here (Low / Med / | |
| High dots) was deleted as dashboard scaffolding and | |
| its explanation moved into the description below — | |
| which then described a three-step colour ramp that | |
| encoded a signed, continuous quantity. #mutmapKey is | |
| the replacement, and it is built by renderMutationKey() | |
| from the same colour function the dots use, so the | |
| key and the chart cannot disagree. --> | |
| <div class="mutmap-card" id="mutmapCard"> | |
| <div class="mutmap-head"> | |
| <h3>Mutation landscape</h3> | |
| <!-- Colour is the MEAN attributed ΔLL, signed — | |
| not the sum. The sum grew with the variant | |
| count that bar height already encodes, so the | |
| two channels said the same thing twice and | |
| neither said the direction. The full key, | |
| with units and domain, is rendered into | |
| #mutmapKey below. --> | |
| <p class="muted">Where mutations land across the protein. <strong>x</strong> = residue position (1–<span id="mutmapLen">N</span>) · <strong>bar height</strong> = variants sharing that position · <strong>colour</strong> = the mean ΔLL there, signed, in ESM-2 log-likelihood units. Full scale and domain in the key below.</p> | |
| </div> | |
| <div class="mutmap-canvas" id="mutmapCanvas"></div> | |
| <div class="mutmap-key" id="mutmapKey"></div> | |
| </div> | |
| <!-- ================= INTERACTION RADAR (Pillar 1) ================= | |
| Zero-shot epistasis: the ranked library scores each mutation | |
| ALONE (additive). This re-scores each multi-mutant in context | |
| with ESM-2 and flags where substitutions antagonize — catching | |
| a dud combination before it's synthesized. Opt-in (one forward | |
| pass per variant), signed-in only. Wired by initRadar()/ | |
| _renderRadar() in app.js against /api/de/epistasis. --> | |
| <section class="radar-card" id="radarCard" hidden> | |
| <div class="radar-head"> | |
| <div class="radar-head-txt"> | |
| <p class="card-kicker">§ Interaction Radar</p> | |
| <h3>Will these mutations play well together?</h3> | |
| <p class="muted">Your library ranks each substitution on its own. This checks the <em>combinations</em> — ESM-2 re-scores every multi-mutant in context and flags where mutations antagonize, before you spend a cent synthesizing a dud.</p> | |
| </div> | |
| <button class="ghost" type="button" id="radarBtn">Analyze interactions →</button> | |
| </div> | |
| <div class="radar-status" id="radarStatus" hidden></div> | |
| <div class="radar-list" id="radarList"></div> | |
| <p class="radar-foot muted" id="radarFoot" hidden></p> | |
| </section> | |
| <!-- The Fitness column's unit, stated where the column is, | |
| not only in a hover title and not only inside the | |
| collapsed "How to read this" block above. A reviewing | |
| scientist asked "what does a fitness score of 1.0 | |
| represent? 2/3 is over and 1/3 is under, but none are | |
| negative" — all three parts of that are answered here: | |
| the unit, the absence of a fixed zero, and why a | |
| top-ranked slice is not expected to contain negatives. | |
| Styling lives in trace.css (app.css is off-limits to | |
| this change set). --> | |
| <p class="unit-legend" id="fitnessLegend"> | |
| <strong>Fitness = ΣΔLL</strong> — the ESM-2 log-likelihood | |
| change vs. wild type, <em>summed</em> over this variant’s substitutions. | |
| Unnormalised: no fixed zero and no maximum, and because the table shows the | |
| top-ranked slice of the search, negatives rarely survive into it. | |
| Ranks variants <em>within this library</em> — not across proteins or runs. | |
| </p> | |
| <!-- Filter bar — instant client-side filter over the rendered table. --> | |
| <div class="table-toolbar"> | |
| <div class="filter-input-wrap"> | |
| <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> | |
| <circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/> | |
| </svg> | |
| <input type="text" id="variantFilter" placeholder="Filter by mutation (e.g. C49), variant ID, or GC range" /> | |
| <button type="button" class="filter-clear" id="filterClear" hidden aria-label="Clear filter">×</button> | |
| </div> | |
| <span class="table-count" id="tableCount"></span> | |
| </div> | |
| <div class="result-table-wrap"> | |
| <table class="result-table" id="resultTable"> | |
| <thead> | |
| <tr> | |
| <th class="sortable" data-sort="rank" title="Rank by predicted fitness (1 = best).">Rank <span class="sort-ind">↕</span></th> | |
| <th class="sortable" data-sort="mutations" title="Substitutions vs. the wild-type protein, in the format WT_aa-position-new_aa.">Mutations <span class="sort-ind">↕</span></th> | |
| <!-- The unit rides in the header itself. It was title-only | |
| since May 2026, which means it did not exist for anyone | |
| who didn't hover the right 60px of the page. --> | |
| <th class="num sortable" data-sort="fitness" title="Cumulative ΣΔLL from ESM-2: sum of log-likelihood improvements over WT across all substitutions in this variant.">Fitness <span class="th-unit">ΣΔLL</span> <span class="sort-ind">↕</span></th> | |
| <th class="num sortable" data-sort="gc" title="GC content of the codon-optimized DNA. PCR-friendly range: 40–60%.">GC % <span class="sort-ind">↕</span></th> | |
| <th class="num sortable" data-sort="tm" title="Cooler of the two PCR primer Tm values (nearest-neighbor, standard salt). Annealing = Tm − 5°C for Q5/Phusion.">Tm (°C) <span class="sort-ind">↕</span></th> | |
| <th class="num sortable" data-sort="bp" title="Amplicon length in base pairs. Same for every variant in this library (length-preserving substitutions only).">bp <span class="sort-ind">↕</span></th> | |
| <th class="expand-col"></th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| </div> | |
| <!-- Learning flywheel: log wet-lab results → propose a | |
| data-conditioned round 2 (Design→Build→Test→Learn). --> | |
| <section class="round2-panel" id="round2Panel" hidden> | |
| <header class="card-header"> | |
| <div class="card-header-left"> | |
| <p class="card-kicker">§ Learn from results</p> | |
| <h3>Tested these on the bench? Teach the engine.</h3> | |
| <p class="card-sub">Enter a measured value for the variants you assayed (any | |
| number — activity, expression, stability; higher = better). Leave the | |
| rest blank. TuringDNA fits a model on your results and proposes a smarter | |
| round 2 — conditioned on your data, not just the prior.</p> | |
| </div> | |
| </header> | |
| <div class="round2-list" id="round2List"></div> | |
| <div class="round2-status" id="round2Status" hidden></div> | |
| <div class="round2-actions"> | |
| <button class="ghost" type="button" id="round2SaveBtn">Save results</button> | |
| <button class="primary" type="button" id="round2Btn">Propose round 2 →</button> | |
| </div> | |
| </section> | |
| </section> | |
| </section> | |
| <!-- | |
| Library view. Was carrying the same step-num-done + | |
| decorative table SVG icon pattern as the Design cards, | |
| plus a redundant SVG icon on the Refresh button, plus | |
| another placeholder illustration in the empty state. | |
| All removed; pattern matches the Design view's § kicker | |
| treatment. Refresh is a text-only ghost button now. | |
| --> | |
| <!-- Library + History views removed 2026-05-27. Replaced | |
| by the per-user /dashboard/ page on the landing site | |
| (task #98). See sidebar comment above for context. --> | |
| <!-- ═════════════════════════════ CRISPR view (Cas9 knockout) ═══ | |
| Paste a gene → ranked SpCas9 sgRNAs with on-target | |
| scoring. Sign-in gated server-side; anonymous users | |
| see the same layout with the submit button disabled | |
| and a "Sign in to design guides" overlay. | |
| ════════════════════════════════════════════════════════ --> | |
| <section class="view view--crispr" data-view="crispr" hidden> | |
| <section class="hero" id="crisprHero"> | |
| <p class="hero-kicker">§ Begin · CRISPR</p> | |
| <h1>Design a CRISPR edit.</h1> | |
| <p class="hero-lede"> | |
| Paste a gene or region. Get ranked sgRNAs for a | |
| SpCas9 / Cas12a knockout — or switch to | |
| <strong>base editing</strong> to install a precise | |
| C→T / A→G change (including premature-stop | |
| knockouts) with predicted amino-acid outcomes. Free for | |
| any account. If a project calls for an edit beyond what a | |
| variant library covers — a knockout, a precise base | |
| change — design it here, on the same construct. | |
| </p> | |
| <p class="hero-meta crispr-disclaimer"> | |
| On-target scoring is heuristic (Doench-style sequence | |
| features). <strong>Off-target is screened here</strong> — | |
| choose an organism and your top guides are searched | |
| against the genome and CFD-scored. Coverage is the | |
| complete genome for E. coli, and coding sequence | |
| for human and mouse; intronic and intergenic sites | |
| fall outside that index. | |
| </p> | |
| </section> | |
| <section class="card" id="crisprInputCard"> | |
| <header class="card-header"> | |
| <div class="card-header-left"> | |
| <p class="card-kicker">§ 1 · Target</p> | |
| <h2>Paste your DNA</h2> | |
| <p class="card-sub"> | |
| The gene or region you want to edit. ACGT only; | |
| FASTA headers OK; up to 1 Mbp. | |
| </p> | |
| </div> | |
| <button class="ghost" type="button" id="crisprExampleBtn" | |
| title="Loads the GFP coding sequence as a knockout target">Try an example</button> | |
| </header> | |
| <!-- Phase 3 (M2): paste-anything resolver. Type a gene | |
| symbol (TP53), an accession (ENST… / NM_…), or just | |
| paste DNA below. Resolving a symbol/accession fills | |
| the box and sets the gene context that base-edit | |
| amino-acid consequences and the structure view use. | |
| Only the (organism, identifier) is sent for lookups — | |
| a pasted sequence never leaves the Space. --> | |
| <div class="crispr-resolve-row"> | |
| <input | |
| id="crisprResolveInput" | |
| class="crispr-context-input crispr-resolve-input" | |
| type="text" | |
| maxlength="64" | |
| autocomplete="off" | |
| autocapitalize="characters" | |
| spellcheck="false" | |
| placeholder="Gene symbol (TP53), accession (ENST… / NM_…), or paste DNA below" /> | |
| <button id="crisprResolveBtn" type="button" class="ghost">Fetch</button> | |
| </div> | |
| <div class="crispr-resolve-chip" id="crisprResolveChip" hidden></div> | |
| <!-- Same line-numbered gutter pattern as the | |
| directed-evolution paste textarea (see #pasteGutter). | |
| Gutter spans render via app.js renderCrisprGutter() | |
| on every input event; scroll syncs both ways. --> | |
| <div class="paste-shell"> | |
| <div class="paste-gutter" id="crisprPasteGutter"><span>1</span></div> | |
| <textarea | |
| id="crisprInput" | |
| class="crispr-input" | |
| rows="9" | |
| spellcheck="false" | |
| wrap="off" | |
| autocorrect="off" | |
| autocapitalize="off" | |
| autocomplete="off" | |
| placeholder="Paste DNA (ATGC…). FASTA headers and whitespace are OK."></textarea> | |
| </div> | |
| <!-- Enzyme picker — Phase 1. SpCas9 (NGG PAM, 20 nt | |
| spacer) is the default + most common. Cas12a | |
| (TTTV PAM, 23 nt spacer) added for AT-rich | |
| regions where Cas9 PAMs are sparse. Hidden | |
| controls — single line, mono labels, the same | |
| visual register as the cloning-designer | |
| enzyme picker on the directed-evolution side. --> | |
| <div class="crispr-enzyme-row"> | |
| <span class="crispr-enzyme-label">Nuclease</span> | |
| <label class="crispr-enzyme-opt"> | |
| <input type="radio" name="crisprEnzyme" value="cas9" checked /> | |
| <span>SpCas9 <em>· NGG PAM</em></span> | |
| </label> | |
| <label class="crispr-enzyme-opt"> | |
| <input type="radio" name="crisprEnzyme" value="cas12a" /> | |
| <span>Cas12a <em>· TTTV PAM</em></span> | |
| </label> | |
| </div> | |
| <!-- Phase 3 (M1): editing mode. Knockout = a Cas9 | |
| double-strand break repaired into frameshift | |
| indels (the default). Base edit = a precise | |
| single-base change (C→T via a cytosine base | |
| editor, A→G via an adenine base editor) with NO | |
| double-strand break — including premature-stop | |
| "CRISPR-STOP" knockouts. Base editing is | |
| SpCas9-only, so choosing it locks the nuclease. --> | |
| <div class="crispr-enzyme-row crispr-mode-row"> | |
| <span class="crispr-enzyme-label">Mode</span> | |
| <label class="crispr-enzyme-opt"> | |
| <input type="radio" name="crisprMode" value="knockout" checked /> | |
| <span>Knockout <em>· DSB + indels</em></span> | |
| </label> | |
| <label class="crispr-enzyme-opt"> | |
| <input type="radio" name="crisprMode" value="base_edit" /> | |
| <span>Base edit <em>· precise C→T / A→G</em></span> | |
| </label> | |
| </div> | |
| <!-- Base-editor picker — shown only in base-edit | |
| mode. Options come from /api/crispr/base-editors | |
| on first switch. The amino-acid consequence | |
| column fills in when a gene symbol + organism | |
| is given or the pasted sequence is a coding | |
| sequence (starts with ATG, ends in a stop). --> | |
| <div class="crispr-context-row crispr-be-row" id="crisprBaseEditorRow" hidden> | |
| <div class="crispr-context-field"> | |
| <label class="crispr-context-label" for="crisprBaseEditor"> | |
| Base editor <em>· cytosine or adenine</em> | |
| </label> | |
| <select id="crisprBaseEditor" class="crispr-context-input"></select> | |
| </div> | |
| <p class="crispr-be-note" id="crisprBaseEditorMeta"></p> | |
| </div> | |
| <!-- Organism + gene context. Both OPTIONAL — the tool | |
| works on a pasted sequence with neither set. The | |
| organism does double duty: it's the genome the | |
| off-target search runs against AND the species used | |
| to resolve a gene symbol above, so the label names | |
| both jobs (a reviewer picked a symbol, saw only | |
| "off-target genome", and thought they needed an | |
| accession). Six genomes are live; see the dropdown. --> | |
| <div class="crispr-context-row"> | |
| <div class="crispr-context-field"> | |
| <label class="crispr-context-label" for="crisprOrganism"> | |
| Organism <em>· for gene lookup & off-target · optional</em> | |
| </label> | |
| <select id="crisprOrganism" class="crispr-context-input"> | |
| <option value="">None — skip genome search</option> | |
| <optgroup label="Complete genome — finds off-targets anywhere"> | |
| <option value="ecoli">E. coli K-12 MG1655 · builds in seconds</option> | |
| <option value="yeast">S. cerevisiae (R64-1-1) · builds in ~30 s</option> | |
| <option value="worm">C. elegans (WBcel235) · builds once, ~3 min</option> | |
| <option value="fly">D. melanogaster (BDGP6.46) · builds once, ~5 min</option> | |
| </optgroup> | |
| <optgroup label="Coding regions only — introns/intergenic not covered"> | |
| <option value="human">Homo sapiens (GRCh38) · builds once, ~3 min</option> | |
| <option value="mouse">Mus musculus (GRCm39) · builds once, ~2 min</option> | |
| </optgroup> | |
| </select> | |
| </div> | |
| <div class="crispr-context-field"> | |
| <label class="crispr-context-label" for="crisprGeneSymbol"> | |
| Gene symbol <em>· optional, for exon context</em> | |
| </label> | |
| <input | |
| id="crisprGeneSymbol" | |
| class="crispr-context-input" | |
| type="text" | |
| maxlength="32" | |
| placeholder="e.g. TP53, GAL1, cas9 — any organism" | |
| autocapitalize="characters" | |
| autocomplete="off" | |
| spellcheck="false" /> | |
| </div> | |
| </div> | |
| <div class="paste-actions"> | |
| <span class="paste-stats" id="crisprInputStats">0 nt</span> | |
| <button | |
| id="crisprDesignBtn" | |
| type="button" | |
| class="primary primary-lg" | |
| disabled> | |
| <span class="primary-content"> | |
| <span class="primary-label">Design guides</span> | |
| <span class="primary-sub">Paste a sequence to enable</span> | |
| </span> | |
| </button> | |
| </div> | |
| <!-- Phase 2C-2: progress shell matching the directed- | |
| evolution pattern. Indeterminate (always-shimmering) | |
| because the CRISPR API is synchronous — we can't | |
| emit real percentage, but the moving stripe + status | |
| text reassures the user that the request is alive, | |
| especially during the ~60-90 s first build of the | |
| human off-target index. --> | |
| <div class="progress-shell crispr-progress" id="crisprProgressShell" hidden> | |
| <div class="progress-bar"> | |
| <div class="progress-fill progress-fill-indeterminate"></div> | |
| </div> | |
| <div class="progress-meta"> | |
| <span id="crisprProgressStatus">Designing…</span> | |
| <span id="crisprProgressElapsed">0.0s</span> | |
| </div> | |
| <div class="progress-message" id="crisprProgressMessage"></div> | |
| </div> | |
| <!-- The old inline #crisprSigninNotice was removed | |
| 2026-05-28 — replaced by the #crisprSigninModal | |
| which pops centered (better than a card that | |
| lives below the fold) and fires from BOTH | |
| the sidebar tab click AND the backend 403. --> | |
| </section> | |
| <!-- Results card — hidden until /api/crispr/design returns. --> | |
| <section class="card" id="crisprResultsCard" hidden> | |
| <header class="card-header results-header"> | |
| <div class="card-header-left"> | |
| <p class="card-kicker">§ 2 · Guides</p> | |
| <h2>Ranked sgRNAs</h2> | |
| <p class="card-sub" id="crisprResultsSub">—</p> | |
| </div> | |
| <div class="results-actions"> | |
| <!-- Phase 3 (M4): save the current guide set to the | |
| signed-in user's library (45-day retention) and | |
| reopen past designs. --> | |
| <button id="crisprSaveBtn" type="button" class="ghost">Save design</button> | |
| <button id="crisprDesignsBtn" type="button" class="ghost">My designs</button> | |
| <!-- Live Collision IP Radar (opt-in, per run). Scans recent | |
| literature for the top guide's exact sequence. Only the | |
| gene + 20-mer leave the Space — never the full design. --> | |
| <button id="crisprRadarBtn" type="button" class="ghost" | |
| title="Scan recent literature & preprints for overlap with your top guide (sends the gene + 20-mer only)"> | |
| <span aria-hidden="true">⌖</span> IP radar | |
| </button> | |
| <div class="download-menu" id="crisprDownloadMenu"> | |
| <button id="crisprDownloadBtn" type="button" class="ghost download-trigger" | |
| aria-haspopup="true" aria-expanded="false"> | |
| Export | |
| <span class="caret">▾</span> | |
| </button> | |
| <div class="download-menu-items" id="crisprDownloadItems" hidden> | |
| <a class="download-item" data-format="csv">CSV</a> | |
| <a class="download-item" data-format="xlsx">Excel</a> | |
| </div> | |
| </div> | |
| </div> | |
| </header> | |
| <!-- Provenance, on demand. Collapsed by default so it adds | |
| no visual clutter, but every scored column can be | |
| audited: exact formula, published basis, and the | |
| point where the number stops being trustworthy. | |
| Filled by TDMethods from GET /api/crispr/methods, | |
| which mirrors dee/core/crispr.py. --> | |
| <details class="how-to-read" id="crisprMethods"> | |
| <summary>How these numbers were computed</summary> | |
| <div class="how-to-read-body" id="crisprMethodsBody"> | |
| <p class="muted">Loading methods…</p> | |
| </div> | |
| </details> | |
| <!-- Auto-generated run parameters + copy-paste methods paragraph. --> | |
| <div class="run-meta" id="crisprRunMeta" hidden></div> | |
| <!-- Live Collision IP Radar banner mounts here (above the | |
| results), populated by the radar button handler. --> | |
| <div id="crisprRadarBanner" class="ip-radar-wrap" hidden></div> | |
| <!-- Phase 2C-1: bulk-order strip. Click a vendor → copy all | |
| oligo pairs as TSV to clipboard + open vendor site so the | |
| user can paste into the order form. Vendor list comes from | |
| /api/crispr/vectors. Moved OUT of the header (2026-05-30) | |
| so it no longer squeezed the title into a one-word-per-line | |
| column — now a full-width strip above the cloning picker. --> | |
| <div class="crispr-vendor-row crispr-vendor-strip" id="crisprVendorRow" hidden></div> | |
| <!-- Phase 2C-1: vector picker. Above the table but inside | |
| the results card so the picker contextualizes the | |
| oligo columns. Picking a vector re-renders the | |
| Sense / Antisense columns with that vector's | |
| overhangs. --> | |
| <div class="crispr-vector-row" id="crisprVectorRow" hidden> | |
| <label class="crispr-vector-label" for="crisprVector"> | |
| Cloning vector | |
| </label> | |
| <select id="crisprVector" class="crispr-vector-select"></select> | |
| <span class="crispr-vector-meta" id="crisprVectorMeta"></span> | |
| </div> | |
| <div class="result-table-wrap"> | |
| <table class="result-table crispr-table"> | |
| <thead> | |
| <tr> | |
| <th class="rank-h">Rank</th> | |
| <th>Strand</th> | |
| <th class="num crispr-sortable" data-sort-key="position">Position</th> | |
| <th>Spacer</th> | |
| <th>PAM</th> | |
| <!-- Composite is the ranking column — | |
| on-target × (1 − 0.6 × self-off CFD). | |
| The 0.6 is the engine's only tuning | |
| constant and a judgement call, so it | |
| is spelled out here, in the scope | |
| strip, per guide in the why-panel, and | |
| in the methods paragraph — a weight | |
| that decides the sort order has to be | |
| readable without asking us. | |
| Always the primary sort. --> | |
| <th class="num crispr-sortable" data-sort-key="composite_score" title="on-target × (1 − 0.6 × self-off CFD). The 0.6 off-target weight is our uncalibrated judgement call, not a published constant — expand a row to see the arithmetic for that guide. Click to sort.">Composite</th> | |
| <th class="num crispr-sortable" data-sort-key="on_target_score" title="TuringDNA on-target activity score (Doench-inspired sequence-only features). 1.0 = expected strong cut. Click to sort.">On-target</th> | |
| <!-- CFD-against-input is the Phase 1 | |
| differentiator. 0.00 = no other | |
| plausible cut site in the input | |
| (clean unique target). 1.00 = an | |
| identical 20-mer + NGG appears | |
| elsewhere — don't use this guide | |
| unless you want both cut. --> | |
| <th class="num crispr-sortable" data-sort-key="cfd_max_offtarget" title="Worst CFD score against any other NGG site in the pasted input. 0.00 = unique; 1.00 = exact repeat. Click to sort.">Self-off</th> | |
| <th class="num col-ko crispr-sortable" data-sort-key="ko_efficacy" title="Predicted probability the indel produces a true loss-of-function knockout. Click to sort.">KO score</th> | |
| <!-- Phase 2A: inDelphi-inspired prediction of the | |
| single most likely repair outcome at this cut, | |
| e.g. "-3 ATG (28%)" or "+1 T (32%)". The | |
| dominant outcome usually accounts for 15-40% | |
| of all repair events. --> | |
| <th class="col-ko" title="Single most-likely repair outcome (inDelphi-inspired). The number is the predicted frequency of THIS outcome among all repair events. Assumes template-free end-joining repair and is NOT conditioned on your cell type.">Top indel</th> | |
| <!-- Phase 2A: % of predicted repair outcomes that | |
| introduce a frameshift. Higher = more likely | |
| to produce a true loss-of-function allele. --> | |
| <th class="num col-ko crispr-sortable" data-sort-key="frameshift_pct" title="% of predicted repair outcomes that introduce a frameshift (non-multiple-of-3 indel). Higher = stronger KO likelihood. Assumes template-free end-joining (MMEJ + NHEJ) with fixed constants and NO cell-type conditioning — rank guides with it, don't quote it as a rate for your line. Click to sort.">FS %</th> | |
| <!-- Phase 2A: dominance of the SINGLE most-likely | |
| outcome, as a percent. Direct biological | |
| readout — "will I get one main edit product | |
| or many?" 40%+ = clean, 25-40% = typical, | |
| <25% = messy heterogeneous repair. --> | |
| <th class="num col-ko crispr-sortable" data-sort-key="top_dominance_pct" title="Frequency of the single most-likely repair outcome. 40%+ = clean; <25% = messy. Same end-joining assumptions as FS % — no cell-type conditioning. Click to sort.">Dominance</th> | |
| <!-- Phase 2A: does the spacer's editing window | |
| (positions 4-8) hold a C or A? If yes, this | |
| guide could ALSO be used for a point edit | |
| via CBE or ABE base editors. --> | |
| <th class="col-ko" title="If the spacer's positions 4-8 hold a C or A, the same guide could be used for a point edit via cytosine (CBE) or adenine (ABE) base editor — not just a knockout.">Base editor</th> | |
| <!-- Phase 3 (M1) base-edit columns — shown only in | |
| Base edit mode. --> | |
| <th class="col-be" title="The single-base change(s) this editor installs inside its activity window, e.g. C5→T. Multiple targets = bystander edits.">Edit</th> | |
| <th class="num col-be crispr-sortable" data-sort-key="be_editability" title="Strongest in-window editing activity for a target base (0–1). Higher = more likely. Click to sort.">Editability</th> | |
| <th class="col-be" title="Amino-acid consequence of the edit when a reading frame is known (gene symbol given, or pasted sequence is a coding sequence). '* (stop gained)' = a DSB-free knockout via CRISPR-STOP.">AA change</th> | |
| <th class="col-be" title="A clean edit changes a single base in the window; a bystander edit hits more than one. 'STOP' marks guides that install a premature stop codon (knockout by base editing).">Outcome</th> | |
| <!-- Phase 2B-1: scored against the chosen organism's | |
| indexed genome. Empty when no organism is | |
| picked; "unique" when the organism was | |
| searched and no real off-targets found. | |
| COVERAGE IS NOT UNIFORM: E. coli, yeast, worm | |
| and fly are indexed complete; human and mouse | |
| are CODING SEQUENCE ONLY. The scope strip above | |
| the table states which applied to this run, | |
| sourced from offtarget.GENOME_SOURCES. --> | |
| <th class="num" title="Worst CFD score against any site in the chosen organism's INDEXED genome. 'unique' = no off-target found above threshold; 0.5+ = strong off-target that may cleave (tooltip gives the location). Coverage varies by organism — human and mouse are coding sequence only, and only the top-ranked guides are screened. See the scope note above the table.">Genome off</th> | |
| <!-- Phase 2B-1: only populated when the user | |
| gave a gene symbol AND the input could be | |
| aligned to the gene's CDS. Shows exon | |
| number + splice/NMD warnings. --> | |
| <th title="Exon-number, splice-site proximity, and NMD-zone prediction (only when a gene symbol is provided). 'NMD likely' = the predicted PTC is >50 nt before the last exon-exon junction, triggering nonsense-mediated decay and guaranteeing a null protein.">Exon context</th> | |
| <!-- Phase 2C-1: annealable oligo pair for | |
| cloning into the chosen vector. Two | |
| columns so Sense and Antisense are | |
| side-by-side; each cell has a Copy | |
| button (single cell click → copy that | |
| strand only). --> | |
| <th title="Sense strand of the annealable oligo pair, ready to order from Twist/IDT and clone into the chosen vector after BsmBI/BbsI digestion.">Sense oligo</th> | |
| <th title="Antisense strand of the annealable oligo pair. Order alongside the sense strand, anneal, ligate.">Antisense oligo</th> | |
| <!-- Per-guide vendor buttons. Click → copies | |
| THIS guide's sense+antisense oligo pair | |
| to clipboard + opens vendor in a new tab. | |
| Mirrors the DE page's per-variant | |
| "Synthesize via X" row. --> | |
| <th title="Order this guide's oligo pair from a synthesis vendor. Click → copies the sense + antisense oligos to your clipboard, opens the vendor in a new tab. Paste into their bulk-oligo order form.">Order</th> | |
| <th class="num crispr-sortable" data-sort-key="gc_pct">GC%</th> | |
| <th>Flags</th> | |
| </tr> | |
| </thead> | |
| <tbody id="crisprTableBody"></tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <!-- ===== LOG EDITING EFFICIENCY (feeds the cross-user on-target model) ===== --> | |
| <section class="card primer-design-card" id="crisprOutcomeCard" hidden> | |
| <div class="primer-results-head"> | |
| <span class="primer-results-title">Log editing efficiency</span> | |
| </div> | |
| <p class="field-hint">Tested these guides at the bench? Enter the measured editing efficiency | |
| (% edits / indels). Your guides stay private — only <strong>anonymous, aggregated</strong> | |
| patterns improve the on-target model for everyone.</p> | |
| <div class="ce-list" id="crisprOutcomeList"></div> | |
| <div class="primer-actions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap"> | |
| <button class="primary primary-lg" type="button" id="crisprOutcomeSave">Save results</button> | |
| <span class="round2-status" id="crisprOutcomeStatus" hidden></span> | |
| </div> | |
| </section> | |
| <!-- Lightweight in-card error banner — shown on | |
| validation errors (sequence too short, no PAM | |
| sites, etc.) without scrolling away. --> | |
| <div class="error-banner" id="crisprError" hidden></div> | |
| </section> | |
| <!-- ===================== PRIMER PICKER ===================== --> | |
| <section class="view view--primers" data-view="primers" hidden> | |
| <header class="view-head"> | |
| <h1 class="view-title"><em>Primer Analysis</em></h1> | |
| <p class="view-sub">Paste your candidate primers, BLAST them all in one go, and let the | |
| fitness model rank them — best amplification of your template, fewest off-target matches. | |
| No more BLASTing one primer at a time. Pairs well with Directed Evolution's output — | |
| check or refine the PCR primers for the variants you're ordering.</p> | |
| </header> | |
| <section class="card primer-input-card"> | |
| <label class="field-label" for="primerInput">Candidate primers</label> | |
| <p class="field-hint">One per line or FASTA. Optional names and forward/reverse tags | |
| (e.g. <code>GeneA_F</code> / <code>GeneA_R</code>) — pairs are auto-detected and scored | |
| as amplicons.</p> | |
| <textarea id="primerInput" class="primer-textarea mono" rows="6" spellcheck="false" | |
| placeholder=">GeneA_F ACAGTGACCTGAAGTCCATGC >GeneA_R TTGGCAGTCAACGTTACGGAC"></textarea> | |
| <label class="field-label" for="primerTemplate">Template to amplify | |
| <span class="field-opt">· optional, recommended</span></label> | |
| <p class="field-hint">The DNA region you want to amplify. Stays on our server — used to | |
| check each primer's binding site, uniqueness and the predicted amplicon size.</p> | |
| <textarea id="primerTemplate" class="primer-textarea mono" rows="4" spellcheck="false" | |
| placeholder="Paste the target DNA sequence (A/C/G/T)…"></textarea> | |
| <label class="primer-toggle"> | |
| <input type="checkbox" id="primerScanNcbi" /> | |
| <span class="primer-toggle-text"> | |
| <strong>Run specificity scan (in-silico PCR)</strong> | |
| <span class="field-hint">Searches the chosen genome and runs in-silico PCR to | |
| predict every product your primers would make — intended <em>and</em> off-target — | |
| right here, so you don't need a separate tool. Sequences go to NCBI BLAST | |
| (~1–3 min); leave off if your primers are confidential.</span> | |
| </span> | |
| </label> | |
| <div class="primer-organism-row" id="primerOrganismRow" hidden> | |
| <label class="field-label" for="primerOrganism">Check specificity against</label> | |
| <select id="primerOrganism" class="primer-select"> | |
| <option value="human" selected>Human — Homo sapiens</option> | |
| <option value="mouse">Mouse — Mus musculus</option> | |
| <option value="rat">Rat — Rattus norvegicus</option> | |
| <option value="zebrafish">Zebrafish — Danio rerio</option> | |
| <option value="yeast">Yeast — S. cerevisiae</option> | |
| <option value="ecoli">E. coli</option> | |
| <option value="all">All sequences (nt) — broad, noisier</option> | |
| </select> | |
| <label class="primer-subtoggle"> | |
| <input type="checkbox" id="primerStrict3p" /> | |
| <span>Strict 3′-end matching | |
| <span class="field-hint">Only count off-target sites whose <strong>last 5 bases | |
| match perfectly</strong> — the 3′ end that actually licenses extension. Fewer, | |
| higher-confidence predictions; leave off to also catch borderline sites.</span></span> | |
| </label> | |
| </div> | |
| <div class="primer-actions"> | |
| <button class="ghost" type="button" id="primerExample" title="Loads candidate GFP primers + a template to score">Try an example</button> | |
| <button class="primary primary-lg" type="button" id="primerAnalyze">Analyse primers</button> | |
| </div> | |
| <div class="error-banner" id="primerError" hidden></div> | |
| <div class="primer-warnings" id="primerWarnings" hidden></div> | |
| </section> | |
| <section class="card primer-results-card" id="primerResultsCard" hidden> | |
| <div class="primer-results-head"> | |
| <span class="primer-results-title">Ranked primers</span> | |
| <div class="primer-results-actions"> | |
| <div class="download-menu" id="primerDownloadMenu"> | |
| <button class="ghost download-trigger" type="button" id="primerDownloadBtn" | |
| aria-haspopup="true" aria-expanded="false">Export <span class="caret">▾</span></button> | |
| <div class="download-menu-items" id="primerDownloadItems" hidden> | |
| <a class="download-item" data-format="csv">CSV</a> | |
| <a class="download-item" data-format="xlsx">Excel</a> | |
| </div> | |
| </div> | |
| <button class="ghost" type="button" id="primerSaveBtn">Save</button> | |
| </div> | |
| </div> | |
| <details class="how-to-read"> | |
| <summary>How to read this</summary> | |
| <div class="how-to-read-body"> | |
| <p><strong>Fitness</strong> — the combined score used to rank your candidates (higher = better pick): Tm, GC, 3′-end stability and specificity.</p> | |
| <p><strong>On template</strong> — whether the primer binds your template uniquely. <strong>Off-target products</strong> — extra amplicons in-silico PCR predicts beyond the intended one (0 = specific). <strong>Tm</strong> — nearest-neighbor melting temp; aim for a matched pair.</p> | |
| <p class="how-to-read-note">These primers are yours — TuringDNA scores and ranks them; it doesn’t invent them.</p> | |
| </div> | |
| </details> | |
| <!-- Auto-generated run parameters + copy-paste methods paragraph. --> | |
| <div class="run-meta" id="primerRunMeta" hidden></div> | |
| <div class="primer-scan-status" id="primerScanStatus" hidden></div> | |
| <div class="primer-best" id="primerBest"></div> | |
| <div class="primer-amplicons" id="primerAmplicons"></div> | |
| <div class="primer-products" id="primerProducts"></div> | |
| <div class="primer-list" id="primerList"></div> | |
| <p class="primer-foot">The specificity scan runs full in-silico PCR against your chosen | |
| genome — the same check NCBI Primer-BLAST performs — so the result is complete here. | |
| Predictions are a strong guide; as always, validate critical results at the bench.</p> | |
| </section> | |
| <!-- ============== DE NOVO PRIMER DESIGN ============== --> | |
| <section class="card primer-design-card" id="primerDesignCard"> | |
| <div class="primer-results-head"> | |
| <span class="primer-results-title">Design primers from a template</span> | |
| <button class="ghost" type="button" id="designExample" title="Load an example template">Try an example</button> | |
| </div> | |
| <p class="field-hint">Paste the DNA you want to amplify (and, optionally, the region the | |
| product must span). TuringDNA designs Tm-matched forward/reverse pairs entirely | |
| on-server — no NCBI, nothing leaves.</p> | |
| <label class="field-label" for="designTemplate">Template · DNA or FASTA</label> | |
| <textarea id="designTemplate" class="primer-textarea mono" rows="4" spellcheck="false" | |
| placeholder="Paste DNA (A/C/G/T) or a FASTA record…"></textarea> | |
| <div class="design-opts"> | |
| <label class="design-opt">Target start<input type="number" id="designTargetStart" min="1" placeholder="optional" /></label> | |
| <label class="design-opt">Target end<input type="number" id="designTargetEnd" min="1" placeholder="optional" /></label> | |
| <label class="design-opt">Product min (bp)<input type="number" id="designProductMin" min="40" value="100" /></label> | |
| <label class="design-opt">Product max (bp)<input type="number" id="designProductMax" min="50" value="1000" /></label> | |
| </div> | |
| <div class="primer-actions"> | |
| <button class="primary primary-lg" type="button" id="designBtn">Design primers</button> | |
| </div> | |
| <div class="error-banner" id="designError" hidden></div> | |
| <div class="primer-warnings" id="designWarnings" hidden></div> | |
| <div class="design-results" id="designResults" hidden></div> | |
| <div class="design-gel gel" id="designGel" hidden></div> | |
| </section> | |
| <!-- ============== MULTIPLEX COMPATIBILITY ============== --> | |
| <section class="card primer-design-card" id="primerMultiplexCard"> | |
| <div class="primer-results-head"> | |
| <span class="primer-results-title">Multiplex compatibility check</span> | |
| </div> | |
| <p class="field-hint">Paste all the primers you want to run in one tube. We flag 3′ | |
| cross-dimers between any two of them, and amplicon sizes too close to resolve on a gel.</p> | |
| <label class="field-label" for="mpxInput">Primers · FASTA or one per line</label> | |
| <textarea id="mpxInput" class="primer-textarea mono" rows="5" spellcheck="false" | |
| placeholder=">F1 ACGT… >R1 ACGT…"></textarea> | |
| <label class="field-label" for="mpxSizes">Expected product sizes (bp, optional)</label> | |
| <input id="mpxSizes" class="primer-textarea mono" style="min-height:40px;font-size:16px" | |
| placeholder="e.g. 320, 540, 900" /> | |
| <div class="primer-actions"> | |
| <button class="primary primary-lg" type="button" id="mpxBtn">Check multiplex</button> | |
| </div> | |
| <div class="error-banner" id="mpxError" hidden></div> | |
| <div class="design-results" id="mpxResults" hidden></div> | |
| </section> | |
| <!-- ====== LOCAL SPECIFICITY (in-silico PCR vs a genome) ====== --> | |
| <section class="card primer-design-card" id="primerLocalSpecCard"> | |
| <div class="primer-results-head"> | |
| <span class="primer-results-title">Specificity vs. your own genome</span> | |
| </div> | |
| <p class="field-hint">In-silico PCR against a genome/FASTA you upload — the same check as | |
| the NCBI scan, but it runs <strong>on our server</strong>, so your primers never leave, and it | |
| works for any organism (incl. non-model). Up to ~12 Mb.</p> | |
| <label class="field-label" for="lspecInput">Primers · FASTA or one per line</label> | |
| <textarea id="lspecInput" class="primer-textarea mono" rows="4" spellcheck="false" | |
| placeholder=">F1 ACGT… >R1 ACGT…"></textarea> | |
| <label class="field-label" for="lspecFasta">Genome / FASTA to scan</label> | |
| <textarea id="lspecFasta" class="primer-textarea mono" rows="4" spellcheck="false" | |
| placeholder="Paste a FASTA genome, or upload a file below…"></textarea> | |
| <div class="primer-actions" style="gap:10px;align-items:center;display:flex;flex-wrap:wrap"> | |
| <input type="file" id="lspecFile" accept=".fa,.fasta,.fna,.txt" class="lspec-file" /> | |
| <button class="primary primary-lg" type="button" id="lspecBtn">Run specificity</button> | |
| </div> | |
| <div class="error-banner" id="lspecError" hidden></div> | |
| <div class="design-results" id="lspecResults" hidden></div> | |
| <div class="design-gel gel" id="lspecGel" hidden></div> | |
| </section> | |
| <!-- ===== LOG AMPLIFICATION RESULT (feeds the cross-user primer model) ===== --> | |
| <section class="card primer-design-card" id="primerOutcomeCard"> | |
| <div class="primer-results-head"> | |
| <span class="primer-results-title">Log amplification result</span> | |
| </div> | |
| <p class="field-hint">Ran a PCR with these primers? Tell us if it amplified. Only | |
| <strong>anonymous, aggregated</strong> patterns (Tm/GC/3′-end buckets) improve the | |
| model — your primers stay private.</p> | |
| <div class="design-opts"> | |
| <label class="design-opt" style="flex:1;min-width:220px">Forward primer<input type="text" id="poFwd" placeholder="5'→3'" /></label> | |
| <label class="design-opt" style="flex:1;min-width:220px">Reverse primer<input type="text" id="poRev" placeholder="5'→3'" /></label> | |
| <label class="design-opt">Result | |
| <select id="poResult" class="design-opt-select"> | |
| <option value="1">Amplified cleanly</option> | |
| <option value="0.5">Partial / weak</option> | |
| <option value="0">Failed</option> | |
| </select> | |
| </label> | |
| </div> | |
| <div class="primer-actions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap"> | |
| <button class="primary primary-lg" type="button" id="poSave">Save result</button> | |
| <span class="round2-status" id="poStatus" hidden></span> | |
| </div> | |
| </section> | |
| </section> | |
| <!-- ===================== PLASMID STUDIO ===================== --> | |
| <section class="view view--plasmid" data-view="plasmid" hidden> | |
| <header class="view-head"> | |
| <h1 class="view-title"><em>Plasmid Editor</em></h1> | |
| <p class="view-sub">Import a GenBank or FASTA record (or paste raw DNA), see the annotated | |
| circular & linear map, run restriction analysis across 1,000+ enzymes, simulate a | |
| digest with a virtual gel, and save constructs to your library. Often the next stop | |
| after Directed Evolution — bring a designed insert here to map it, check the | |
| digest, and get it synthesis-ready.</p> | |
| </header> | |
| <section class="card plasmid-input-card" id="plasmidInputCard"> | |
| <div class="plasmid-input-head"> | |
| <label class="field-label" for="plasmidInput">Sequence · GenBank / FASTA / SnapGene .dna / raw DNA</label> | |
| <div class="plasmid-input-actions"> | |
| <button class="ghost" type="button" id="plasmidLibraryBtn">My plasmids</button> | |
| <button class="ghost" type="button" id="plasmidExample" title="Loads a demo construct with real promoters, an AmpR gene and cut sites">Try an example</button> | |
| </div> | |
| </div> | |
| <textarea id="plasmidInput" class="primer-textarea mono" rows="6" spellcheck="false" | |
| placeholder="Paste a GenBank record (LOCUS…), a FASTA (>name), or raw DNA…"></textarea> | |
| <div class="plasmid-input-row"> | |
| <label class="plasmid-file-btn"> | |
| Upload file<input type="file" id="plasmidFile" accept=".dna,.gb,.gbk,.genbank,.fasta,.fa,.fna,.txt,.seq" hidden /> | |
| </label> | |
| <div class="plasmid-topo"> | |
| <span class="field-hint">Topology (raw/FASTA):</span> | |
| <label><input type="radio" name="plasmidTopo" value="circular" checked /> Circular</label> | |
| <label><input type="radio" name="plasmidTopo" value="linear" /> Linear</label> | |
| </div> | |
| <button class="primary primary-lg" type="button" id="plasmidImport">Import & map</button> | |
| </div> | |
| <div class="error-banner" id="plasmidError" hidden></div> | |
| </section> | |
| <!-- | |
| Workbench "home" strip (2026-06-04). The Plasmid Editor is | |
| the hub: from a loaded construct you can stay here to map & | |
| cut, or hand a region to the other three tools. Type-led, | |
| editorial — NOT a SaaS card grid (see the nav comment). | |
| Hidden by showResults() once a construct is loaded. | |
| --> | |
| <section class="workbench-home" id="workbenchHome"> | |
| <p class="card-kicker">§ One workbench</p> | |
| <p class="workbench-lede">Your construct is the centre of gravity. Import it above to map and | |
| cut it here — then, without leaving your sequence, hand any region to the other tools.</p> | |
| <nav class="workbench-links" aria-label="What you can do from your construct"> | |
| <a class="workbench-link" id="wbToImport" href="#plasmid"> | |
| <strong>Map & cut</strong> | |
| <span>annotate · restriction across 1,000+ enzymes · virtual gel · Gibson / Golden Gate / restriction cloning</span> | |
| </a> | |
| <a class="workbench-link" href="#crispr"> | |
| <strong>Design CRISPR guides</strong> | |
| <span>Cas9 knockout & base editing, with off-target and knockout scoring</span> | |
| </a> | |
| <a class="workbench-link" href="#primers"> | |
| <strong>Analyse primers</strong> | |
| <span>in-silico PCR, specificity and Tm — ranks your candidate pairs</span> | |
| </a> | |
| <a class="workbench-link" href="#design"> | |
| <strong>Evolve a CDS</strong> | |
| <span>ESM-2 zero-shot variant library, codon-optimized and synthesis-ready</span> | |
| </a> | |
| </nav> | |
| </section> | |
| <section class="card plasmid-results-card" id="plasmidResultsCard" hidden> | |
| <div class="plasmid-toolbar"> | |
| <div class="plasmid-title-wrap"> | |
| <input id="plasmidName" class="plasmid-name-input" type="text" maxlength="120" /> | |
| <span class="plasmid-meta" id="plasmidMeta"></span> | |
| </div> | |
| <div class="plasmid-toolbar-actions"> | |
| <!-- Circular/Linear are two drawings of the SAME | |
| map pane. "Sequence" is no longer a third | |
| option here because the sequence is always | |
| on screen beside the map — it was only ever | |
| a tab because all three rendered into one | |
| container. See docs/PLASMID-EDITOR.md §2. --> | |
| <div class="plasmid-viewtoggle" role="tablist" aria-label="Map style"> | |
| <button class="pv-tab pv-tab-active" id="pvCircular" type="button">Circular</button> | |
| <button class="pv-tab" id="pvLinear" type="button">Linear</button> | |
| </div> | |
| <div class="pe-shows" role="group" aria-label="Show on the sequence"> | |
| <label><input type="checkbox" id="peShowComp" checked /> Complement</label> | |
| <label><input type="checkbox" id="peShowFeat" checked /> Features</label> | |
| <label><input type="checkbox" id="peShowCuts" checked /> Cut sites</label> | |
| </div> | |
| <button class="ghost" type="button" id="plasmidSave">Save</button> | |
| <div class="plasmid-use"> | |
| <button class="ghost" type="button" id="plasmidUseBtn">Use in ▾</button> | |
| </div> | |
| <div class="plasmid-export"> | |
| <button class="ghost" type="button" id="plasmidExportGb">GenBank ▾</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Sequence editor toolbar (shown only in Sequence view) --> | |
| <div class="plasmid-seq-tools" id="plasmidSeqTools" hidden> | |
| <div class="seqtool-row"> | |
| <div class="seqtool-find"> | |
| <input type="text" id="plasmidFind" class="seqtool-input mono" placeholder="Find sequence (ACGT)…" spellcheck="false" /> | |
| <button class="ghost" type="button" id="plasmidFindPrev" title="Previous match">‹</button> | |
| <button class="ghost" type="button" id="plasmidFindNext" title="Next match">›</button> | |
| <span class="seqtool-count" id="plasmidFindCount"></span> | |
| </div> | |
| <div class="seqtool-sel"> | |
| <label>From <input type="number" id="plasmidSelFrom" class="seqtool-num" min="1" /></label> | |
| <label>To <input type="number" id="plasmidSelTo" class="seqtool-num" min="1" /></label> | |
| <span class="seqtool-sellen" id="plasmidSelLen"></span> | |
| </div> | |
| </div> | |
| <div class="seqtool-row seqtool-ops"> | |
| <button class="ghost" type="button" id="seqCopy">Copy</button> | |
| <button class="ghost" type="button" id="seqCopyRC">Copy rev-comp</button> | |
| <button class="ghost" type="button" id="seqTranslate">Translate</button> | |
| <button class="ghost" type="button" id="seqAnnotate">Annotate…</button> | |
| <button class="ghost" type="button" id="seqDelete">Delete</button> | |
| <button class="ghost" type="button" id="seqInsert">Insert…</button> | |
| <button class="ghost" type="button" id="seqRevComp" title="Reverse-complement the whole plasmid">Rev-comp all</button> | |
| <span class="seqtool-divider"></span> | |
| <button class="ghost" type="button" id="seqToCrispr" title="Design CRISPR guides against the selected region">→ CRISPR</button> | |
| <button class="ghost" type="button" id="seqToPrimers" title="Use the selected region as the Primer Analysis template">→ Primers</button> | |
| <button class="ghost" type="button" id="seqToEvolve" title="Evolve the selected CDS in Directed Evolution">→ Evolve</button> | |
| </div> | |
| <div class="seqtool-output" id="plasmidSeqOutput" hidden></div> | |
| </div> | |
| <!-- Auto-generated construct summary + copy-paste methods paragraph. --> | |
| <div class="run-meta" id="plasmidRunMeta" hidden></div> | |
| <!-- | |
| THE SPLIT. Sequence left, map right, one selection | |
| model between them. Previously all three views wrote | |
| into #plasmidMapWrap, so they could not coexist and | |
| the tab strip was the honest surface of that; the | |
| map's own selection arc was already implemented and | |
| could never be seen, because the map wasn't in the | |
| DOM whenever you were reading sequence. | |
| --> | |
| <div class="pe-split" id="peSplit"> | |
| <section class="pe-pane pe-pane--seq" id="peSeqPane" aria-label="Sequence"> | |
| <div class="pe-pane-h"> | |
| <span class="pe-pane-t">Sequence</span> | |
| <span class="pe-pane-hint" id="peSeqHint"></span> | |
| </div> | |
| <div class="pe-seq-scroll" id="plasmidSeqWrap"><!-- sequence injected --></div> | |
| </section> | |
| <div class="pe-divider" id="peDivider" role="separator" | |
| aria-orientation="vertical" tabindex="0" | |
| aria-label="Resize the sequence and map panes"></div> | |
| <section class="pe-pane pe-pane--map" aria-label="Map"> | |
| <div class="pe-pane-h"> | |
| <span class="pe-pane-t">Map</span> | |
| <span class="pe-pane-hint" id="peMapHint"></span> | |
| </div> | |
| <div class="pe-map-scroll"> | |
| <div class="plasmid-map-wrap" id="plasmidMapWrap"><!-- SVG map injected --></div> | |
| <h3 class="primer-sec-title pe-feat-t">Features</h3> | |
| <div class="plasmid-feature-list" id="plasmidFeatures"></div> | |
| </div> | |
| </section> | |
| </div> | |
| <!-- One line of truth about what is selected, read by | |
| both panes and by the agent. --> | |
| <div class="pe-status" id="peStatus" role="status" aria-live="polite"></div> | |
| <!-- Restriction analysis + digest --> | |
| <div class="plasmid-enzymes"> | |
| <h3 class="primer-sec-title">Restriction analysis</h3> | |
| <p class="field-hint" id="plasmidEnzymeHint">Tap enzymes to show their cut sites on the map; pick a few and digest to size the fragments.</p> | |
| <div class="plasmid-enzyme-grid" id="plasmidEnzymeGrid"></div> | |
| <div class="plasmid-digest-actions"> | |
| <button class="ghost" type="button" id="plasmidDigestBtn">Digest selected →</button> | |
| <button class="plasmid-clear-enz" type="button" id="plasmidClearEnz">Clear</button> | |
| </div> | |
| <div class="plasmid-gel-wrap" id="plasmidGel" hidden></div> | |
| </div> | |
| <p class="primer-foot" id="plasmidFoot">Restriction sites from Biopython/REBASE (1,000+ enzymes), | |
| circular-aware. Auto-annotation flags ORFs + common motifs; GenBank features are kept as-is.</p> | |
| </section> | |
| <!-- Pairwise sequence alignment --> | |
| <section class="card plasmid-align-card"> | |
| <div class="clone-head"> | |
| <h2 class="clone-title">Align two sequences</h2> | |
| <div class="clone-methods" role="tablist"> | |
| <button class="clone-method clone-method-active" data-align-mode="global" type="button">Global</button> | |
| <button class="clone-method" data-align-mode="local" type="button">Local</button> | |
| </div> | |
| </div> | |
| <p class="field-hint">Compare two DNA or protein sequences — percent identity, mismatches and gaps. <strong>Global</strong> aligns end-to-end (Needleman–Wunsch); <strong>Local</strong> finds the best shared region (Smith–Waterman).</p> | |
| <div class="align-inputs"> | |
| <textarea id="alignA" class="primer-textarea mono" rows="3" spellcheck="false" placeholder="Sequence A — ACGT… or protein"></textarea> | |
| <textarea id="alignB" class="primer-textarea mono" rows="3" spellcheck="false" placeholder="Sequence B"></textarea> | |
| </div> | |
| <div class="align-actions"> | |
| <button class="primary" type="button" id="alignRun">Align</button> | |
| <span class="align-stats" id="alignStats"></span> | |
| </div> | |
| <div class="align-output" id="alignOutput" hidden></div> | |
| </section> | |
| <!-- Cloning / assembly --> | |
| <section class="card plasmid-clone-card"> | |
| <div class="clone-head"> | |
| <h2 class="clone-title">Clone & assemble</h2> | |
| <div class="clone-methods" role="tablist"> | |
| <button class="clone-method clone-method-active" data-method="gibson" type="button">Gibson / overlap</button> | |
| <button class="clone-method" data-method="golden_gate" type="button">Golden Gate</button> | |
| <button class="clone-method" data-method="restriction" type="button">Restriction</button> | |
| </div> | |
| </div> | |
| <!-- Gibson / Golden Gate: fragment list --> | |
| <div class="clone-pane" id="clonePaneFragments"> | |
| <p class="field-hint" id="cloneFragHint">Paste your fragments — FASTA, or one per line as <code>name: SEQUENCE</code>. Overlapping ends assemble scarlessly; bare junctions get homology arms designed into the primers.</p> | |
| <textarea id="cloneFragments" class="primer-textarea mono" rows="6" spellcheck="false" | |
| placeholder=">backbone ACGT… >insert TTGG…"></textarea> | |
| <div class="clone-opts"> | |
| <button class="ghost" type="button" id="cloneUseCurrent">+ Use current plasmid</button> | |
| <label class="clone-circ" id="cloneCircWrap"><input type="checkbox" id="cloneCircular" checked /> Circular product</label> | |
| <label class="clone-gg-enz" id="cloneGgEnzWrap" hidden>Enzyme | |
| <select id="cloneGgEnzyme" class="seqtool-num" style="width:auto"> | |
| <option value="BsaI">BsaI</option> | |
| <option value="BbsI">BbsI</option> | |
| <option value="BsmBI">BsmBI</option> | |
| </select> | |
| </label> | |
| </div> | |
| </div> | |
| <!-- Restriction: vector + insert --> | |
| <div class="clone-pane" id="clonePaneRestriction" hidden> | |
| <label class="field-label" for="cloneVector">Vector (cut once by the enzyme)</label> | |
| <textarea id="cloneVector" class="primer-textarea mono" rows="3" spellcheck="false" placeholder="Vector DNA…"></textarea> | |
| <label class="field-label" for="cloneInsert">Insert (flanked by the same enzyme)</label> | |
| <textarea id="cloneInsert" class="primer-textarea mono" rows="3" spellcheck="false" placeholder="Insert DNA…"></textarea> | |
| <div class="clone-opts"> | |
| <label>Enzyme <input type="text" id="cloneReEnzyme" class="seqtool-num" style="width:90px" value="EcoRI" /></label> | |
| <label class="clone-circ"><input type="checkbox" id="cloneVecCirc" checked /> Vector is circular</label> | |
| </div> | |
| </div> | |
| <div class="clone-actions"> | |
| <button class="primary primary-lg" type="button" id="cloneRun">Assemble →</button> | |
| </div> | |
| <div class="error-banner" id="cloneError" hidden></div> | |
| <div class="clone-result" id="cloneResult" hidden></div> | |
| </section> | |
| <!-- ===== LOG CLONING RESULT (feeds the cross-user assembly model) ===== --> | |
| <section class="card primer-design-card" id="cloneOutcomeCard"> | |
| <div class="primer-results-head"> | |
| <span class="primer-results-title">Log cloning result</span> | |
| </div> | |
| <p class="field-hint">Did your assembly work at the bench? Logging it teaches the model which | |
| methods/junctions succeed. Only <strong>anonymous, aggregated</strong> patterns are kept.</p> | |
| <div class="design-opts"> | |
| <label class="design-opt">Method | |
| <select id="coMethod" class="design-opt-select"> | |
| <option value="gibson">Gibson</option> | |
| <option value="goldengate">Golden Gate</option> | |
| <option value="restriction">Restriction</option> | |
| <option value="gateway">Gateway</option> | |
| </select> | |
| </label> | |
| <label class="design-opt">Fragments<input type="number" id="coFrags" min="1" max="20" value="2" /></label> | |
| <label class="design-opt">Overlap (bp)<input type="number" id="coOverlap" min="0" max="120" value="25" /></label> | |
| <label class="design-opt">Result | |
| <select id="coResult" class="design-opt-select"> | |
| <option value="" selected>Select outcome…</option> | |
| <option value="1">Assembled</option> | |
| <option value="0.5">Partial</option> | |
| <option value="0">Failed</option> | |
| </select> | |
| </label> | |
| </div> | |
| <div class="primer-actions" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap"> | |
| <button class="primary primary-lg" type="button" id="coSave">Save result</button> | |
| <span class="round2-status" id="coStatus" hidden></span> | |
| </div> | |
| </section> | |
| <!-- Library modal --> | |
| <div class="modal" id="plasmidLibraryModal" hidden role="dialog" aria-modal="true"> | |
| <div class="modal-backdrop" data-plasmid-lib-close></div> | |
| <div class="modal-panel" role="document"> | |
| <header class="modal-header"> | |
| <div><p class="modal-sub">§ Library</p><h2>My plasmids</h2></div> | |
| <button class="modal-close" aria-label="Close" data-plasmid-lib-close>×</button> | |
| </header> | |
| <div class="modal-body"><div id="plasmidLibraryList" class="plasmid-lib-list"></div></div> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="view view--docs" data-view="docs" hidden> | |
| <section class="card docs-card"> | |
| <header class="card-header"> | |
| <div class="card-header-left"> | |
| <p class="card-kicker">§ Documentation</p> | |
| <h2>How TuringDNA works</h2> | |
| <p class="card-sub">Turing, and the four tools it runs — what each does, what to trust, how to cite it.</p> | |
| </div> | |
| </header> | |
| <div class="docs-body"> | |
| <section> | |
| <h3>Overview</h3> | |
| <p>TuringDNA is driven by <strong>Turing</strong> — tell it what you want (fetch a gene, evolve it, design guides, check primers) and it calls the right tool, in the right order, in one conversation. Every tool is also available directly, for hands-on work without the chat.</p> | |
| <p>Your individual sequences stay private — never shared, exposed, or reproduced, and they only leave this machine on the explicit opt-in network steps listed below. Only anonymous, aggregated signals improve our models (<a href="https://turingdna.com/privacy/" target="_blank" rel="noopener">Privacy Policy</a>).</p> | |
| </section> | |
| <section> | |
| <h3>Turing</h3> | |
| <p>A conversational agent that runs the other four tools for you — describe what you want and it calls the right one, chaining them in a single reply when a task needs more than one (fetch a gene, then evolve it, for example).</p> | |
| <ul class="docs-list"> | |
| <li><strong>Tools it can call.</strong> fetch_sequence (gene symbol/accession → real DNA, via Ensembl/NCBI), design_variant_library (directed evolution), design_crispr_guides, design_primers — the same engines the four direct tools use below, not a separate model.</li> | |
| <li><strong>Sign-in.</strong> Chatting is free; running a tool (fetching a real sequence, generating a design) requires a free account, same as the direct tools.</li> | |
| <li><strong>Memory.</strong> Remembers what it's fetched or designed earlier in the same conversation — refer back to “it” or “that gene” instead of re-pasting.</li> | |
| <li><strong>Honesty.</strong> If something's out of scope — a capability that isn't wired up, or a request needing bench validation it can't do — it says so instead of fabricating a result.</li> | |
| </ul> | |
| <p class="docs-callout">Turing calls the same scoring/design code the direct tools call — the same caveats apply. See each tool's section below for what to trust and how to cite it.</p> | |
| </section> | |
| <section> | |
| <h3>Plasmid Editor</h3> | |
| <p>Import and work with circular or linear constructs end to end.</p> | |
| <ul class="docs-list"> | |
| <li><strong>Import.</strong> GenBank · FASTA · EMBL · SnapGene-style features · raw DNA. Annotations are read from the record where present.</li> | |
| <li><strong>Maps & editing.</strong> Annotated circular and linear maps, plus a sequence view with feature colouring, find, translate, reverse-complement and in-place editing.</li> | |
| <li><strong>Restriction.</strong> Scans the REBASE enzyme set (1,000+ enzymes), separates cutters from non-cutters, and simulates a digest on a virtual agarose gel.</li> | |
| <li><strong>Cloning.</strong> Simulates Gibson assembly, Golden Gate, and restriction-ligation — junctions, fragment order, and the assembled product.</li> | |
| <li><strong>Export.</strong> GenBank or FASTA; save constructs to your per-user library for 45 days.</li> | |
| </ul> | |
| <p class="docs-callout">Cloning and digests are <strong>in-silico predictions</strong>. Confirm overhangs, junctions and orientation before you commit reagents.</p> | |
| </section> | |
| <section> | |
| <h3>CRISPR</h3> | |
| <p>Design SpCas9 guide RNAs against a pasted region (or a gene resolved by symbol/accession) — for knockout or base editing.</p> | |
| <ul class="docs-list"> | |
| <li><strong>Guides.</strong> Every NGG-PAM 20-mer with an on-target activity score (Doench-style sequence features), strand and position.</li> | |
| <li><strong>Off-target.</strong> CFD scoring (Doench 2016) against other sites in the input. Base-edit mode adds CBE/ABE windows, the predicted edit, the amino-acid consequence, bystander flags and CRISPR-STOP knockouts.</li> | |
| <li><strong>Knockout.</strong> Predicted indel spectrum, frameshift %, out-of-frame dominance, and a loss-of-function likelihood.</li> | |
| <li><strong>Cloning oligos.</strong> Ready-to-order sense/antisense pairs for the standard vectors (BbsI/BsmBI; Cas12a geometry), copied straight to a vendor order.</li> | |
| </ul> | |
| <p class="docs-callout">Off-target is screened against the genome when you choose an organism: your top-ranked guides are searched with a seed index and CFD-scored. Coverage is the <strong>complete genome</strong> for E. coli and <strong>coding sequence</strong> for human (GRCh38) and mouse (GRCm39) — intronic and intergenic off-targets sit outside that index, so a whole-genome tool is still the right call if your application depends on them. Leave the organism unset and only the sequence you pasted is checked.</p> | |
| </section> | |
| <section> | |
| <h3>Primer Analysis</h3> | |
| <p>Bring your <em>candidate</em> primers; TuringDNA evaluates and ranks them — it does not invent primers from scratch.</p> | |
| <ul class="docs-list"> | |
| <li><strong>Scoring.</strong> Nearest-neighbor Tm (SantaLucia), GC content, 3′-end stability, and hairpin / self-dimer heuristics for every primer.</li> | |
| <li><strong>In-silico PCR.</strong> Binds each pair against your template and reports the intended amplicon plus any off-target products and their sizes.</li> | |
| <li><strong>Specificity (opt-in).</strong> Checks candidates against NCBI nt and separates intended from off-target hits.</li> | |
| <li><strong>Ranking.</strong> Combines the above into a recommended pair — no BLASTing one primer at a time.</li> | |
| </ul> | |
| </section> | |
| <section> | |
| <h3>Directed Evolution</h3> | |
| <p>Design a smart mutation library for an existing protein using ESM-2 zero-shot scoring. Provide a wild-type sequence (or send a CDS from the Plasmid Editor); receive a library of multi-mutant variants ranked by predicted evolutionary fitness, codon-optimized for your host, ready to order.</p> | |
| <p>This is <strong>not</strong> de novo protein design. It's directed-evolution library generation: improving an existing protein along an existing axis (stability, expression, mild activity tuning).</p> | |
| <ol class="docs-list"> | |
| <li><strong>Parse & translate.</strong> FASTA · SnapGene · GenBank · EMBL · raw DNA · raw protein. Plasmid files surface a CDS picker; raw DNA with multiple stops triggers 6-frame ORF discovery.</li> | |
| <li><strong>Zero-shot scoring.</strong> ESM-2 computes ΔLL = log P(mutant | x_WT) − log P(WT | x_WT) for every position × 19 substitutions (Meier et al. 2021 wild-type marginal scheme).</li> | |
| <li><strong>Combinatorial search.</strong> Simulated annealing over the top-percentile pool of single-site mutations. Multiple restarts; cumulative ΣΔLL as fitness; stop-codon and duplicate-position penalties.</li> | |
| <li><strong>Codon optimization.</strong> Reverse-translate with a host codon-usage table. Synonymously scrub BsaI / BsmBI / NotI sites for Golden Gate compatibility.</li> | |
| </ol> | |
| <table class="docs-table"> | |
| <tr><th>Mutations / variant</th><th>Approx. functional retention</th></tr> | |
| <tr><td>1–2</td><td>70–85%</td></tr> | |
| <tr><td>3–4</td><td>50–70%</td></tr> | |
| <tr><td>5–6</td><td>30–55%</td></tr> | |
| <tr><td>7–8</td><td>15–40%</td></tr> | |
| <tr><td>9+</td><td>< 25%</td></tr> | |
| </table> | |
| <p class="docs-callout">Stay shallow. Cap <code>Max mutations / variant</code> at 3–4 unless you have a structural reason. Screen, don't trust.</p> | |
| <p><strong>What ESM-2 sees:</strong> evolutionary plausibility from ~65 M UniRef50 sequences — conservation, coevolution, sequence context. <strong>What it can't see:</strong> structure explicitly, rare-but-essential active-site roles, epistasis between selected mutations; membrane proteins, IDPs and multi-domain assemblies are weakest.</p> | |
| <p><strong>Filter syntax</strong> (variant table): <code>C49</code> = variants mutating residue 49 · <code>W58L</code> = exactly that substitution · <code>gc>50</code>, <code>tm>58</code>, <code>fitness>2</code>, <code>bp<800</code> = numeric ranges.</p> | |
| </section> | |
| <section> | |
| <h3>What runs locally vs over the network</h3> | |
| <ul class="docs-list"> | |
| <li><strong>Local:</strong> plasmid parsing, annotation, restriction & cloning simulation; CRISPR design, CFD off-target and oligo generation; primer scoring and in-silico PCR; ESM-2 inference, simulated annealing and codon optimization; all CSV / Excel / GenBank / FASTA export. Sequences never leave this machine for these steps.</li> | |
| <li><strong>Opt-in network:</strong> NCBI BLAST (sequence identification and primer specificity — sends that sequence to NCBI); AlphaFold-DB / ESMFold structure embeds; Europe PMC literature lookup (IP Radar); synthesis-vendor redirects. Each is explicit and off by default.</li> | |
| </ul> | |
| </section> | |
| <section> | |
| <h3>Citations</h3> | |
| <p class="docs-citation">Meier et al. (2021). Language models enable zero-shot prediction of the effects of mutations on protein function. <cite>NeurIPS</cite> 34.</p> | |
| <p class="docs-citation">Lin et al. (2023). Evolutionary-scale prediction of atomic-level protein structure. <cite>Science</cite> 379:1123–1130.</p> | |
| <p class="docs-citation">Doench et al. (2016). Optimized sgRNA design to maximize activity and minimize off-target effects of CRISPR-Cas9. <cite>Nat. Biotechnol.</cite> 34:184–191.</p> | |
| <p class="docs-citation">Hsu et al. (2013). DNA targeting specificity of RNA-guided Cas9 nucleases. <cite>Nat. Biotechnol.</cite> 31:827–832.</p> | |
| <p class="docs-citation">Komor et al. (2016) & Gaudelli et al. (2017). Programmable base editing of C·G and A·T pairs. <cite>Nature</cite> 533:420 / 551:464.</p> | |
| <p class="docs-citation">Allawi & SantaLucia (1997). Thermodynamics and NMR of internal G·T mismatches in DNA. <cite>Biochemistry</cite> 36:10581–10594.</p> | |
| </section> | |
| <section> | |
| <h3>Licenses of bundled components</h3> | |
| <ul class="docs-list"> | |
| <li>ESM-2 weights — MIT (Meta/FAIR)</li> | |
| <li>Transformers, Accelerate — Apache 2.0 (Hugging Face)</li> | |
| <li>PyTorch — BSD-style (Meta)</li> | |
| <li>Biopython (parsing, restriction, Tm) — BSD-derived</li> | |
| <li>Mol* viewer — MIT (PDBe / RCSB)</li> | |
| <li>Inter, JetBrains Mono — SIL Open Font License</li> | |
| <li>Lucide icons — ISC</li> | |
| </ul> | |
| </section> | |
| </div> | |
| </section> | |
| </section> | |
| </div> | |
| <footer> | |
| <span class="footer-note">TuringDNA v0.1 · individual sequences kept private · <a href="https://turingdna.com/privacy/" target="_blank" rel="noopener">Privacy</a> · <a href="https://turingdna.com/terms/" target="_blank" rel="noopener">Terms</a></span> | |
| </footer> | |
| </main> | |
| </div> | |
| <!-- CRISPR sign-in modal — fires when an anonymous user clicks | |
| the CRISPR sidebar tab OR when the backend returns 403 | |
| signin_required (e.g., expired JWT). Same .modal / | |
| .modal-panel-narrow infrastructure as the BLAST consent | |
| modal — centered overlay, dismissable, doesn't depend on | |
| scroll position. --> | |
| <div class="modal modal--sheet" id="crisprSigninModal" hidden role="dialog" aria-labelledby="crisprSigninTitle" aria-modal="true"> | |
| <div class="modal-backdrop" data-crispr-signin-close></div> | |
| <div class="modal-panel modal-panel-narrow" role="document"> | |
| <header class="modal-header"> | |
| <div> | |
| <p class="modal-sub">§ Sign in</p> | |
| <h2 id="crisprSigninTitle">These tools are members-only.</h2> | |
| </div> | |
| <button class="modal-close" aria-label="Close" data-crispr-signin-close>×</button> | |
| </header> | |
| <div class="modal-body"> | |
| <p> | |
| Don't miss out — the <strong>CRISPR designer</strong>, | |
| <strong>Primer Analysis</strong>, and the <strong>Plasmid Editor</strong> are free | |
| for anyone with an account. Design, analyse, export any time, and save your | |
| designs and plasmids for <strong>45 days</strong>. | |
| </p> | |
| <p> | |
| No card, no spam, takes ten seconds. <span style="white-space:nowrap;">:))</span> | |
| </p> | |
| <div class="blast-consent-actions"> | |
| <button class="ghost" type="button" data-crispr-signin-close>Maybe later</button> | |
| <a class="primary" href="https://turingdna.com/signup/?from=crispr" target="_top" id="crisprSigninCreate">Create free account</a> | |
| <a class="ghost" href="https://turingdna.com/signin/?from=crispr" target="_top" id="crisprSigninExisting">I already have one</a> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Phase 3 (M5): CRISPR structure viewer. Opens from the "why this | |
| guide" panel when a gene context exists. Shows the gene's | |
| AlphaFold-DB model (via the Mol* viewer already loaded for the | |
| directed-evolution page) with the Cas9 cut-site residue focused | |
| + highlighted. --> | |
| <div class="modal" id="crisprStructureModal" hidden role="dialog" aria-labelledby="crisprStructureTitle" aria-modal="true"> | |
| <div class="modal-backdrop" data-crispr-struct-close></div> | |
| <div class="modal-panel" role="document"> | |
| <header class="modal-header"> | |
| <div> | |
| <p class="modal-sub">§ Structure</p> | |
| <h2 id="crisprStructureTitle">Cut site on the protein</h2> | |
| <p class="modal-sub" id="crisprStructureMeta">—</p> | |
| </div> | |
| <button class="modal-close" aria-label="Close" data-crispr-struct-close>×</button> | |
| </header> | |
| <div class="modal-body"> | |
| <div class="alphafold-viewer crispr-structure-viewer" id="crisprStructureViewer"> | |
| <div class="alphafold-loading">Loading AlphaFold-DB structure…</div> | |
| </div> | |
| <p class="alphafold-disclaimer" id="crisprStructureDisclaimer"> | |
| Predicted structure from AlphaFold-DB. The highlighted residue is the | |
| Cas9 cut site mapped through the coding sequence — an approximate | |
| location, not experimentally determined. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Phase 3 (M4): saved-designs list. Opens from "My designs"; click a | |
| row to reopen that guide set in the table. --> | |
| <div class="modal" id="crisprDesignsModal" hidden role="dialog" aria-labelledby="crisprDesignsTitle" aria-modal="true"> | |
| <div class="modal-backdrop" data-crispr-designs-close></div> | |
| <div class="modal-panel modal-panel-narrow" role="document"> | |
| <header class="modal-header"> | |
| <div> | |
| <p class="modal-sub">§ Library</p> | |
| <h2 id="crisprDesignsTitle">My saved designs</h2> | |
| <p class="modal-sub">Kept for 45 days. Click one to reopen it.</p> | |
| </div> | |
| <button class="modal-close" aria-label="Close" data-crispr-designs-close>×</button> | |
| </header> | |
| <div class="modal-body"> | |
| <div id="crisprDesignsList" class="crispr-designs-list"> | |
| <p class="muted">Loading…</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Unified saved-work hub (2026-06-05). One panel listing everything the | |
| signed-in user has saved across all four tools, each reopenable — | |
| replaces the per-tool "My plasmids" list and the CRISPR-only "My | |
| designs". Sections lazy-load in parallel; each degrades independently. --> | |
| <div class="modal" id="savedModal" hidden role="dialog" aria-labelledby="savedTitle" aria-modal="true"> | |
| <div class="modal-backdrop" data-saved-close></div> | |
| <div class="modal-panel" role="document"> | |
| <header class="modal-header"> | |
| <div> | |
| <p class="modal-sub">§ Library</p> | |
| <h2 id="savedTitle">My designs</h2> | |
| <p class="modal-sub">Everything you’ve saved — kept for 45 days. Click one to reopen it.</p> | |
| </div> | |
| <button class="modal-close" aria-label="Close" data-saved-close>×</button> | |
| </header> | |
| <div class="modal-body saved-body"> | |
| <section class="saved-section"> | |
| <h3 class="saved-sec-title">Plasmids</h3> | |
| <div id="savedPlasmids" class="saved-list"><p class="muted">Loading…</p></div> | |
| </section> | |
| <section class="saved-section"> | |
| <h3 class="saved-sec-title">CRISPR designs</h3> | |
| <div id="savedCrispr" class="saved-list"><p class="muted">Loading…</p></div> | |
| </section> | |
| <section class="saved-section"> | |
| <h3 class="saved-sec-title">Primer sets</h3> | |
| <div id="savedPrimers" class="saved-list"><p class="muted">Loading…</p></div> | |
| </section> | |
| <section class="saved-section"> | |
| <h3 class="saved-sec-title">Evolution libraries</h3> | |
| <div id="savedLibraries" class="saved-list"><p class="muted">Loading…</p></div> | |
| </section> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- "Start here" goal router (2026-06-05). Shown once after the first-use | |
| disclaimer and re-openable from the sidebar — orients a newcomer to | |
| the right tool by goal. Editorial + dismissible, never a blocker. --> | |
| <!-- ===================== FIELD ATLAS (Pillar 2: the living commons) ===== | |
| A public, browsable view of the de-identified, k-anonymized cross-user | |
| substitution-effect aggregate (public.mutation_priors, served by | |
| /api/atlas). A 20×20 from→to amino-acid grid: which substitution TYPES | |
| the field has found favorable (or not), pooled across labs — the | |
| network effect made visible. Honest cold-start: empty until enough | |
| labs have logged outcomes. Controller: TDAtlas in app.js. --> | |
| <div class="modal" id="atlasModal" hidden role="dialog" aria-labelledby="atlasTitle" aria-modal="true"> | |
| <div class="modal-backdrop" data-atlas-close></div> | |
| <div class="modal-panel atlas-panel" role="document"> | |
| <header class="modal-header"> | |
| <div> | |
| <p class="modal-sub">§ The living commons</p> | |
| <h2 id="atlasTitle">Field atlas</h2> | |
| <p class="modal-sub">What every lab’s bench results, pooled and de-identified, have taught the engine — by amino-acid substitution. Your data makes this sharper for everyone; nothing here is attributable to a person.</p> | |
| </div> | |
| <button class="modal-close" aria-label="Close" data-atlas-close>×</button> | |
| </header> | |
| <div class="modal-body"> | |
| <div class="atlas-body" id="atlasBody"><p class="muted">Loading the commons…</p></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="modal" id="startHereModal" hidden role="dialog" aria-labelledby="startHereTitle" aria-modal="true"> | |
| <div class="modal-backdrop" data-starthere-close></div> | |
| <div class="modal-panel" role="document"> | |
| <header class="modal-header"> | |
| <div> | |
| <p class="modal-sub">§ Start here</p> | |
| <h2 id="startHereTitle">What are you working on?</h2> | |
| <p class="modal-sub">Tell Turing what you’re trying to do — it fetches genes, evolves them, designs guides, and checks primers for you, in one conversation. Prefer hands-on? Jump straight to a tool below.</p> | |
| </div> | |
| <button class="modal-close" aria-label="Close" data-starthere-close>×</button> | |
| </header> | |
| <div class="modal-body"> | |
| <div class="starthere-goals"> | |
| <button type="button" class="starthere-goal" data-goal="turing"> | |
| <strong>Just tell Turing what you need <span class="pill">Start here</span></strong> | |
| <span>Fetch a gene, evolve it, design guides, check primers — it runs the right tool for you</span> | |
| </button> | |
| </div> | |
| <p class="modal-sub starthere-divider">…or jump straight to a tool</p> | |
| <div class="starthere-goals"> | |
| <button type="button" class="starthere-goal" data-goal="design"> | |
| <strong>Evolve or improve a protein</strong> | |
| <span>Design a ranked variant library with ESM-2 — then build, edit, and log results for round 2</span> | |
| </button> | |
| <button type="button" class="starthere-goal" data-goal="plasmid"> | |
| <strong>Build a construct from your variants</strong> | |
| <span>Clone, map, and annotate — Gibson, Golden Gate, or restriction digest</span> | |
| </button> | |
| <button type="button" class="starthere-goal" data-goal="crispr"> | |
| <strong>Edit or knock out a gene</strong> | |
| <span>Design CRISPR guide RNAs — knockout or base editing</span> | |
| </button> | |
| <button type="button" class="starthere-goal" data-goal="primers"> | |
| <strong>Check or pick PCR primers</strong> | |
| <span>Score your candidates with in-silico PCR + specificity</span> | |
| </button> | |
| </div> | |
| <button type="button" class="starthere-skip" data-starthere-close>I’ll explore on my own →</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- BLAST consent modal — fires the first time per-session that the | |
| user clicks "Identify via NCBI BLAST". Per-session sessionStorage | |
| flag means the user only sees this once per browser session. | |
| Centered .modal-panel-narrow keeps it visually anchored to the | |
| viewport center regardless of where the user has scrolled to. --> | |
| <div class="modal" id="blastConsentModal" hidden role="dialog" aria-labelledby="blastConsentTitle" aria-modal="true"> | |
| <div class="modal-backdrop" data-blast-cancel></div> | |
| <div class="modal-panel modal-panel-narrow" role="document"> | |
| <header class="modal-header"> | |
| <div> | |
| <p class="modal-sub">§ Consent</p> | |
| <h2 id="blastConsentTitle">Send sequence to NCBI?</h2> | |
| </div> | |
| <button class="modal-close" aria-label="Close" data-blast-cancel>×</button> | |
| </header> | |
| <div class="modal-body"> | |
| <p> | |
| Identification runs BLASTp against NCBI's <code>nr</code> | |
| database. To do that, the wild-type protein sequence is sent | |
| to <code>blast.ncbi.nlm.nih.gov</code> over HTTPS — | |
| encrypted in transit, visible to the U.S. National Library | |
| of Medicine, and logged by NCBI for service operation and | |
| security. | |
| </p> | |
| <p> | |
| <em>Don't BLAST sequences that are confidential or | |
| pre-publication IP.</em> The on-server pipeline (scoring, | |
| search, codon optimization) keeps sequences local; only | |
| BLAST and the opt-in ESMFold structure prediction leave | |
| the machine. | |
| </p> | |
| <div class="blast-consent-actions"> | |
| <button class="ghost" type="button" data-blast-cancel>Cancel</button> | |
| <button class="primary" type="button" id="blastConsentContinue"> | |
| Continue · remember for this session | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ESMFold modal — fires from the per-row "Fold" button. POSTs the | |
| variant's amino-acid sequence to Meta's public ESM Atlas API and | |
| renders the returned PDB in a Mol* viewer. Standard .modal / | |
| .modal-panel structure with the shared close-wiring. --> | |
| <div class="modal" id="foldModal" hidden role="dialog" aria-labelledby="foldModalTitle"> | |
| <div class="modal-backdrop" data-close-modal></div> | |
| <div class="modal-panel" role="document"> | |
| <header class="modal-header"> | |
| <div> | |
| <h2 id="foldModalTitle">Predicted structure · ESMFold</h2> | |
| <p class="modal-sub" id="foldModalSub">Folding with Meta ESMFold…</p> | |
| </div> | |
| <button class="modal-close" aria-label="Close" data-close-modal>×</button> | |
| </header> | |
| <div class="modal-body" id="foldModalBody"> | |
| <!-- Re-uses the .alphafold-viewer / .alphafold-loading | |
| classes so the embed reads identically to the BLAST → | |
| AlphaFold viewer further down the page. --> | |
| <div class="alphafold-viewer" id="foldViewer"> | |
| <div class="alphafold-loading"> | |
| <span class="identify-spinner"></span> | |
| Submitting sequence to ESMFold… | |
| </div> | |
| </div> | |
| <div class="alphafold-disclaimer" id="foldModalDisclaimer"> | |
| Predicted by <a href="https://esmatlas.com/about" target="_blank" rel="noopener">ESMFold</a> (Meta AI). | |
| <strong>The variant's amino-acid sequence is sent to <code>api.esmatlas.com</code></strong> | |
| over HTTPS — encrypted in transit, visible to Meta's servers. Do not fold | |
| confidential or pre-publication sequences. | |
| Drag to rotate · scroll to zoom · double-click to fit. | |
| Backbone confidence colored by pLDDT (blue = high, orange = low). | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ===================================================================== | |
| First-use disclaimer modal — shown automatically on first visit | |
| (localStorage flag) and re-openable via the "Why?" link in the | |
| research-strip under the topbar. Single-click acceptance; no fields | |
| to fill. The text is the in-UI half of the legal narrative — paired | |
| with the ToS the user's lawyer will draft and the per-session | |
| synthesis-confirmation modal below. | |
| The modal blocks app interaction until the user clicks Accept (the | |
| underlying modal-backdrop catches clicks and doesn't dismiss). | |
| ===================================================================== --> | |
| <div class="modal modal-disclaimer" id="firstUseModal" hidden role="dialog" aria-labelledby="firstUseTitle" aria-modal="true"> | |
| <div class="modal-backdrop modal-backdrop-fixed"></div> | |
| <div class="modal-panel modal-panel-narrow" role="document"> | |
| <div class="disclaimer-body"> | |
| <p class="boot-kicker">§ Before you begin</p> | |
| <h2 id="firstUseTitle" class="disclaimer-title"><em>Research use only.</em></h2> | |
| <hr class="boot-rule" /> | |
| <p> | |
| TuringDNA designs candidate variant libraries with a | |
| protein-language-model scoring pipeline. The outputs are | |
| <strong>computational predictions</strong>, not laboratory | |
| measurements. | |
| </p> | |
| <p> | |
| You are responsible for visually verifying every sequence | |
| before sending it to a synthesis vendor. The tool is | |
| intended for research and discovery — <em>not</em> for | |
| diagnostic, therapeutic, or clinical decision-making. | |
| </p> | |
| <p> | |
| Sequences sent through the optional NCBI BLAST and ESMFold | |
| features leave this machine; both are gated behind | |
| explicit per-session consent. Don't run them on | |
| confidential or pre-publication sequences. | |
| </p> | |
| <div class="disclaimer-actions"> | |
| <button class="primary" type="button" id="firstUseAccept"> | |
| I understand — continue | |
| </button> | |
| </div> | |
| <p class="disclaimer-meta"> | |
| Acceptance is remembered on this browser. Clear site data | |
| to be re-prompted. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- ===================================================================== | |
| Per-session synthesis verification modal — interposes between the | |
| user clicking a Synthesize / Copy designed insert button and the | |
| actual network call or clipboard write. Asks the user to actively | |
| tick a "I have visually verified this sequence" checkbox. Once | |
| ticked once per session (sessionStorage), subsequent synth actions | |
| in the same tab skip this modal and use the existing per-action | |
| confirms (RE-warn etc.) only when applicable. | |
| The acknowledgment is intentionally per-session, not per-action: | |
| per-action would create research-tool-hostile friction; per-session | |
| still produces a clear "user actively opted in to shipping DNA" | |
| record for the legal narrative. | |
| ===================================================================== --> | |
| <div class="modal modal-disclaimer" id="synthConfirmModal" hidden role="dialog" aria-labelledby="synthConfirmTitle" aria-modal="true"> | |
| <div class="modal-backdrop modal-backdrop-fixed"></div> | |
| <div class="modal-panel modal-panel-narrow" role="document"> | |
| <div class="disclaimer-body"> | |
| <p class="boot-kicker">§ Verify before synthesis</p> | |
| <h2 id="synthConfirmTitle" class="disclaimer-title"><em>Confirm this order.</em></h2> | |
| <hr class="boot-rule" /> | |
| <div id="synthConfirmDetails" class="disclaimer-detail-block"></div> | |
| <p> | |
| Before you send a sequence for synthesis, take one last | |
| look. Open the variant's detail panel, inspect the DNA | |
| block, and check that the mutations, length, and reading | |
| frame are what you intend to receive in your tube. | |
| </p> | |
| <label class="disclaimer-check"> | |
| <input type="checkbox" id="synthConfirmCheck" /> | |
| <span>I have visually verified this sequence and accept responsibility for the order.</span> | |
| </label> | |
| <div class="disclaimer-actions"> | |
| <button class="ghost-btn" type="button" id="synthConfirmCancel">Cancel</button> | |
| <button class="primary" type="button" id="synthConfirmProceed" disabled> | |
| Proceed · remember for this session | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- auth.js MUST load before app.js (and ideally before any /api/* | |
| call) — it reads the JWT from the URL fragment, stashes it in | |
| sessionStorage, and wraps window.fetch to attach the | |
| Authorization header on every same-origin /api/* call. Without | |
| this, signed-in users would still hit the anonymous-quota | |
| gate. --> | |
| <script src="/static/auth.js?v=20260706-viewdeep"></script> | |
| <!-- Cloning reference data must load before app.js so the Designer | |
| can read VECTORS / ENZYMES / CLONING_METHODS / TAGS / LINKERS. --> | |
| <script src="/static/cloning_db.js?v=20260530-ui-polish" defer></script> | |
| <script src="/static/app.js?v=20260801-review" defer></script> | |
| <!-- The decision trace, BEFORE cockpit.js: applyEvent calls TDTrace.push | |
| on the very first event, and both are `defer`, so document order is | |
| load order. Loading it after would drop the opening events of a | |
| restored run on the floor — silently, since the push is guarded. --> | |
| <script src="/static/trace.js?v=20260801-review" defer></script> | |
| <!-- THE COCKPIT — the persistent orchestrator rail. Loads after app.js so | |
| TDBench/TDStructure exist when a tool result asks the workspace to | |
| render something. This is the only conversation surface in the app. --> | |
| <script src="/static/cockpit.js?v=20260801-review" defer></script> | |
| <!-- structcard before catalog: the catalog calls TDStructCard.observe as | |
| soon as it paints. Both are defer, so document order is load order. --> | |
| <script src="/static/structcard.js?v=20260801-review" defer></script> | |
| <script src="/static/catalog.js?v=20260801-review" defer></script> | |
| <script src="/static/railsplit.js?v=20260801-review" defer></script> | |
| <!-- Dwell-time heartbeat. Loads after auth.js so its /api/ping calls go | |
| through the JWT-attaching fetch wrapper (signed-in attribution). --> | |
| <script src="/static/telemetry.js?v=20260622-analytics" defer></script> | |
| <!-- First-party UX-behaviour collector (dead/rage clicks, scroll depth, | |
| funnel steps, feature clicks, client errors). Structure-only, never | |
| reads sequence content; shares telemetry.js's persisted td_session_id | |
| and POSTs batches to /api/collect via sendBeacon. First-party /static | |
| file so it satisfies the strict script-src CSP. --> | |
| <script src="/static/collector.js?v=20260727-replayfix3" defer></script> | |
| </body> | |
| </html> | |