smiles-drawer
Version:
A SMILES drawer and parser. Generate molecular structure depictions in pure JavaScript.
277 lines (245 loc) • 136 kB
HTML
<!DOCTYPE html><html lang="en"> <head><!-- Google tag (gtag.js) --><script async src="https://www.googletagmanager.com/gtag/js?id=G-DWK15V0MRS"></script><script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-DWK15V0MRS');
</script><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta name="description" content="Interactive SMILES molecule drawing playground with single and batch publication export modes."><link rel="icon" type="image/svg+xml" href="/smilesDrawer/favicon.svg"><link rel="preconnect" href="https://fonts.googleapis.com"><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin><link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"><title>Playground | SmilesDrawer</title><!-- SmilesDrawer library + shared rendering helpers --><script src="/smilesDrawer/js/smiles-drawer.min.js"></script><script src="/smilesDrawer/js/smiles-website.js"></script><link rel="stylesheet" href="/smilesDrawer/_astro/examples.7q3mxKxv.css"></head> <body class="min-h-screen flex flex-col"> <header class="sticky top-0 z-40 bg-paper border-b border-ink"> <div class="max-w-7xl mx-auto flex items-center justify-between px-6 py-4"> <a href="/smilesDrawer" class="flex items-baseline gap-2 no-underline"> <span class="text-[14px] font-extrabold tracking-tight text-ink">SMILESDRAWER</span> <span class="text-[10px] font-medium text-ink-hint uppercase tracking-[0.1em]">v2.2.1</span> </a> <nav class="flex items-center gap-7"> <a href="/smilesDrawer/getting-started" class="btn-ghost "> <span class="text-[9px] text-ink-faint font-semibold">01</span> <span>Docs</span> </a> <a href="/smilesDrawer/playground" class="btn-ghost text-mint-deep"> <span class="text-[9px] text-ink-faint font-semibold">02</span> <span>Playground</span> </a> <a href="/smilesDrawer/examples" class="btn-ghost "> <span class="text-[9px] text-ink-faint font-semibold">03</span> <span>Examples</span> </a> <a href="https://github.com/reymond-group/smilesDrawer" target="_blank" rel="noopener" class="btn-ghost"> <span class="text-[9px] text-ink-faint font-semibold">04</span> <span>GitHub ↗</span> </a> </nav> </div> </header> <main class="flex-1"> <section class="border-b border-ink bg-paper"> <div class="max-w-7xl mx-auto px-6 py-8 flex flex-wrap items-end justify-between gap-6"> <div> <div class="eyebrow mb-2">02 / PLAYGROUND</div> <h1 class="text-[34px] sm:text-[40px] font-extrabold tracking-[-0.025em] text-ink">Render, tweak, export.</h1> </div> <div class="flex border border-ink rounded-md overflow-hidden"> <button id="tab-single" type="button" class="mode-tab px-5 py-2.5 text-[11px] font-bold uppercase tracking-[0.06em] text-ink border-r border-ink data-[active=true]:bg-mint" data-active="true">Single</button> <button id="tab-batch" type="button" class="mode-tab px-5 py-2.5 text-[11px] font-bold uppercase tracking-[0.06em] text-ink data-[active=true]:bg-mint">Batch</button> </div> </div> </section> <div class="max-w-7xl mx-auto"> <!-- =========================================== --> <!-- SINGLE MODE --> <!-- =========================================== --> <div id="panel-single" class="grid lg:grid-cols-[300px_minmax(0,1fr)] min-h-[640px]"> <aside class="border-r border-rule bg-white"> <!-- Preset molecules kept as hidden offscreen elements so the
preserved script's `.preset-btn` click handlers still find
targets. Not shown in the sidebar; users click Import file
(batch) or type their own SMILES. --> <div class="hidden"> <div> <button type="button" class="preset-btn" data-smiles="CO">Methanol</button><button type="button" class="preset-btn" data-smiles="CC">Ethane</button><button type="button" class="preset-btn" data-smiles="CC(=O)O">Acetic acid</button><button type="button" class="preset-btn" data-smiles="O">Water</button> </div><div> <button type="button" class="preset-btn" data-smiles="Oc1ccccc1">Phenol</button><button type="button" class="preset-btn" data-smiles="c1ccc2cc3ccccc3cc2c1">Anthracene</button><button type="button" class="preset-btn" data-smiles="C1CCCCC1">Cyclohexane</button><button type="button" class="preset-btn" data-smiles="C1CC1">Cyclopropane</button> </div><div> <button type="button" class="preset-btn" data-smiles="CC(=O)Oc1ccccc1C(=O)O">Aspirin</button><button type="button" class="preset-btn" data-smiles="CN1C=NC2=C1C(=O)N(C(=O)N2C)C">Caffeine</button><button type="button" class="preset-btn" data-smiles="CC1([C@@H](N2[C@H](S1)[C@@H](C2=O)NC(=O)Cc3ccccc3)C(=O)O)C">Penicillin G</button><button type="button" class="preset-btn" data-smiles="C1COCC(=O)N1C2=CC=C(C=C2)N3C[C@@H](OC3=O)CNC(=O)C4=CC=C(S4)Cl">Rivaroxaban</button> </div><div> <button type="button" class="preset-btn" data-smiles="C[C@H](CCCC(C)C)[C@H]1CC[C@@H]2[C@@]1(CC[C@H]3[C@H]2CC=C4[C@@]3(CC[C@@H](C4)O)C)C">Cholesterol</button><button type="button" class="preset-btn" data-smiles="OC[C@H]1OC(O)[C@H](O)[C@@H](O)[C@@H]1O">Glucose</button><button type="button" class="preset-btn" data-smiles="c1nc(c2c(n1)n(cn2)[C@@H]3[C@@H]([C@@H]([C@H](O3)COP(=O)(O)OP(=O)(O)OP(=O)(O)O)O)O)N">ATP</button> </div><div> <button type="button" class="preset-btn" data-smiles="N[C@@H](C)C(=O)O">L-Alanine</button><button type="button" class="preset-btn" data-smiles="CN1CCC[C@H]1c2cccnc2">Nicotine</button><button type="button" class="preset-btn" data-smiles="O=C1CCC(N2C(=O)c3ccccc3C2=O)C(=O)N1">Thalidomide</button> </div><div> <button type="button" class="preset-btn" data-smiles="CBr.[OH-]>>CO.[Br-]">SN2</button><button type="button" class="preset-btn" data-smiles="CC(=O)O.CO>>CC(=O)OC.O">Esterification</button><button type="button" class="preset-btn" data-smiles="C=CC=C.C=C>>C1CC=CCC1">Diels-Alder</button><button type="button" class="preset-btn" data-smiles="CC(=O)O.NCC>>CC(=O)NCC.O">Amide formation</button> </div> </div> <details id="pg-theme" class="acc-group border-b border-rule"> <summary class="acc-row"> <span class="acc-num">001</span> <span class="acc-title">THEME</span> <span class="acc-hint">Render palette</span> <span class="acc-chev">›</span> </summary> <div class="acc-body"> <select id="opt-theme" class="w-full text-[12px] font-mono border border-rule rounded-sm px-2 py-1.5 bg-white"> <option value="light">light</option><option value="dark">dark</option><option value="oldschool">oldschool</option><option value="solarized">solarized</option><option value="solarized-dark">solarized-dark</option><option value="matrix">matrix</option><option value="github">github</option><option value="carbon">carbon</option><option value="cyberpunk">cyberpunk</option><option value="gruvbox">gruvbox</option><option value="gruvbox-dark">gruvbox-dark</option> </select> </div> </details> <details id="pg-sizing" class="acc-group border-b border-rule" open> <summary class="acc-row"> <span class="acc-num">002</span> <span class="acc-title">SIZING</span> <span class="acc-hint">Bond · font size</span> <span class="acc-chev">›</span> </summary> <div class="acc-body space-y-3"> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Bond length</span><span id="opt-bondLength-val" class="readout">19</span></label> <input id="opt-bondLength" type="range" class="rail" min="5" max="30" value="19" step="1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Bond thickness</span><span id="opt-bondThickness-val" class="readout">1.1</span></label> <input id="opt-bondThickness" type="range" class="rail" min="0.1" max="5" value="1.1" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Font size</span><span id="opt-fontSizeLarge-val" class="readout">6.3</span></label> <input id="opt-fontSizeLarge" type="range" class="rail" min="1" max="20" value="6.3" step="0.1"> </div> </div> </details> <details id="pg-canvas" class="acc-group border-b border-rule"> <summary class="acc-row"> <span class="acc-num">003</span> <span class="acc-title">CANVAS</span> <span class="acc-hint">Width · height · spacing</span> <span class="acc-chev">›</span> </summary> <div class="acc-body space-y-3"> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Width</span><span id="opt-width-val" class="readout">550</span></label> <input id="opt-width" type="range" class="rail" min="100" max="1000" value="550" step="50"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Height</span><span id="opt-height-val" class="readout">450</span></label> <input id="opt-height" type="range" class="rail" min="100" max="1000" value="450" step="50"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Short bond length</span><span id="opt-shortBondLength-val" class="readout">0.6</span></label> <input id="opt-shortBondLength" type="range" class="rail" min="0.1" max="1" value="0.6" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Bond spacing</span><span id="opt-bondSpacing-val" class="readout">3.2</span></label> <input id="opt-bondSpacing" type="range" class="rail" min="0" max="10" value="3.2" step="0.1"> </div> </div> </details> <details id="pg-display" class="acc-group border-b border-rule"> <summary class="acc-row"> <span class="acc-num">004</span> <span class="acc-title">DISPLAY</span> <span class="acc-hint">Atoms · hydrogens · debug</span> <span class="acc-chev">›</span> </summary> <div class="acc-body space-y-2"> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Terminal carbons</span> <span class="relative inline-flex"><input id="opt-terminalCarbons" type="checkbox" class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Explicit hydrogens</span> <span class="relative inline-flex"><input id="opt-explicitHydrogens" type="checkbox" class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Compact drawing</span> <span class="relative inline-flex"><input id="opt-compactDrawing" type="checkbox" checked class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Debug mode</span> <span class="relative inline-flex"><input id="opt-debug" type="checkbox" class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> </div> </details> <details id="pg-atoms" class="acc-group border-b border-rule"> <summary class="acc-row"> <span class="acc-num">005</span> <span class="acc-title">ATOMS</span> <span class="acc-hint">Atom style</span> <span class="acc-chev">›</span> </summary> <div class="acc-body"> <label class="block text-[12px] font-medium text-ink-muted mb-1">Atom style</label> <select id="opt-atomVisualization" class="w-full text-[12px] font-mono border border-rule rounded-sm px-2 py-1.5 bg-white"> <option value="default">Default</option> <option value="balls">Balls</option> <option value="allballs">All balls</option> </select> </div> </details> <details id="pg-reaction" class="acc-group"> <summary class="acc-row"> <span class="acc-num">006</span> <span class="acc-title">REACTION</span> <span class="acc-hint">Reaction SMILES options</span> <span class="acc-chev">›</span> </summary> <div class="acc-body space-y-3"> <p class="text-[10px] text-ink-hint">Used when the input is a reaction SMILES string.</p> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Scale</span><span id="opt-scale-val" class="readout">0</span></label> <input id="opt-scale" type="range" class="rail" min="0" max="2" value="0" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Spacing</span><span id="opt-rxnSpacing-val" class="readout">10</span></label> <input id="opt-rxnSpacing" type="range" class="rail" min="0" max="20" value="10" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Font size</span><span id="opt-rxnFontSize-val" class="readout">9</span></label> <input id="opt-rxnFontSize" type="range" class="rail" min="0" max="20" value="9" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Plus size</span><span id="opt-rxnPlusSize-val" class="readout">9</span></label> <input id="opt-rxnPlusSize" type="range" class="rail" min="0" max="20" value="9" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Plus thickness</span><span id="opt-rxnPlusThickness-val" class="readout">1</span></label> <input id="opt-rxnPlusThickness" type="range" class="rail" min="0" max="5" value="1" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Arrow length</span><span id="opt-rxnArrowLength-val" class="readout">120</span></label> <input id="opt-rxnArrowLength" type="range" class="rail" min="0" max="500" value="120" step="1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Arrow thickness</span><span id="opt-rxnArrowThickness-val" class="readout">1</span></label> <input id="opt-rxnArrowThickness" type="range" class="rail" min="0.1" max="10" value="1" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Arrow head size</span><span id="opt-rxnArrowHeadSize-val" class="readout">6</span></label> <input id="opt-rxnArrowHeadSize" type="range" class="rail" min="1" max="10" value="6" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Arrow margin</span><span id="opt-rxnArrowMargin-val" class="readout">3</span></label> <input id="opt-rxnArrowMargin" type="range" class="rail" min="0" max="10" value="3" step="0.1"> </div> </div> </details> </aside> <!-- SINGLE MAIN PANE --> <div class="flex flex-col bg-white"> <div class="group flex items-stretch border-b border-rule focus-within:ring-2 focus-within:ring-mint focus-within:ring-inset"> <span class="flex items-center px-4 border-r border-rule bg-paper text-[10px] font-bold uppercase tracking-[0.14em] text-ink">SMILES</span> <input id="pg-input" type="text" value="CN1C=NC2=C1C(=O)N(C(=O)N2C)C" placeholder="Type a SMILES string…" spellcheck="false" class="flex-1 min-w-0 px-4 py-3 font-mono text-[13px] text-ink bg-white outline-none placeholder:text-ink-hint group-hover:bg-mint-tint/40 focus:bg-white transition-colors"> <span class="flex items-center px-3 text-ink-hint group-hover:text-mint-deep transition-colors pointer-events-none" aria-hidden="true"> <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931z"></path></svg> </span> <button id="pg-draw-btn" type="button" class="px-5 py-3 bg-mint text-ink text-[11px] font-bold uppercase tracking-[0.06em] border-l border-ink hover:bg-mint-deep hover:text-white">DRAW</button> </div> <div id="pg-error" class="hidden px-4 py-2 text-[11px] text-red-600 bg-red-50 border-b border-red-200"></div> <div class="flex items-stretch border-b border-rule bg-white"> <span class="flex items-center px-4 border-r border-rule bg-paper text-[10px] font-bold uppercase tracking-[0.14em] text-ink-hint">EXPORT</span> <button id="pg-copy-smiles" type="button" class="px-4 py-2.5 text-[11px] font-semibold text-ink border-r border-rule hover:bg-mint-tint"><span class="text-mint-deep mr-1">⎘</span> Copy SMILES</button> <button id="pg-copy-svg" type="button" class="px-4 py-2.5 text-[11px] font-semibold text-ink border-r border-rule hover:bg-mint-tint"><span class="text-mint-deep mr-1">⎘</span> Copy SVG</button> <button id="pg-download-svg" type="button" class="px-4 py-2.5 text-[11px] font-semibold text-ink border-r border-rule hover:bg-mint-tint"><span class="text-mint-deep mr-1">↓</span> Download SVG</button> <button id="pg-download-png" type="button" class="px-4 py-2.5 text-[11px] font-semibold text-ink border-r border-rule hover:bg-mint-tint"><span class="text-mint-deep mr-1">↓</span> Download PNG</button> <button id="pg-share" type="button" class="px-4 py-2.5 text-[11px] font-semibold text-ink border-r border-rule hover:bg-mint-tint"><span class="text-mint-deep mr-1">↗</span> Share</button> <div class="flex-1"></div> </div> <div id="pg-preview-surface" class="grid-paper flex-1 flex items-center justify-center p-6 min-h-[480px]" data-theme-surface="true"> <div id="pg-svg-wrap" class="flex items-center justify-center w-full"> <svg id="pg-svg" class="w-full" style="max-width: 600px; max-height: 460px;"></svg> </div> </div> <div id="pg-formula" class="hidden border-t border-rule px-4 py-2 text-[12px] text-ink bg-paper"> <span class="font-semibold text-ink">Formula:</span> <span id="pg-formula-text" class="font-mono text-ink"></span> </div> </div> </div> <!-- =========================================== --> <!-- BATCH MODE --> <!-- =========================================== --> <div id="panel-batch" class="hidden grid grid-cols-1 lg:grid-cols-[300px_minmax(0,1fr)] min-h-[840px]"> <aside class="border-r border-rule bg-white"> <details class="acc-group border-b border-rule" open> <summary class="acc-row"> <span class="acc-num">001</span> <span class="acc-title">GRID</span> <span class="acc-hint">Columns · rows · cell size</span> <span class="acc-chev">›</span> </summary> <div class="acc-body space-y-3"> <div class="flex flex-wrap gap-1.5"> <button type="button" class="batch-grid-preset text-[10px] font-semibold px-2 py-1 border border-rule rounded-sm hover:border-mint hover:bg-mint-tint" data-cols="2" data-rows="2">2×2</button> <button type="button" class="batch-grid-preset text-[10px] font-semibold px-2 py-1 border border-rule rounded-sm hover:border-mint hover:bg-mint-tint" data-cols="3" data-rows="3">3×3</button> <button type="button" class="batch-grid-preset text-[10px] font-semibold px-2 py-1 border border-rule rounded-sm hover:border-mint hover:bg-mint-tint" data-cols="3" data-rows="4">3×4</button> <button type="button" class="batch-grid-preset text-[10px] font-semibold px-2 py-1 border border-rule rounded-sm hover:border-mint hover:bg-mint-tint" data-cols="4" data-rows="4">4×4</button> <button type="button" class="batch-grid-preset text-[10px] font-semibold px-2 py-1 border border-rule rounded-sm hover:border-mint hover:bg-mint-tint" data-cols="5" data-rows="5">5×5</button> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Columns</span><span id="batch-opt-cols-val" class="readout">3</span></label> <input id="batch-opt-cols" type="range" class="rail" min="1" max="8" value="3" step="1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Rows</span><span id="batch-opt-rows-val" class="readout">3</span></label> <input id="batch-opt-rows" type="range" class="rail" min="1" max="10" value="3" step="1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Cell size</span><span id="batch-opt-cellSize-val" class="readout">250</span></label> <input id="batch-opt-cellSize" type="range" class="rail" min="100" max="500" value="250" step="10"> </div> </div> </details> <details class="acc-group border-b border-rule"> <summary class="acc-row"> <span class="acc-num">002</span> <span class="acc-title">DISPLAY</span> <span class="acc-hint">Labels · formula · MW</span> <span class="acc-chev">›</span> </summary> <div class="acc-body space-y-2"> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Show labels</span> <span class="relative inline-flex"><input id="batch-opt-showLabels" type="checkbox" checked class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Show formula</span> <span class="relative inline-flex"><input id="batch-opt-showFormula" type="checkbox" class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Show molecular weight</span> <span class="relative inline-flex"><input id="batch-opt-showMW" type="checkbox" class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> </div> </details> <details class="acc-group border-b border-rule"> <summary class="acc-row"> <span class="acc-num">003</span> <span class="acc-title">THEME</span> <span class="acc-hint">Render palette</span> <span class="acc-chev">›</span> </summary> <div class="acc-body"> <select id="batch-opt-theme" class="w-full text-[12px] font-mono border border-rule rounded-sm px-2 py-1.5 bg-white"> <option value="light">light</option><option value="dark">dark</option><option value="oldschool">oldschool</option><option value="solarized">solarized</option><option value="solarized-dark">solarized-dark</option><option value="matrix">matrix</option><option value="github">github</option><option value="carbon">carbon</option><option value="cyberpunk">cyberpunk</option><option value="gruvbox">gruvbox</option><option value="gruvbox-dark">gruvbox-dark</option> </select> </div> </details> <!-- PUBLICATION EXPORT: same visual treatment as the other accordions --> <details class="acc-group border-b border-rule"> <summary class="acc-row"> <span class="acc-num">004</span> <span class="acc-title">PUBLICATION EXPORT</span> <span class="acc-hint">Uniform · B&W · DPI · figure title</span> <span class="acc-chev">›</span> </summary> <div class="acc-body"> <label class="flex items-start justify-between gap-3 mb-3"> <div> <div class="text-[11px] font-semibold text-ink">Publication style</div> <div class="text-[9px] text-ink-subtle mt-0.5">Uniform bond lengths, black atoms/bonds</div> </div> <span class="relative inline-flex mt-0.5"><input id="batch-opt-pubStyle" type="checkbox" class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> <div class="mb-3"> <label class="block text-[12px] font-medium text-ink-muted mb-1">DPI / Resolution</label> <div class="grid grid-cols-4 gap-1"> <button type="button" class="batch-scale-preset text-[10px] font-mono font-semibold py-1.5 bg-white border border-rule rounded-sm hover:border-mint hover:bg-mint-tint" data-scale="1">72</button> <button type="button" class="batch-scale-preset text-[10px] font-mono font-semibold py-1.5 bg-white border border-rule rounded-sm hover:border-mint hover:bg-mint-tint" data-scale="2">144</button> <button type="button" class="batch-scale-preset text-[10px] font-mono font-semibold py-1.5 bg-mint border-mint border rounded-sm" data-scale="4.1667">300</button> <button type="button" class="batch-scale-preset text-[10px] font-mono font-semibold py-1.5 bg-white border border-rule rounded-sm hover:border-mint hover:bg-mint-tint" data-scale="8.3333">600</button> </div> </div> <div class="mb-3"> <label class="block text-[12px] font-medium text-ink-muted mb-1">Background</label> <div class="flex gap-3 text-[10px]"> <label class="flex items-center gap-1"><input name="batch-opt-bg" id="batch-opt-bg-theme" type="radio" value="theme" checked> Theme</label> <label class="flex items-center gap-1"><input name="batch-opt-bg" id="batch-opt-bg-white" type="radio" value="white"> White</label> <label class="flex items-center gap-1"><input name="batch-opt-bg" id="batch-opt-bg-transparent" type="radio" value="transparent"> Transparent</label> </div> </div> <div class="mb-3"> <label class="block text-[12px] font-medium text-ink-muted mb-1">Figure title</label> <input id="batch-opt-title" type="text" placeholder="e.g. Figure 1" class="w-full text-[11px] border border-rule rounded-sm px-2 py-1.5 bg-white"> </div> <div class="mb-3"> <label class="block text-[12px] font-medium text-ink-muted mb-1">Atom style</label> <select id="batch-opt-atomVisualization" class="w-full text-[11px] font-mono border border-rule rounded-sm px-2 py-1.5 bg-white"> <option value="default">Default</option> <option value="balls">Balls</option> <option value="allballs">All balls</option> </select> </div> <div class="space-y-2 mb-3"> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Bond length</span><span id="batch-opt-bondLength-val" class="readout">19</span></label> <input id="batch-opt-bondLength" type="range" class="rail" min="5" max="30" value="19" step="1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Bond thickness</span><span id="batch-opt-bondThickness-val" class="readout">1.1</span></label> <input id="batch-opt-bondThickness" type="range" class="rail" min="0.1" max="5" value="1.1" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Short bond length</span><span id="batch-opt-shortBondLength-val" class="readout">0.6</span></label> <input id="batch-opt-shortBondLength" type="range" class="rail" min="0.1" max="1" value="0.6" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Bond spacing</span><span id="batch-opt-bondSpacing-val" class="readout">3.2</span></label> <input id="batch-opt-bondSpacing" type="range" class="rail" min="0" max="10" value="3.2" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Font size</span><span id="batch-opt-fontSizeLarge-val" class="readout">6.3</span></label> <input id="batch-opt-fontSizeLarge" type="range" class="rail" min="1" max="20" value="6.3" step="0.1"> </div> </div> <div class="space-y-1.5 mb-3"> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Compact drawing</span> <span class="relative inline-flex"><input id="batch-opt-compactDrawing" type="checkbox" checked class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Terminal carbons</span> <span class="relative inline-flex"><input id="batch-opt-terminalCarbons" type="checkbox" class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> <label class="flex items-center justify-between text-[12px] text-ink-muted"> <span>Explicit hydrogens</span> <span class="relative inline-flex"><input id="batch-opt-explicitHydrogens" type="checkbox" class="sr-only peer"><span class="toggle-pill peer-checked:bg-mint"></span></span> </label> </div> </div> </details> <details class="acc-group border-t border-rule"> <summary class="acc-row"> <span class="acc-num">005</span> <span class="acc-title">REACTION</span> <span class="acc-hint">rxn overrides</span> <span class="acc-chev">›</span> </summary> <div class="acc-body space-y-3"> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Scale</span><span id="batch-opt-scale-val" class="readout">0</span></label> <input id="batch-opt-scale" type="range" class="rail" min="0" max="2" value="0" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Spacing</span><span id="batch-opt-rxnSpacing-val" class="readout">10</span></label> <input id="batch-opt-rxnSpacing" type="range" class="rail" min="0" max="20" value="10" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Font size</span><span id="batch-opt-rxnFontSize-val" class="readout">9</span></label> <input id="batch-opt-rxnFontSize" type="range" class="rail" min="0" max="20" value="9" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Plus size</span><span id="batch-opt-rxnPlusSize-val" class="readout">9</span></label> <input id="batch-opt-rxnPlusSize" type="range" class="rail" min="0" max="20" value="9" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Plus thickness</span><span id="batch-opt-rxnPlusThickness-val" class="readout">1</span></label> <input id="batch-opt-rxnPlusThickness" type="range" class="rail" min="0" max="5" value="1" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Arrow length</span><span id="batch-opt-rxnArrowLength-val" class="readout">120</span></label> <input id="batch-opt-rxnArrowLength" type="range" class="rail" min="0" max="500" value="120" step="1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Arrow thickness</span><span id="batch-opt-rxnArrowThickness-val" class="readout">1</span></label> <input id="batch-opt-rxnArrowThickness" type="range" class="rail" min="0.1" max="10" value="1" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Arrow head size</span><span id="batch-opt-rxnArrowHeadSize-val" class="readout">6</span></label> <input id="batch-opt-rxnArrowHeadSize" type="range" class="rail" min="1" max="10" value="6" step="0.1"> </div> <div> <label class="flex justify-between text-[12px] font-medium text-ink-muted mb-1"><span>Arrow margin</span><span id="batch-opt-rxnArrowMargin-val" class="readout">3</span></label> <input id="batch-opt-rxnArrowMargin" type="range" class="rail" min="0" max="10" value="3" step="0.1"> </div> </div> </details> </aside> <!-- BATCH MAIN PANE --> <div class="flex flex-col bg-white"> <div class="border-b border-rule"> <div class="flex items-center justify-between px-4 py-2 border-b border-rule bg-paper"> <span class="text-[10px] font-bold uppercase tracking-[0.14em] text-ink">SMILES · ONE PER LINE</span> <div class="flex items-center gap-3"> <span id="batch-count" class="text-[10px] text-ink-hint">0 molecules</span> <input type="file" id="batch-file-input" accept=".smi,.txt,.csv,.tsv" class="hidden"> <button id="batch-import-btn" type="button" class="text-[10px] font-semibold px-3 py-1 border border-mint-deep text-mint-deep rounded-sm hover:bg-mint-tint" title="Accepts .smi, .txt, .csv, .tsv, one molecule per row">Import file</button> </div> </div> <div class="px-4 py-1.5 border-b border-rule bg-paper text-[10px] text-ink-hint tracking-wide"> <span class="font-mono">⌘/Ctrl+Click</span> to select · <span class="font-mono">Shift+Click</span> for range
</div> <textarea id="batch-input" rows="6" spellcheck="false" class="w-full px-4 py-3 font-mono text-[12px] text-ink bg-transparent outline-none resize-y">CN1C=NC2=C1C(=O)N(C(=O)N2C)C
CC(=O)Oc1ccccc1C(=O)O
c1ccc2[nH]ccc2c1</textarea> <div id="batch-error" class="hidden px-4 py-2 text-[11px] text-red-600 bg-red-50 border-t border-red-200"></div> <div class="flex items-center justify-between gap-3 px-4 py-2 border-t border-rule bg-paper"> <button id="batch-render-btn" type="button" class="btn-primary">Render All →</button> <div class="flex items-center gap-2"> <button id="pg-batch-share" type="button" class="text-[11px] font-bold uppercase tracking-[0.05em] px-3 py-2 bg-white border border-ink text-ink rounded-sm hover:bg-paper"><span class="text-mint-deep mr-1">↗</span> Share</button> <button id="batch-export-svg" type="button" class="text-[11px] font-bold uppercase tracking-[0.05em] px-3 py-2 bg-white border border-ink text-ink rounded-sm hover:bg-paper disabled:opacity-40 disabled:cursor-not-allowed" disabled>Export All SVG</button> <button id="batch-export-png" type="button" class="text-[11px] font-bold uppercase tracking-[0.05em] px-3 py-2 bg-mint border border-mint text-ink rounded-sm hover:bg-mint-deep hover:text-white hover:border-mint-deep disabled:opacity-40 disabled:cursor-not-allowed" disabled>Export All PNG</button> </div> </div> </div> <div id="batch-selection-bar" class="hidden items-center flex-wrap gap-2 px-4 py-2 border-b border-rule bg-mint-tint"> <span id="batch-selection-count" class="text-[11px] font-semibold text-mint-deep mr-1">0 selected</span> <button id="batch-select-all" type="button" class="text-[10px] font-semibold px-2 py-0.5 border border-mint-deep text-mint-deep rounded-sm hover:bg-mint">Select all</button> <button id="batch-deselect-all" type="button" class="text-[10px] font-semibold px-2 py-0.5 border border-mint-deep text-mint-deep rounded-sm hover:bg-mint">Deselect all</button> <span class="mx-1 h-4 w-px bg-mint-deep/30"></span> <button id="batch-copy-selected" type="button" class="text-[10px] font-semibold px-2 py-0.5 border border-mint-deep text-mint-deep rounded-sm hover:bg-mint">Copy SMILES</button> <button id="batch-export-selected-svg" type="button" class="text-[10px] font-semibold px-2 py-0.5 border border-mint-deep text-mint-deep rounded-sm hover:bg-mint">Export SVG</button> <button id="batch-export-selected-png" type="button" class="text-[10px] font-semibold px-2 py-0.5 border border-mint-deep bg-mint-deep text-white rounded-sm hover:bg-mint-deep/80">Export PNG</button> <span class="mx-1 h-4 w-px bg-mint-deep/30"></span> <button id="batch-delete-selected" type="button" class="text-[10px] font-semibold px-2 py-0.5 border border-red-500 text-red-600 rounded-sm hover:bg-red-50">Delete selected</button> </div> <div id="batch-preview-surface" class="grid-paper flex-1 p-6 min-h-[480px]" data-theme-surface="true"> <div id="batch-grid" class="grid gap-2" style="grid-template-columns: repeat(3, 1fr);"> <div class="col-span-full py-16 text-center text-[12px] text-ink-hint">
Enter SMILES above and click Render All.
</div> </div> </div> <div id="batch-pagination" class="hidden items-center justify-center gap-3 border-t border-rule py-3 bg-paper"> <button id="batch-page-prev" type="button" class="text-[11px] font-semibold px-3 py-1 border border-rule rounded-sm hover:bg-white disabled:opacity-40" disabled>‹ Prev</button> <span id="batch-page-info" class="text-[12px] text-ink-muted">Page 1 of 1</span> <button id="batch-page-next" type="button" class="text-[11px] font-semibold px-3 py-1 border border-rule rounded-sm hover:bg-white disabled:opacity-40" disabled>Next ›</button> </div> </div> </div> </div> </main> <footer class="border-t border-rule bg-paper"> <div class="max-w-7xl mx-auto flex flex-col sm:flex-row justify-between gap-3 px-6 py-6 text-[11px] text-ink-hint tracking-wide"> <span> <strong class="text-ink font-semibold">SMILESDRAWER</strong> <span class="mx-2 text-ink-faint">·</span>
MIT
<span class="mx-2 text-ink-faint">·</span>
Pure JavaScript
</span> <span class="flex items-center gap-4"> <a href="https://doi.org/10.1021/acs.jcim.7b00425" target="_blank" rel="noopener" class="text-mint-deep font-semibold no-underline hover:underline">CITE ↗</a> <a href="https://github.com/reymond-group/smilesDrawer" target="_blank" rel="noopener" class="text-mint-deep font-semibold no-underline hover:underline">GITHUB ↗</a> <a href="https://www.npmjs.com/package/smiles-drawer" target="_blank" rel="noopener" class="text-mint-deep font-semibold no-underline hover:underline">NPM ↗</a> <a href="https://github.com/reymond-group/smilesDrawer/issues" target="_blank" rel="noopener" class="text-mint-deep font-semibold no-underline hover:underline">REPORT ISSUE ↗</a> </span> </div> </footer> </body></html> <script>(function(){const themePresets = {"light":{"C":"#222222","O":"#e74c3c","N":"#3498db","F":"#27ae60","CL":"#16a085","BR":"#d35400","I":"#8e44ad","P":"#d35400","S":"#f1c40f","B":"#e67e22","SI":"#e67e22","H":"#666666","BACKGROUND":"#ffffff"},"dark":{"C":"#ffffff","O":"#e74c3c","N":"#3498db","F":"#27ae60","CL":"#16a085","BR":"#d35400","I":"#8e44ad","P":"#d35400","S":"#f1c40f","B":"#e67e22","SI":"#e67e22","H":"#aaaaaa","BACKGROUND":"#141414"},"oldschool":{"C":"#000000","O":"#000000","N":"#000000","F":"#000000","CL":"#000000","BR":"#000000","I":"#000000","P":"#000000","S":"#000000","B":"#000000","SI":"#000000","H":"#000000","BACKGROUND":"#ffffff"},"solarized":{"C":"#586e75","O":"#dc322f","N":"#268bd2","F":"#859900","CL":"#16a085","BR":"#cb4b16","I":"#6c71c4","P":"#d33682","S":"#b58900","B":"#2aa198","SI":"#2aa198","H":"#657b83","BACKGROUND":"#fdf6e3"},"solarized-dark":{"C":"#93a1a1","O":"#dc322f","N":"#268bd2","F":"#859900","CL":"#16a085","BR":"#cb4b16","I":"#6c71c4","P":"#d33682","S":"#b58900","B":"#2aa198","SI":"#2aa198","H":"#839496","BACKGROUND":"#002b36"},"matrix":{"C":"#678c61","O":"#2fc079","N":"#4f7e7e","F":"#90d762","CL":"#82d967","BR":"#23755a","I":"#409931","P":"#c1ff8a","S":"#faff00","B":"#50b45a","SI":"#409931","H":"#426644","BACKGROUND":"#0f120f"},"github":{"C":"#24292f","O":"#cf222e","N":"#0969da","F":"#2da44e","CL":"#6fdd8b","BR":"#bc4c00","I":"#8250df","P":"#bf3989","S":"#d4a72c","B":"#fb8f44","SI":"#bc4c00","H":"#57606a","BACKGROUND":"#ffffff"},"carbon":{"C":"#161616","O":"#da1e28","N":"#0f62fe","F":"#198038","CL":"#007d79","BR":"#fa4d56","I":"#8a3ffc","P":"#ff832b","S":"#f1c21b","B":"#8a3800","SI":"#e67e22","H":"#525252","BACKGROUND":"#f4f4f4"},"cyberpunk":{"C":"#ea00d9","O":"#ff3131","N":"#0abdc6","F":"#00ff9f","CL":"#00fe00","BR":"#fe9f20","I":"#ff00ff","P":"#fe7f00","S":"#fcee0c","B":"#ff00ff","SI":"#ffffff","H":"#913cb1","BACKGROUND":"#111827"},"gruvbox":{"C":"#665c54","O":"#cc241d","N":"#458588","F":"#98971a","CL":"#79740e","BR":"#d65d0e","I":"#b16286","P":"#af3a03","S":"#d79921","B":"#689d6a","SI":"#427b58","H":"#7c6f64","BACKGROUND":"#fbf1c7"},"gruvbox-dark":{"C":"#ebdbb2","O":"#cc241d","N":"#458588","F":"#98971a","CL":"#b8bb26","BR":"#d65d0e","I":"#b16286","P":"#fe8019","S":"#d79921","B":"#8ec07c","SI":"#83a598","H":"#bdae93","BACKGROUND":"#282828"}};
const themeSurfaceModes = {"light":"light","dark":"dark","oldschool":"light","solarized":"light","solarized-dark":"dark","matrix":"dark","github":"light","carbon":"light","cyberpunk":"dark","gruvbox":"light","gruvbox-dark":"dark"};
const renderOverrides = {"C=CCBr.[Na+].[I-]>CC(=O)C>C=CCI.[Na+].[Br-] __{'textAboveArrow': 'acetone', 'textBelowArrow': '90%'}__":{"bondLength":16.8,"fontSizeLarge":5.4,"padding":20},"CC1([C@@H](N2[C@H](S1)[C@@H](C2=O)NC(=O)Cc3ccccc3)C(=O)O)C":{"bondLength":15.4,"fontSizeLarge":5.2,"padding":8},"C[C@H](CCCC(C)C)[C@H]1CC[C@@H]2[C@@]1(CC[C@H]3[C@H]2CC=C4[C@@]3(CC[C@@H](C4)O)C)C":{"bondLength":16.2,"fontSizeLarge":5.4,"padding":8},"OC[C@H]1OC(O)[C@H](O)[C@@H](O)[C@@H]1O":{"bondLength":17.8,"fontSizeLarge":5.6,"padding":8},"C[C@]12CC[C@H]3[C@@H](CCC4=CC(=O)CC[C@@]34C)[C@@H]1CC[C@@H]2O":{"bondLength":16,"fontSizeLarge":5.3,"padding":8},"c1nc(c2c(n1)n(cn2)[C@@H]3[C@@H]([C@@H]([C@H](O3)COP(=O)(O)OP(=O)(O)OP(=O)(O)O)O)O)N":{"bondLength":13,"fontSizeLarge":4.9,"padding":8}};
(function() {
document.addEventListener('DOMContentLoaded', function() {
var site = window.SmilesWebsite;
var input = document.getElementById('pg-input');
var svg = document.getElementById('pg-svg');
var errorEl = document.getElementById('pg-error');
var drawBtn = document.getElementById('pg-draw-btn');
var previewSurface = document.getElementById('pg-preview-surface');
// Check if a SMILES string is a reaction (contains > outside brackets)
function isReaction(smiles) {
return site.isReaction(smiles);
}
// Option elements
var sliders = ['bondLength', 'bondThickness', 'shortBondLength', 'bondSpacing', 'fontSizeLarge', 'width', 'height'];
var checkboxes = ['terminalCarbons', 'explicitHydrogens', 'compactDrawing', 'debug'];
var selects = ['atomVisualization'];
// 'scale' lives with reaction sliders; it only behaves as expected when rendering reactions.
var reactionSliders = ['scale', 'rxnSpacing', 'rxnFontSize', 'rxnPlusSize', 'rxnPlusThickness', 'rxnArrowLength', 'rxnArrowThickness', 'rxnArrowHeadSize', 'rxnArrowMargin'];
function getDefaultTheme() {
return site.getPageTheme();
}
function applySurfaceTheme(surface, themeName) {
if (!surface) return;
var preset = themePresets[themeName] || themePresets.light;
var isDarkSurface = themeSurfaceModes[themeName] === 'dark';
surface.style.backgroundColor = preset.BACKGROUND || '';
surface.style.backgroundImage = isDarkSurface ? 'linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0))' : 'none';
}
function getOptions() {
var opts = {
themes: themePresets
};
sliders.forEach(function(name) {
var el = document.getElementById('opt-' + name);
if (el) opts[name] = parseFloat(el.value);
});
selects.forEach(function(name) {
var el = document.getElementById('opt-' + name);
if (el) opts[name] = el.value;
});
checkboxes.forEach(function(name) {
var el = document.getElementById('opt-' + name);
if (el) opts[name] = el.checked;
});
if (!opts.width) opts.width = 550;
if (!opts.height) opts.height = 450;
return opts;
}
function getTheme() {
var el = document.getElementById('opt-theme');
return el ? el.value : 'light';
}
function getReactionOptions() {
return {
spacing: parseFloat(document.getElementById('opt-rxnSpacing').value),
fontSize: parseFloat(document.getElementById('opt-rxnFontSize').value),
plus: {
size: parseFloat(document.getElementById('opt-rxnPlusSize').value),
thickness: parseFloat(document.getElementById('opt-rxnPlusThickness').value)
},
arrow: {
length: parseFloat(document.getElementById('opt-rxnArrowLength').value),
thickness: parseFloat(document.getElementById('opt-rxnArrowThickness').value),
headSize: parseFloat(document.getElementById('opt-rxnArrowHeadSize').value),
margin: parseFloat(document.getElementById('opt-rxnArrowMargin').value)
}
};
}
var svgWrap = document.getElementById('pg-svg-wrap');
function getPreviewWidth() {
if (!svgWrap) return 0;
return svgWrap.clientWidth || 0;
}
function applyPreviewSize(mode, opts) {
var width = opts && opts.width ? opts.width : 550;
var height = opts && opts.height ? opts.height : 450;
var previewWidth = getPreviewWidth();
svg.classList.add('w-full');
svg.style.width = '100%';
svg.style.height = 'auto';
svg.style.display = 'block';
svg.setAttribute('preserveAspectRatio', 'xMidYMid meet');
if (mode === 'reaction') {
svg.style.maxWidth = '100%';
svg.style.maxHeight = Math.max(height, 360) + 'px';
} else {
if (previewWidth > 0) {
svg.style.maxWidth = Math.min(width, previewWidth) + 'px';
} else {
svg.style.maxWidth = width + 'px';
}
svg.style.maxHeight = height + 'px';
}
}
function getReactionMoleculeOptions(opts) {
var rxnOpts = {};
var baseWidth = opts && opts.width ? opts.width : 550;
var baseHeight = opts && opts.height ? opts.height : 450;
var wrapWidth = getPreviewWidth();
var k;
for (k in opts) rxnOpts[k] = opts[k];
if (wrapWidth > 0) {
rxnOpts.width = Math.max(baseWidth, Math.floor(wrapWidth - 16));
} else {
rxnOpts.width = baseWidth;
}
rxnOpts.height = Math.max(baseHeight, 360);
rxnOpts.bondLength = Math.max(opts.bondLength || 19, 19);
var scaleEl = document.getElementById('opt-scale');
if (scaleEl) rxnOpts.scale = parseFloat(scaleEl.value);
return rxnOpts;
}
// Fully reset the SVG element so no stale attributes from SmiDrawer persist
function resetSvg() {
var fresh = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
fresh.id = 'pg-svg';
svgWrap.replaceChild(fresh, svg);
svg = fresh;
}
function draw() {
var smiles = input.value.trim();
var theme = getTheme();
applySurfaceTheme(previewSurface, theme);
if (!smiles) {
resetSvg();
applyPreviewSize('molecule', {width: 550, height: 450});
errorEl.classList.add('hidden');
return;
}
var opts = getOptions();
var reactionOpts = getReactionOptions();
var useReaction = isReaction(smiles);
try {
svg = site.renderSmiles({
adaptive: false,
baseOptions: useReaction ? getReactionMoleculeOptions(opts) : opts,
layout: {
containerWidth: getPreviewWidth(),
minHeight: useReaction ? 400 : 0
},
onError: function(err, kind, nextSvg) {
svg = nextSvg;
errorEl.textContent = (kind === 'reaction' ? 'Invalid reaction SMILES: ' : 'Invalid SMILES: ') + (err && err.message ? err.message : err);
errorEl.classList.remove('hidden');
},
onSuccess: function(result) {
svg = result.svg;
applyPreviewSize(result.kind, result.options);
errorEl.classList.add('hidden');
},
reactionOptions: reactionOpts,
renderConfig: renderOverrides[smiles],
smiles: smiles,
svg: svg,
svgId: 'pg-svg',
themeName: theme
}).svg;
} catch (e) {
errorEl.textContent = 'Error: ' + e.message;
errorEl.classList.remove('hidden');
}
// Don't clobber the URL if batch owns it. Two cases to detect:
// (1) User has switched to batch — panel-batch is visible.
// (2) Initial load with ?mode=batch but the tab-switch script
// hasn't run yet (single's draw fires first because the
// input has a hardcoded default value, so panel-batch still
// has the `hidden` class at this point — the URL is the
// authoritative signal here, not the DOM.
var batchPanel = document.getElementById('panel-batch');
var urlMode = new URLSearchParams(window.location.search).get('mode');
var batchActive = urlMode === 'batch' || (batchPanel && !batchPanel.classList.contains('hidden'));
if (!batchActive) {
var url = new URL(window.location);
url.searchParams.set('smiles', smiles);
try {
url.searchParams.set('cfg', btoa(JSON.stringify(collectState())));
} catch (e) {
url.searchParams.delete('cfg');
}
window.history.replaceState({}, '', url);
}
}
// Debounced draw on input
var debounceTimer;
input.addEventListener('input', function() {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(draw, 200);
});
input.addEventListener('keydown', function(e) {
if (e.key === 'Enter') draw();
});
drawBtn.addEventListener('click', draw);
var resizeTimer;
window.addEventListener('resize', function() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function() {
if (input.value.trim()) draw();
}, 150);
});
// Update slider value displays and redraw on change
sliders.forEach(function(name) {
var el = document.getElementById('opt-' + name);
var valEl = document.getElementById('opt-' + name + '-val');
if (el && valEl) {
el.addEventListener('input', function() {
valEl.textContent = el.value;
draw();
});
}
});
selects.forEach(function(name) {
var el = document.getElementById('opt-' + name);
if (el) el.addEventListener('change', draw);
});
reactionSliders.forEach(function(name) {
var el = document.getElementById('opt-' + name);
var valEl = document.getElementById('opt-' + name + '-val');
if (el && valEl) {
el.addEventListener('input', function() {
valEl.textContent = el.value;
draw();
});
}
});
checkboxes.forEach(function(na