/* panel.css — the control panel, for every lab. Pairs with panel.js: that decides structure and behavior, this
 * decides appearance.
 *
 * Only what is generic lives here: the panel shell, section headers, rows, the four controls a row can hold
 * (range, toggle, choice, color) and the action strip. Anything that knows what a lab MEASURES belongs to that
 * lab's own stylesheet.
 *
 * Retint through the custom properties below rather than forking — Reactor overrides seven of them to go green.
 */
:root {
  --panel-w:      340px;
  --panel-bg:     #0b0d10;
  --panel-line:   #1c2128;
  --panel-accent: #ffb454;
  --panel-accent-soft: rgba(255, 180, 84, .35);
  --panel-accent-lit:  #ffd9a0;
  --panel-label:  #8b949e;
  --panel-dim:    #6e7681;
  --panel-fill:   #12100c;
  --panel-fill-2: #12161b;
  --panel-edge:   #2a2620;
  --panel-hot:    #2a1a08;
  --panel-hover:  #1b1408;
  --panel-focus:  #141a21;
  --panel-focus-edge: #3a2a14;
  --panel-track:  rgba(120, 128, 140, .55);
}

/* flex-shrink:0 so the panel keeps its width, and scrollbar-gutter so the scrollbar does not eat
   into the row when the content gets long enough to need one. */
#panel { flex: 0 0 var(--panel-w); width: var(--panel-w); background: var(--panel-bg);
         border-left: 1px solid var(--panel-line);
         padding: 14px 16px 40px; overflow-y: auto; scrollbar-gutter: stable; font-size: 11px }
#panel h1 { font-family: Archivo, sans-serif; font-size: 13px; letter-spacing: .2em;
            color: var(--panel-accent); margin: 0 0 2px; font-weight: 900 }
#panel .sub { color: var(--panel-dim); font-size: 10px; letter-spacing: .1em; margin: 0 0 14px }

.sec { color: var(--panel-accent); letter-spacing: .16em; font-size: 10px; margin: 16px 0 8px;
       padding-bottom: 4px; border-bottom: 1px solid var(--panel-line); font-weight: 700 }
.sec { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none }
.sec:hover { color: var(--panel-accent) }
.sec .caret { flex: 0 0 9px; font-size: 9px; opacity: .55; line-height: 1 }
.sec:hover .caret { opacity: 1 }
.sec .sectitle { flex: 1 1 auto }
.sec.has-tgl { justify-content: space-between }

.row { display: flex; align-items: center; gap: 5px; margin: 0 0 6px; padding: 2px 4px; border-radius: 3px;
       border: 1px solid transparent }
/* WHICH ROW HAS FOCUS, which 61 sliders badly needed. :focus-within lights the whole row rather than just the
   thumb, so arrowing down the panel shows you where you are without hunting for a 3px outline. */
.row:focus-within { background: var(--panel-focus); border-color: var(--panel-focus-edge) }
.row:focus-within label { color: var(--panel-accent) }
.row input[type=range]:focus { outline: none }
.row input[type=range]:focus::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--panel-accent-soft),
  0 0 10px rgba(255, 180, 84, .9) }
.row label { flex: 0 0 88px; min-width: 0; color: var(--panel-label); letter-spacing: .03em; font-size: 10px;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap }
/* min-width:0 IS THE FIX, and without it the value column was pushed off the panel entirely.
   A flex item defaults to min-width:auto, which refuses to shrink below its intrinsic size -- and a
   range input's intrinsic minimum is ~129px. So the row demanded 303px inside a 283px panel, the
   overflow went to the right, and the numbers ended up past the window edge. Measured: value column
   at 1535..1597 against a panel ending at 1587. */
.row input[type=range] { flex: 1 1 0; min-width: 0; -webkit-appearance: none; appearance: none; height: 3px;
  border-radius: 3px; background: var(--panel-track); outline: none }
.row input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; width: 11px; height: 11px;
  border-radius: 50%; background: var(--panel-accent); cursor: pointer;
  box-shadow: 0 0 8px rgba(255, 180, 84, .8) }
/* THE STEPPERS, as the reference has them: a nudge either side of the readout, so a value can be set exactly
   without hunting for a pixel on a 110px track. They repeat on hold, because a 0.01 step over a 0..2 range is
   200 clicks otherwise. */
/* THE READOUT IS AN INPUT AND MUST NOT LOOK LIKE ONE until it is being used: a panel of seventy rows with seventy
   boxed fields is a form, not an instrument. Everything a browser adds to a text input is stripped, and the only
   thing that comes back on focus is a ring saying "this one is live". */
