/* ---------------------------------------------------------------------------
   Fonts load HERE, not in the pages. Every page already links sandbox.css, so
   this @import is the one place the font set is declared — adding a family or a
   weight is a single edit instead of seven. Pages keep only the two
   <link rel=preconnect> hints (an @import is discovered late, after this file
   is fetched and parsed, so the early handshake is worth the duplicated line;
   those two lines never change when the font set does).

   --font-sans is Nunito, the closest freely-licensed stand-in for Proxima Soft.
   The Proxima names stay ahead of it in the stack, so a machine that has the
   real thing installed uses it and everyone else gets Nunito.
   --------------------------------------------------------------------------- */
@import url('https://fonts.googleapis.com/css2?family=Permanent+Marker&family=Zilla+Slab:wght@500;600;700&family=Caveat:wght@500;600;700&family=Nunito:wght@400;600;700&display=swap');

 :root {
   --paper: #efe6d6;
   --paper-line: #e2d8c6;
   --panel: #f8f5ef;
   --ink: #2b2723;
   --muted: #8a8073;
   --hair: #d8cfbf;
   --sketch: #353f44;
   --radius: 10px;
   --O: #e6362f;
   --H: #9DADC2;
   --Na: #a052e0;
   --Cl: #2fbf7a;
   --hot: #e5533a;
   --cold: #4a90d9;
   --font-marker: 'Permanent Marker', cursive;
   --font-slab: 'Zilla Slab', Georgia, serif;
   --font-hand: 'Caveat', cursive;
   --font-sans: 'Proxima Soft', 'Proxima Nova', 'Nunito', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
   --torn: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='60' viewBox='0 0 14 60' preserveAspectRatio='none'%3E%3Cpath d='M14,0 L14,60 L8,60 L2,55 L7,49 L1,43 L6,37 L2,31 L7,25 L1,19 L6,13 L2,8 L7,3 L1,0 Z' fill='black'/%3E%3C/svg%3E");
 }

 * {
   box-sizing: border-box
 }

 html,
 body {
   margin: 0;
   height: 100%;
   color: var(--ink);
   font-family: var(--font-sans)
 }

 #app {
   display: grid;
   grid-template-columns: 1fr 372px;
   grid-template-rows: 1fr;
   height: 100vh;
   overflow: hidden
 }

 /* ---------- stage (left, dotted stationery) ---------- */
 #dissocmsg {
   position: absolute;
   top: 80px;
   left: 50%;
   transform: translateX(-50%);
   z-index: 12;
   pointer-events: none;
   text-align: center;
   opacity: 0;
   white-space: nowrap;
   font-family: var(--font-hand);
   font-size: 20px;
   color: var(--muted);
   letter-spacing: .2px;
   background-color: rgba(255, 255, 255, .5);
   padding: 10px;
   border-radius: var(--radius);
 }

 #dissocmsg b {
   font-weight: 400
 }

 #dissocmsg .Na {
   color: var(--Na)
 }

 #dissocmsg .Cl {
   color: var(--Cl)
 }

 #dissocmsg.show {
   animation: dissocFade 3.5s ease-out forwards
 }

 @keyframes dissocFade {
   0% {
     opacity: 0;
     transform: translateX(-50%) translateY(-6px)
   }

   10% {
     opacity: 1;
     transform: translateX(-50%) translateY(0)
   }

   80% {
     opacity: 1;
     transform: translateX(-50%) translateY(0)
   }

   100% {
     opacity: 0;
     transform: translateX(-50%) translateY(-4px)
   }
 }

 /* NOTHING ON THE STAGE IS SELECTABLE. Dragging the canvas turns the model,
    and without this the same drag also sweeps a text selection through
    everything sitting over it — the caption, the title, the readout, the
    callout labels — so orbiting a molecule left the page streaked with
    highlighted text. The stage is a control surface, not prose: the words
    on it are chrome, and the real text lives in #side, which stays
    selectable. Set here rather than per page because the drag surface is
    the same on all of them. */
 #stage {
   grid-column: 1;
   position: relative;
   overflow: hidden;
   user-select: none;
   -webkit-user-select: none;
   background-color: var(--paper);
   background-image: radial-gradient(var(--paper-line) 1.4px, transparent 1.4px);
   background-size: 26px 26px;
   background-position: 13px 13px
 }

 #gl {
   display: block;
   width: 100%;
   height: 100%
 }

 /* ...EXCEPT PROSE. The rule above is about the drag, and the drag never
    starts inside a panel that sits over the stage — those take the pointer
    themselves, so nothing is lost by leaving their text selectable, and
    something real is lost by not: the info panel is the page explaining
    itself, which is exactly the text a reader wants to copy or quote.
    `.selectable` is the hook for the next such panel, so this rule does
    not have to grow an id every time one is added. */
 #infopanel,
 .selectable {
   user-select: text;
   -webkit-user-select: text
 }

 /* wordmark */
 #brand {
   position: absolute;
   top: 22px;
   left: 26px;
   display: flex;
   align-items: center;
   gap: 11px;
   z-index: 6
 }

 #brand .drop {
   font-size: 26px;
   color: var(--ink)
 }

 #brand h1 {
   margin: 0;
   font-family: var(--font-marker);
   font-weight: 400;
   font-size: 25px;
   letter-spacing: .3px
 }

 /* H2O formula card, top-center */
 #formula {
   position: absolute;
   top: 20px;
   right: 28px;
   z-index: 6;
   display: flex;
   flex-direction: column;
   align-items: center;
   gap: 8px
 }

 .sketchbox {
   position: relative;
   width: 100px;
   height: 100px;
   border: 2.5px solid var(--ink);
   border-radius: var(--radius);
   display: flex;
   align-items: center;
   justify-content: center;
   background: rgba(255, 255, 255, .28)
 }

 .sketchbox img {
   width: 74px;
   height: 74px;
   object-fit: contain
 }

 .sketchbox .flabel {
   position: absolute;
   top: -13px;
   left: 6px;
   font-family: var(--font-slab);
   font-weight: 600;
   font-size: 15px;
   background: var(--paper);
   padding: 0 5px
 }

 .sketchbox .flabel sub {
   font-size: .72em
 }

 .addbtn {
   width: 34px;
   height: 34px;
   border-radius: 50%;
   border: none;
   background: #4b4640;
   color: #fff;
   font-size: 18px;
   cursor: pointer;
   display: flex;
   align-items: center;
   justify-content: center;
   box-shadow: 0 2px 6px rgba(0, 0, 0, .22);
   transition: .15s
 }

 .addbtn:hover {
   background: #2b2723;
   transform: scale(1.06)
 }

 .addbtn.mini {
   width: 26px;
   height: 26px;
   font-size: 14px;
   background: transparent;
   color: var(--muted);
   border: 1.5px solid var(--hair);
   box-shadow: none
 }

 .addbtn.mini:hover {
   color: var(--ink);
   border-color: var(--muted);
   background: transparent
 }

 #formula .addrow {
   position: relative;
   display: flex;
   justify-content: center
 }

 #formula .addrow .mini {
   position: absolute;
   left: calc(50% + 26px);
   top: 50%;
   transform: translateY(-50%)
 }

 /* NaCl card, bottom-center (step 5) */
 #saltcard {
   position: absolute;
   bottom: 26px;
   right: 28px;
   z-index: 6;
   display: none;
   flex-direction: column;
   align-items: center;
   gap: 8px
 }

 #saltcard .clear {
   font-family: var(--font-hand);
   font-size: 20px;
   color: var(--muted);
   cursor: pointer;
   text-decoration: underline;
   text-underline-offset: 3px
 }

 #saltcard .clear:hover {
   color: var(--ink)
 }

 /* temperature slider, left (steps 3+) */
 #tempbar {
   position: absolute;
   left: 34px;
   top: 50%;
   transform: translateY(-50%);
   z-index: 5;
   display: none;
   flex-direction: row;
   align-items: center;
   gap: 16px
 }

 #tempbar .tcol {
   display: flex;
   flex-direction: column;
   align-items: center;
   gap: 9px
 }

 #tempbar .tlabel {
   font-family: var(--font-slab);
   font-weight: 600;
   font-size: 16px
 }

 #tempbar .tlabel.hot {
   color: var(--hot)
 }

 #tempbar .tlabel.cold {
   color: var(--cold)
 }

 #trange {
   -webkit-appearance: none;
   appearance: none;
   writing-mode: vertical-lr;
   direction: rtl;
   width: 20px;
   height: 340px;
   border-radius: 12px;
   cursor: pointer;
   outline: none;
   box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .08)
 }

 #trange::-webkit-slider-thumb {
   -webkit-appearance: none;
   width: 24px;
   height: 24px;
   border-radius: 50%;
   background: #fff;
   border: 2.5px solid var(--ink);
   cursor: pointer;
   box-shadow: 0 2px 5px rgba(0, 0, 0, .25)
 }

 #trange::-moz-range-thumb {
   width: 22px;
   height: 22px;
   border-radius: 50%;
   background: #fff;
   border: 2.5px solid var(--ink);
   cursor: pointer
 }

 #treadout {
   display: flex;
   flex-direction: column;
   gap: 2px;
   min-width: 120px;
   position: absolute;
   top: 32px;
   left: 50px;
 }

 #treadout .tval {
   font-family: var(--font-slab);
   font-weight: 700;
   font-size: 34px;
   line-height: 1
 }

 #treadout .tdesc {
   font-family: var(--font-hand);
   font-size: 19px;
   color: var(--muted);
   line-height: 1.05;
   margin-top: 6px;
   white-space: pre-line
 }

 /* step-5 salt readout, bottom-center of the canvas */
 #saltinfo {
   position: absolute;
   left: 50%;
   bottom: 24px;
   transform: translateX(-50%);
   z-index: 5;
   display: none;
   text-align: center;
   font-family: var(--font-slab);
   font-weight: 500;
   font-size: 14px;
   color: #5a5346;
   line-height: 1.5;
   white-space: nowrap
 }

 #saltinfo b {
   font-weight: 700;
   color: var(--ink)
 }

 #saltinfo .cold {
   color: var(--cold)
 }

 #saltinfo .hot {
   color: var(--hot)
 }

 #saltinfo .muted {
   color: var(--muted);
   font-weight: 500
 }

 /* bottom-left stats + PNG */
 #stats {
   position: absolute;
   left: 26px;
   bottom: 66px;
   z-index: 5;
   font-size: 13px;
   color: var(--muted);
   line-height: 1.7
 }

 #savepng {
   position: absolute;
   left: 26px;
   bottom: 22px;
   z-index: 5;
   font-weight: 600;
   font-size: 13px;
   letter-spacing: .5px;
   color: var(--ink);
   background: transparent;
   border: 2px solid var(--ink);
   border-radius: var(--radius);
   padding: 6px 15px;
   cursor: pointer;
   display: inline-flex;
   align-items: center;
   gap: 6px
 }

 #savepng:hover {
   background: var(--ink);
   color: var(--paper)
 }

 /* on-stage toggle chips / layers panel */
 #chips {
   position: absolute;
   left: 26px;
   bottom: 150px;
   z-index: 5;
   display: flex;
   flex-direction: column;
   gap: 6px;
   align-items: flex-start
 }

 .layer-panel {
   background: rgba(255, 255, 255, .85);
   backdrop-filter: blur(6px);
   border: 1.5px solid var(--hair);
   border-radius: var(--radius);
   box-shadow: 0 4px 14px rgba(0, 0, 0, .08);
   overflow: hidden;
   min-width: 175px
 }

 .layer-panel-header {
   font-family: var(--font-slab);
   font-size: 11px;
   font-weight: 700;
   letter-spacing: .8px;
   text-transform: uppercase;
   color: var(--muted);
   padding: 7px 12px 5px;
   background: rgba(0, 0, 0, .03);
   border-bottom: 1px solid var(--hair)
 }

 .chip {
   font-size: 13px;
   color: var(--ink);
   padding: 6px 12px;
   cursor: pointer;
   user-select: none;
   display: flex;
   align-items: center;
   justify-content: space-between;
   gap: 10px;
   transition: background .15s, color .15s
 }

 .chip:not(:last-child) {
   border-bottom: 1px solid rgba(0, 0, 0, .05)
 }

 .chip:hover {
   background: rgba(0, 0, 0, .04)
 }

 .chip .layer-name {
   display: flex;
   align-items: center;
   gap: 6px
 }

 .chip .eye-icon {
   font-size: 16px;
   color: var(--muted);
   transition: color .15s
 }

 .chip.on {
   background: rgba(43, 39, 35, .07);
   color: var(--ink);
   font-weight: 600
 }

 .chip.on:hover {
   background: rgba(43, 39, 35, .12)
 }

 .chip.on .eye-icon {
   color: var(--ink)
 }

 /* ---------- right panel (torn grid paper) ---------- */
 #side {
   grid-column: 2;
   position: relative;
   background: var(--panel);
   background-image: linear-gradient(var(--hair) 1px, transparent 1px), linear-gradient(90deg, var(--hair) 1px, transparent 1px);
   background-size: 22px 22px;
   display: flex;
   flex-direction: column;
   padding: 26px 30px 44px 34px;
   box-shadow: -14px 0 30px -18px rgba(0, 0, 0, .25)
 }

 #side::before {
   content: "";
   position: absolute;
   top: 0;
   left: -9px;
   width: 14px;
   height: 100%;
   background: var(--panel);
   z-index: 2;
   -webkit-mask: var(--torn);
   mask: var(--torn);
   -webkit-mask-size: 100% 100%;
   mask-size: 100% 100%
 }

 /* step numbers with hand-drawn circle */
 #steps {
   display: flex;
   gap: 14px;
   align-items: center;
   margin-bottom: 22px
 }

 .stepnum {
   position: relative;
   font-family: var(--font-slab);
   font-weight: 700;
   font-size: 30px;
   color: var(--muted);
   cursor: pointer;
   width: 44px;
   height: 44px;
   display: flex;
   align-items: center;
   justify-content: center
 }

 .stepnum:hover {
   color: var(--ink)
 }

 .stepnum.active {
   color: var(--ink)
 }

 .stepnum.active::after {
   content: "";
   position: absolute;
   inset: -6px -4px;
   background: url('sketch_circle.svg') center/100% 100% no-repeat
 }

 /* #card itself stays overflow:visible so the lightbulb (below) can spill past its
    left edge onto the sidebar's torn edge; only the title+body text scrolls, in
    its own inner box, so long copy doesn't clip the bulb */
 #card {
   flex: 1;
   display: flex;
   flex-direction: column;
 }

 #card .cardtext {
   flex: 0 1 auto;
   overflow-y: auto;
   min-height: 0
 }

 #card h2 {
   font-family: var(--font-marker);
   font-weight: 400;
   font-size: 28px;
   line-height: 1.12;
   margin: 0 0 16px
 }

 #card p {
   font-family: var(--font-sans);
   font-weight: 400;
   font-size: 16px;
   line-height: 1.55;
   margin: 0 0 14px;
   color: #3a352f
 }

 #card p.lead {
   font-weight: 600
 }

 #card ul {
   font-family: var(--font-sans);
   font-weight: 400;
   font-size: 15px;
   line-height: 1.55;
   padding-left: 20px;
   margin: 0 0 14px
 }

 #card b {
   font-weight: 700
 }

 #card .fact {
   padding: 14px 16px;
   background: rgba(255, 224, 120, .28);
   border-left: 3px solid #e5b93a;
   border-radius: 0 var(--radius) var(--radius) 0;
   line-height: 1.5;
   opacity: 0;
   pointer-events: none;
   transition: opacity .2s
 }

 #card .fact.show {
   opacity: 1;
   pointer-events: auto
 }

 /* lightbulb sticker + the fact it reveals — the fact keeps its box size even
    hidden (opacity, not display), so the bulb — absolutely positioned within the
    relative wrapper — stays put instead of jumping when toggled; repeats per
    fact if a step has several */
 #card .factwrap {
   position: relative;
   margin-top: 18px;
   padding-left: 12px
 }

 #card .factwrap .bulb {
   position: absolute;
   left: -65px;
   top: 50%;
   transform: translateY(-50%);
   width: 64px;
   cursor: pointer;
   z-index: 2;
   filter: drop-shadow(0 4px 8px rgba(0, 0, 0, .2));
   transition: transform .15s
 }

 #card .factwrap .bulb:hover {
   transform: translateY(-50%) scale(1.08) rotate(-3deg)
 }

 #card .factwrap .bulb.lit {
   transform: translateY(-50%) scale(1.05)
 }

 /* nav footer */
 footer {
   display: flex;
   gap: 12px;
   align-items: center;
   margin-top: 16px
 }

 footer button {
   font-weight: 600;
   font-size: 15px;
   padding: 11px 22px;
   border-radius: var(--radius);
   cursor: pointer;
   border: 2px solid var(--ink)
 }

 #prev {
   background: transparent;
   color: var(--ink)
 }

 #prev:hover {
   background: rgba(0, 0, 0, .05)
 }

 #next {
   background: var(--ink);
   color: var(--panel);
   border-color: var(--ink);
   margin-left: auto
 }

 #next:hover {
   background: #000
 }

 footer button:disabled {
   opacity: .3;
   cursor: not-allowed;
   pointer-events: none
 }

 #dbgtoggle {
   position: absolute;
   left: 0;
   right: 0;
   bottom: 14px;
   text-align: center;
   font-family: var(--font-slab);
   font-weight: 600;
   font-size: 13px;
   letter-spacing: 1px;
   color: var(--muted);
   cursor: pointer;
   user-select: none
 }

 #dbgtoggle:hover {
   color: var(--ink)
 }

 /* ---------- right panel extras ---------- */
 #labtitle {
   margin: 0 0 8px;
   font-family: var(--font-marker);
   font-weight: 400;
   font-size: 28px;
   line-height: 1.12
 }

 #labintro {
   margin: 0 0 18px;
   font-family: var(--font-sans);
   font-size: 15px;
   line-height: 1.5;
   color: #3a352f
 }

 #library {
   display: flex;
   flex-direction: column;
   gap: 8px;
   margin-bottom: 20px
 }

 .libgroup {
   font-family: var(--font-slab);
   font-weight: 700;
   font-size: 11px;
   letter-spacing: 1.2px;
   text-transform: uppercase;
   color: var(--muted);
   margin-top: 8px
 }

 .libgroup:first-child {
   margin-top: 0
 }

 .molrow {
   display: flex;
   align-items: center;
   gap: 10px;
   padding: 8px 10px;
   cursor: pointer;
   border: 1.5px solid var(--hair);
   border-radius: 9px;
   background: rgba(255, 255, 255, .5);
   user-select: none
 }

 .molrow:hover {
   border-color: var(--muted)
 }

 .molrow.sel {
   border-color: var(--ink);
   background: rgba(255, 255, 255, .92);
   box-shadow: 0 1px 0 var(--ink)
 }

 .molrow .swatch {
   width: 14px;
   height: 14px;
   border-radius: 50%;
   flex: none
 }

 .molrow .mname {
   font-family: var(--font-sans);
   font-size: 15px;
   font-weight: 600;
   color: var(--ink)
 }

 .molrow .mform {
   font-family: var(--font-slab);
   font-size: 13px;
   color: var(--muted);
   margin-left: auto
 }

 /* add / clear buttons under the picker */
 #libactions {
   display: flex;
   gap: 10px;
   align-items: center;
   margin-bottom: 4px
 }

 #libactions button {
   font-weight: 600;
   font-size: 14px;
   padding: 9px 18px;
   border-radius: 9px;
   cursor: pointer;
   border: 2px solid var(--ink)
 }

 #addmol {
   background: var(--ink);
   color: var(--panel)
 }

 #addmol:hover {
   background: #000
 }

 #clearmol {
   background: transparent;
   color: var(--ink)
 }

 #clearmol:hover {
   background: rgba(0, 0, 0, .05)
 }

 #libactions button:disabled {
   opacity: .3;
   cursor: not-allowed;
   pointer-events: none
 }


 /* ---------- callouts on the model (annotate.js) ----------
    The look lives here rather than in the module so a callout is styled by
    the same stylesheet as every other piece of chrome — it is the house
    voice applied to a label, and a page must not be able to invent its own.
    annotate.js writes ONLY transform, opacity, z-index and display; every
    other property is here.

    The layer sits over the whole stage, so it must never take the mouse —
    the module sets pointer-events on the dots and only in click mode, and
    this rule is the backstop. An invisible overlay that eats orbit drags
    is the single worst bug this feature can have, because nothing on
    screen suggests a cause. */
 .annot-layer {
   position: absolute;
   inset: 0;
   overflow: hidden;
   pointer-events: none;
   z-index: 7
 }

 /* Positioned by transform at the anchor's projected point; the dot is
    centred ON that point and the label hangs off it. */
 .annot {
   position: absolute;
   top: 0;
   left: 0;
   will-change: transform, opacity
 }

 .annot-dot {
   position: absolute;
   left: -5px;
   top: -5px;
   width: 10px;
   height: 10px;
   padding: 0;
   border-radius: 50%;
   border: 2px solid var(--ink);
   background: var(--panel);
   box-shadow: 0 1px 4px rgba(0, 0, 0, .25);
   cursor: default
 }

 .annot-layer.is-click .annot-dot {
   cursor: pointer
 }

 .annot-layer.is-click .annot-dot:hover,
 .annot-dot:focus-visible {
   background: var(--ink);
   outline: none
 }

 /* The leader. A drawn line rather than a gap, so a label reads as
    BELONGING to its dot even where several are close together — which on a
    single heme they always are. Length and angle are solved by annotate.js
    when the note is added and handed over as --alen/--aang. */
 .annot-leader {
   position: absolute;
   left: 0;
   top: 0;
   height: 0;
   width: var(--alen, 18px);
   border-top: 1.5px solid var(--hair);
   transform-origin: 0 0;
   transform: rotate(var(--aang, 0deg));
   pointer-events: none
 }

 .annot-label {
   position: absolute;
   left: 0;
   top: 0;
   transform: translate(var(--adx, 18px), var(--ady, 0px)) translateY(-50%);
   white-space: nowrap;
   padding: 4px 9px;
   font-family: var(--font-sans);
   font-size: 13px;
   font-weight: 600;
   line-height: 1.2;
   color: var(--ink);
   background: rgba(255, 255, 255, .88);
   backdrop-filter: blur(6px);
   border: 1.5px solid var(--hair);
   border-radius: var(--radius);
   box-shadow: 0 3px 10px rgba(0, 0, 0, .12);
   opacity: 1;
   transition: opacity .18s ease
 }

 /* Left-hand callouts: the label hangs off the other side of its offset, so
    the text ends at the leader instead of starting on top of it. */
 .annot-left .annot-label {
   transform: translate(var(--adx, -18px), var(--ady, 0px)) translate(-100%, -50%)
 }

 /* Closed is the click mode's resting state: the dot stays, the label
    goes. Kept in the layout (not display:none) so opening it costs no
    reflow and the fade has something to fade. */
 .annot:not(.is-open) .annot-label {
   opacity: 0;
   pointer-events: none
 }

 /* Tones. A callout naming the iron should carry the iron's ink, the way
    every other coloured thing on these pages does. */
 .annot-iron .annot-dot {
   border-color: #a8321a
 }

 .annot-iron .annot-label {
   color: #8d2a16
 }

 @media (prefers-reduced-motion:reduce) {
   .annot-label {
     transition: none
   }
 }

 @media (max-width:920px) {
   #app {
     grid-template-columns: 1fr;
     grid-template-rows: 56vh 1fr
   }

   #stage {
     grid-column: 1
   }

   #side {
     grid-column: 1;
     padding: 20px
   }

   #side::before {
     display: none
   }
 }
