/* base.css — fonts, design tokens, reset, overall grid and shared components.
   Loaded first: everything else builds on the custom properties defined here. */

/* Self-hosted fonts, so the runtime loads no external resource and works offline. The swap
   display strategy falls back to a system font immediately if one has not loaded. */
@font-face{font-family:'IBM Plex Mono';src:url('fonts/plex-mono-400.woff2') format('woff2');font-weight:400;font-display:swap;}
@font-face{font-family:'IBM Plex Mono';src:url('fonts/plex-mono-500.woff2') format('woff2');font-weight:500;font-display:swap;}
@font-face{font-family:'IBM Plex Mono';src:url('fonts/plex-mono-600.woff2') format('woff2');font-weight:600;font-display:swap;}
@font-face{font-family:'IBM Plex Mono';src:url('fonts/plex-mono-700.woff2') format('woff2');font-weight:700;font-display:swap;}
@font-face{font-family:'IBM Plex Sans';src:url('fonts/plex-sans-400.woff2') format('woff2');font-weight:400;font-display:swap;}
@font-face{font-family:'IBM Plex Sans';src:url('fonts/plex-sans-500.woff2') format('woff2');font-weight:500;font-display:swap;}
@font-face{font-family:'IBM Plex Sans';src:url('fonts/plex-sans-600.woff2') format('woff2');font-weight:600;font-display:swap;}
:root{
  /* Declares the page dark to the browser itself, which is what makes scrollbars, form
     controls and the canvas behind the document follow instead of arriving white. Then the
     bar is toned down to the panel edge: a scrollbar states how far down a list is, it is
     not a control anyone aims at here. */
  color-scheme:dark;
  scrollbar-color:var(--dim2) transparent;
  scrollbar-width:thin;
  --bg:#0a0e12; --panel:#0f151b; --panel2:#10171e; --edge:#1d2731;
  --grid:#16202a; --ink:#c2d0da; --dim:#5d6e7c; --dim2:#3c4a56;
  --teal:#2dd4bf; --green:#41c463; --magenta:#d65db1; --amber:#f0a500;
  --red:#ef5a5a; --blue:#4aa8ff;
  /* The trace colour and a flat fill, never a vertical gradient: a fine trace over a grid,
     with no wash. */
  --trace:#37d0bf; --fill:rgba(55,208,191,.08);
  --mono:"IBM Plex Mono","SFMono-Regular",ui-monospace,"Cascadia Mono","Consolas",monospace;
  --sans:"IBM Plex Sans","Segoe UI",system-ui,-apple-system,sans-serif;
}
*{box-sizing:border-box}
html,body{height:100%}
body{
  margin:0; background:radial-gradient(1200px 600px at 50% -10%,#0e161e,#070a0e 70%);
  color:var(--ink); font-family:var(--sans); font-size:13px;
  -webkit-font-smoothing:antialiased; overflow:hidden;
}
/* Layout: the page is a COLUMN OF SHARES, like every panel in it. The header and the control
   bar have no share and keep their natural height; the spectrum and the middle row trade theirs
   on a handle. It was a grid of four fixed rows, which made the boundary under the spectrum the
   only one on the page that could not be moved -- and so the only one that could not line up
   with its neighbours. Floors live on what needs them, never on a row: a row capped or floored
   in pixels sits at a different fraction of the page on every screen. */
.app{height:100vh; display:flex; flex-direction:column; gap:7px; padding:8px;}
.panel{background:linear-gradient(180deg,var(--panel2),var(--panel));
       border:1px solid var(--edge); border-radius:3px; position:relative;}
.cap{position:absolute; top:6px; left:9px; font-size:10px; letter-spacing:.14em;
     text-transform:uppercase; color:var(--dim); pointer-events:none;}
.cap .r{position:absolute; left:0;}
/* Panel corner toggle: spectrum centring, amplitude autoscale, time graph pause. Shared by
   several panels, so it lives here rather than in one widget's sheet. */
.spectrl{position:absolute; top:3px; right:8px; z-index:3; cursor:pointer; font-size:15px;
     line-height:1; color:var(--dim2); padding:1px 4px; border-radius:4px; user-select:none;}
.spectrl:hover{color:var(--ink); background:#172530;}
.spectrl.act{color:var(--teal);}
/* Receiver commands carried by a widget, in the FREE corner of its card: the top right is
   already the slot's mark and the widget's own toggles. Shared, since more than one widget
   carries commands, and smaller than a bar button -- it sits over an instrument, not under
   one. The lit and locked states come from `.btn`, which these are. */
.wcmd{position:absolute; top:3px; z-index:3; display:flex; gap:4px;}
.wcmd .btn{padding:3px 9px; font-size:10px; letter-spacing:.1em;}

/* What a keyboard shortcut just did. A key press has no visible origin, so the command names
   itself for a moment. Placed low and centred, over the page and never in it: it must not
   cover a reading, and it must not move one either -- hence fixed, out of the flow, and
   transparent to the pointer, so a message cannot swallow a click on what is underneath. */
.osd{position:fixed; left:50%; bottom:38px; transform:translateX(-50%);
     z-index:60; pointer-events:none;
     font-family:var(--mono); font-size:12px; letter-spacing:.14em; color:var(--teal);
     background:rgba(8,14,19,.92); border:1px solid #1f5048; border-radius:3px;
     padding:7px 16px; white-space:nowrap;
     opacity:0; transition:opacity .5s ease;}
/* Appearing is immediate and fading is slow: the answer has to land within the same gesture,
   while the message leaving must not pull the eye back to it. */
.osd.on{opacity:1; transition:opacity .06s ease;}
/* Continuous-trace meter with peak hold, and its watermark scale. What separates an
   instrument from a progress bar is the graduated scale and the peak marker that falls back
   slowly, so the look sits beside the code that drives it (`lMeter` and `lRuler`) rather than
   in one panel's sheet: several pages carry one of these. Panel-specific variants stay with
   their widget. */
.track{position:relative; height:13px; border-radius:2px; overflow:hidden;
       background:#0b1116; border:1px solid #182430; box-shadow:inset 0 1px 2px rgba(0,0,0,.6);}
.track .clip{position:absolute; top:0; left:0; height:100%; overflow:hidden;}
.track .grad{position:absolute; top:0; left:0; height:100%;}
.track.sig .grad{background:linear-gradient(90deg,#1f8f45 0%,var(--green) 42%,var(--amber) 74%,var(--red) 100%);}
.track .peak{position:absolute; top:-1px; bottom:-1px; width:2px; background:#fff;
             box-shadow:0 0 5px rgba(255,255,255,.7); border-radius:1px;}
.ruler{position:relative; height:11px; margin-top:2px; font-size:8px; color:var(--dim2);}
.ruler span{position:absolute; transform:translateX(-50%); top:3px;}
.ruler i{position:absolute; top:0; width:1px; height:3px; background:var(--dim2); transform:translateX(-.5px);}

canvas{display:block; width:100%;}
#spec,#fall{height:100%;}   /* overrides the implicit aspect ratio of a canvas element */

/* Header */
.hdr{display:flex; align-items:center; gap:14px; padding:8px 12px;}
.brand{font-weight:700; letter-spacing:.18em; font-size:13px; color:var(--ink);}
.brand b{color:var(--teal);}
.badge{font-size:10px; color:var(--amber); border:1px solid #3a3217;
       background:#1a1709; border-radius:2px; padding:2px 6px; letter-spacing:.08em;}
.hspacer{flex:1;}
.bigfreq{font-family:var(--mono); color:#eaf2f7; line-height:1; white-space:nowrap;}
.bigfreq b{font-size:34px; font-weight:600; letter-spacing:.02em;}
/* Tunable digits: hovering one and turning the wheel tunes that digit. */
.bigfreq .fd{cursor:ns-resize; border-radius:3px;}
.bigfreq .fd:hover{color:var(--teal); background:#13202b;}
.bigfreq .fdot{padding:0 1px;}
.bigfreq .fdvoid{visibility:hidden;}   /* integer padding: keeps the width, stays invisible */
.bigfreq i{font-size:13px; font-style:normal; color:var(--dim);
           letter-spacing:.18em; margin-left:8px;}
.freqline{display:flex; align-items:baseline; gap:12px; margin-bottom:8px;}
.stat{flex:0 0 auto; display:flex; align-items:center; gap:8px; font-family:var(--mono);}
.dot{width:8px; height:8px; border-radius:50%; background:var(--green);
     box-shadow:0 0 8px var(--green);}
.stat .mode{color:var(--dim); letter-spacing:.12em; font-size:12px;}
/* The two commands that end what the mode names. Scaled to the header line rather than to a
   bar, and given room from the readout so the state and its actions read as two things. */
.stat .btn{padding:4px 10px; font-size:10px; letter-spacing:.1em;}
.stat .btn:first-of-type{margin-left:6px;}

/* Containers of slots, side by side */
/* One module width, four of them across. A widget occupies exactly one within its family,
   wherever it sits, so moving it between slots reflows nothing inside it. The lateral panels
   take one module each; the middle column takes the two between them.
   ONE ROW. The grid places the three containers and stops there: every height inside them is a
   SHARE, traded on a boundary the operator drags. The row used to be split in two so the time
   graph could sit above the listening panel, which made that one boundary the only one on the
   page that could not be moved -- and the only one that could not line up with its neighbours.
   Floors live on the cards that need them, never here: a row capped in pixels sits at a
   different fraction of the page on every screen, so two lines that met at one height drift
   apart at another and an alignment the operator reached stops holding. */
/* A row item like the others -- it takes a share of the page's height and its boundary with the
   spectrum is dragged. Nothing mounts in it: it declares columns of its own, which is what keeps
   it out of the slots. */
.bottom{display:grid; gap:7px; grid-template-columns:repeat(4, minmax(0, 1fr));
        grid-template-rows:minmax(0,1fr);}
/* Its floor is what four containers of instruments need to stay readable. Three classes deep,
   because it has to outweigh the `min-height: 0` every row item carries -- and that rule lives
   in a sheet loaded after this one, so equal specificity would lose. */
.app > .bottom[data-slot]{min-height:500px;}
.bottom > .left{grid-column:1; grid-row:1;}
.bottom > .midcol{grid-column:2 / 4; grid-row:1;}
/* The middle column is a container of slots like the lateral ones, so it carries the same
   class and would take the lateral placement -- which is more specific than its own rule
   above, and would drop it into the right-hand column. It is excluded by its own name. */
.bottom > .side:not(.left):not(.midcol){grid-column:4; grid-row:1;}
.ctxmenu{position:fixed; z-index:50; min-width:170px; padding:4px;
         background:var(--panel2); border:1px solid var(--edge); border-radius:7px;
         box-shadow:0 8px 24px rgba(0,0,0,.5); font-family:var(--mono); font-size:12px;}
.ctxitem{padding:7px 10px; border-radius:5px; color:var(--ink); cursor:pointer; white-space:nowrap;}
.ctxitem:hover{background:var(--teal); color:#04201c;}
/* Secondary, not refused: the entry is choosable, it simply is not free of consequence. */
.ctxitem.dim{color:var(--dim);}
.ctxitem.dim:hover{color:#04201c;}
/* A rule between two natures of choice, so a menu offering both does not read as one list. */
.ctxsep{height:1px; margin:4px 6px; background:var(--edge);}
/* A group carries a chevron, so an entry that opens a panel is told apart from one that acts
   before it is clicked rather than after. */
.ctxitem.sub{padding-right:22px; position:relative;}
.ctxitem.sub::after{content:"›"; position:absolute; right:9px; top:50%; transform:translateY(-50%);
                    color:var(--dim); font-size:14px; line-height:1;}
.ctxitem.sub:hover::after{color:#04201c;}
/* Four modules need the width of four modules. Below that the row folds rather than crushing
   every column at once: the middle column stays whole as long as it can, then everything
   stacks. Each step restates the placements, since a grid area declared for four columns is
   meaningless in two. The floor here is on the middle column, which holds two cards. */
@media (max-width:1400px){
  .bottom{grid-template-columns:repeat(2, minmax(0, 1fr));
          grid-template-rows:minmax(340px,2fr) minmax(0,1fr);}
  .bottom > .midcol{grid-column:1 / 3; grid-row:1;}
  .bottom > .left{grid-column:1; grid-row:2;}
  .bottom > .side:not(.left):not(.midcol){grid-column:2; grid-row:2;}
}
@media (max-width:760px){
  .bottom{grid-template-columns:1fr; grid-auto-rows:minmax(0,auto); grid-template-rows:none;}
  .bottom > .side{grid-column:1; grid-row:auto;}
  .side{max-height:34vh;}
}