.row .val { flex: 0 0 52px; text-align: center; color: var(--panel-accent); font-size: 10px; white-space: nowrap;
            font-variant-numeric: tabular-nums; overflow: visible;
            min-width: 0; background: none; border: none; padding: 0; margin: 0;
            font-family: inherit; cursor: text }
.row .val:focus { outline: 1px solid var(--panel-accent); outline-offset: 1px; border-radius: 2px }
.row .stp { flex: 0 0 15px; background: none; border: none; color: var(--panel-dim); font-family: inherit;
            font-size: 11px; line-height: 1; cursor: pointer; padding: 2px 0; border-radius: 2px;
            user-select: none }
.row .stp:hover { color: var(--panel-accent); background: var(--panel-hover) }
.row:focus-within .stp { color: var(--panel-label) }

.mtgl, .row .tgl { font-family: inherit; font-size: 10px; letter-spacing: .06em; cursor: pointer;
                   background: var(--panel-fill); border: 1px solid var(--panel-edge); color: var(--panel-dim);
                   border-radius: 3px; padding: 2px 9px }
.mtgl:hover, .row .tgl:hover { border-color: var(--panel-accent); color: var(--panel-accent) }
.mtgl.on, .row .tgl.on { background: var(--panel-hot); border-color: var(--panel-accent);
                         color: var(--panel-accent-lit) }
.row .tgl { flex: 0 0 86px; text-align: center; margin-left: auto }
/* A CHOICE ROW SHARES THE SLIDER'S TRACK between its buttons, so the pills line up with the sliders above and
 * below rather than sitting in the 86px a single toggle claims. margin-left:auto on the first one only, or
 * every gap in the group would be pushed to the left. */
.row.choice .tgl { flex: 1 1 0; min-width: 0; margin-left: 0; padding: 2px 0 }
.row.choice .tgl:first-of-type { margin-left: auto }
/* WIDE: for a choice whose buttons ARE the label — glyphs that have to be read as shapes, not words. The label
   column is dropped so the buttons span the panel, and the height is doubled so the glyph has room to be a
   silhouette rather than a character. */
.row.wide label { display: none }
.row.wide.choice .tgl { flex: 1 1 0; margin-left: 0; height: 38px; font-size: 17px; line-height: 1 }
.row.wide.choice .tgl:first-of-type { margin-left: 0 }

/* A PAIR OF SWATCHES occupies exactly what one does, so a row carrying two colors is the same height as a row
   carrying one and the panel's rhythm does not break. */
.row .cpair { flex: 0 0 86px; margin-left: auto; display: flex; gap: 3px }
.row .cpair .csw { flex: 1 1 0; margin-left: 0 }

.row .csw { flex: 0 0 86px; height: 18px; margin-left: auto; padding: 0; border: none; border-radius: 3px;
            background: none; cursor: pointer }
.row .csw::-webkit-color-swatch-wrapper { padding: 0 }
.row .csw::-webkit-color-swatch { border: none; border-radius: 3px }

/* ---------------------------------------------------------------- the panel's own switch
 *
 * A 340px panel is a sidebar on a desktop and most of the screen on a phone, so it needs to get out of the
 * way. Two behaviors, one state class (`panel-hidden` on <body>), because two independent states would be
 * two things to get out of step:
 *
 *   WIDE   the panel is in the flex flow and hiding it REMOVES it, so the stage grows into the space. That
 *          changes the stage's size, which is why the toggle tells its caller -- a renderer sized to the old
 *          box would letterbox or stretch until the next window resize.
 *   NARROW the panel is lifted out of the flow entirely and slides over the stage. Nothing reflows, so the
 *          stage keeps its size and no re-render is needed; the state class only moves the transform.
 *
 * The button is fixed rather than in either box, so it cannot be scrolled away from or covered by the panel
 * it controls -- and on wide screens it tucks against the panel's outer edge rather than sitting on top of
 * its scrollbar. */
/* A CHEVRON, AND IT LIVES IN THE PANEL'S OWN TOP-RIGHT CORNER when the panel is open. Fixed rather than
 * placed inside #panel so the OPEN affordance has somewhere to be once the panel is gone -- one element in
 * two places beats two elements that can disagree about the state. The panel occupies the right edge, so
 * `right: 10px` is inside it while it is open and against the screen edge once it is not.
 *
 * The glyph is the DIRECTION THE PANEL WILL MOVE, not an icon for "menu": > pushes it away to the right,
 * < pulls it back in. That is why it does not need a word beside it -- and the accessible name, which does
 * carry the meaning, is set in mountPanelToggle. */
#paneltgl {
  position: fixed; z-index: 60;
  top: calc(10px + env(safe-area-inset-top, 0px));
  right: calc(10px + env(safe-area-inset-right, 0px));
  width: 22px; height: 22px; padding: 0;
  display: grid; place-items: center;
  background: var(--panel-bg); border: 1px solid var(--panel-edge); color: var(--panel-accent);
  font-family: inherit; font-size: 13px; font-weight: 700; line-height: 1;
  border-radius: 3px; cursor: pointer;
  transition: border-color .15s, background .15s, opacity .15s;
}
#paneltgl:hover { border-color: var(--panel-accent); background: var(--panel-hover) }
#paneltgl:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--panel-accent-soft) }

/* CLOSED: a pull-tab on the edge rather than a button floating over the picture. Flush right with only the
 * left corners rounded, so it reads as something to drag out rather than a control sitting on the tube --
 * and dimmed until pointed at, because with the panel away the instrument is the thing being looked at. */
body.panel-hidden #paneltgl {
  right: calc(0px + env(safe-area-inset-right, 0px));
  width: 18px; height: 30px; font-size: 12px; opacity: .55;
  border-radius: 3px 0 0 3px; border-right: none;
}
body.panel-hidden #paneltgl:hover,
body.panel-hidden #paneltgl:focus-visible { opacity: 1 }
body.panel-hidden #panel { display: none }

/* NARROW *OR* SHORT, and the second half is the handset on its side. A phone in landscape is 852x393: wider
   than any phone breakpoint, so a `max-width` query alone put the panel back in the flex flow and gave 340 of
   those 852 px to controls on a screen 393 px tall. 500 separates phone landscape (320-430 tall) from the
   smallest real laptop (about 600), and mountPanelToggle's `shortSide` default carries the same number -- a
   stylesheet cannot be read from the script, and the two deciding differently is a panel that overlays the
   stage while the script believes it is in the flow. */
@media (max-width: 820px), (max-height: 500px) {
  /* OUT OF THE FLOW, so showing and hiding it costs the stage nothing. inset pins all four edges except the
     left, which the width then sets -- 88vw so a narrow phone still shows a sliver of the tube behind it and
     the panel never becomes the whole screen. */
  #panel {
    position: fixed; top: 0; right: 0; bottom: 0; left: auto;
    width: min(var(--panel-w), 88vw); z-index: 50;
    padding-top: calc(14px + env(safe-area-inset-top, 0px));
    box-shadow: -18px 0 40px rgba(0, 0, 0, .55);
    transition: transform .22s ease;
  }
  /* `display:none` is NOT reused here. It would skip the transition, and more to the point a display-toggled
     element cannot animate at all -- so the narrow case slides and the wide case simply goes. */
  body.panel-hidden #panel { display: block; transform: translateX(100%) }
  /* No position override here any more. The panel occupies the right edge at BOTH widths, so the base rules
   * already put the chevron inside it when open and on the edge when closed; an override repeating that only
   * gave the closed tab somewhere different to sit on a phone than on a desktop. z-index is on the base rule
   * too, and it has to stay above the panel -- this is the control the panel is dismissed with. */
}

/* NO ANIMATION ON THE FIRST PAINT, and this is a correction rather than a polish. The starting state is a
 * STATE, not a transition: without this a phone loads with the panel across the screen and then visibly slides
 * it away, which reads as the page fixing a mistake it just made. Measured at 390x844 -- the panel sat at its
 * open position on the first frame and animated out over 220ms.
 *
 * Higher specificity than the rules it suppresses (`body.panel-boot #panel` beats `#panel`), so no !important
 * is needed; mountPanelToggle drops the class once the opening state has been painted. */
body.panel-boot #panel, body.panel-boot #paneltgl { transition: none }

/* A reader who has asked for less motion gets the state change without the slide. */
@media (prefers-reduced-motion: reduce) {
  #panel, #paneltgl { transition: none }
}

.btns { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0 2px }
.btns button { flex: 1 1 auto; background: var(--panel-fill-2); border: 1px solid var(--panel-focus-edge);
  color: var(--panel-accent); padding: 7px 8px;
  font-family: inherit; font-size: 10px; letter-spacing: .12em; cursor: pointer; border-radius: 3px }
.btns button:hover { border-color: var(--panel-accent); background: var(--panel-hover) }
.btns button.on { background: var(--panel-hot); border-color: var(--panel-accent);
                  color: var(--panel-accent-lit) }
