/* ==========================================================================
   ide.css — the IDE shell, the default since 2026-09-04 (?ide=0 turns it off)
   --------------------------------------------------------------------------
   Every rule is scoped to :root[data-shell="ide"], so this sheet is inert on a
   page without the shell. It is linked in index.html's head from 2026-10-23
   (#67), so the first painted frame is already styled; js/ide_shell.js used to
   inject it, which on a slow connection showed the shell unstyled.

   Built from mockup-ide.html (branch beta, d980f10), the layout reference
   (removed 2026-10-09, #44; in git history at 7487700). The shell here wraps the REAL panels — the mockup's maps are CSS
   stand-ins and its scene is a random walk, so the things that can actually go
   wrong (Gridstack, the sticky IGV strip, Babylon resizing on a column change,
   the fit layout's viewport cap) only appear at this stage.

   Width budget at 1440, from the mockup:
     browser 260 + centre 880 + inspector 300 = 1440
     centre inner = 880 - 32 padding - 16 gap = 832 -> 404 map + 428 scene
   Collapsing the browser to its 48px rail gives all 212px to the scene.

   No colour literals: everything reads from css/phid-tokens.css.
   ========================================================================== */

:root[data-shell="ide"] {
  /* 300px, matching the inspector — 2026-09-06, the maintainer's call. The two
     side columns framing the workspace now carry the same width, which is one
     fewer arbitrary number and lets an accession sit on one line in the tree. */
  /* The reader's own width, from dragging the column's border, wins (#53). */
  --ide-browser-w: var(--ide-browser-size, 300px);
  --ide-inspector-w: var(--ide-inspector-size, 300px);
  --ide-footer-h: 30px;
  /* #topbar is min-height 60px plus its 1px border — see browse.css, which
     pins its sticky summary to the same number. Move the two together.
     (80 -> 60 on 2026-09-06, at the maintainer's request.) */
  --ide-topbar-h: 61px;
}
:root[data-shell="ide"][data-chrome="none"] { --ide-topbar-h: 0px; }
/* COLLAPSED TO NOTHING, not to a rail. The rail existed to carry the button that
   brought a column back; the buttons live in the footer now and cannot vanish
   with the thing they control, so a hidden column can simply be hidden and give
   all of its width to the workspace. */
:root[data-shell="ide"].hide-browser { --ide-browser-w: 0px; }
:root[data-shell="ide"].hide-inspector { --ide-inspector-w: 0px; }
:root[data-shell="ide"].hide-browser #ide-browser,
:root[data-shell="ide"].hide-inspector #ide-inspector,
:root[data-shell="ide"].hide-bottom #ide-bottom { display: none; }

/* --- the three columns ---------------------------------------------------- */
/* #layout is display:flex in style.css. Here it is the shell grid: browser,
   centre, inspector, with the footer spanning all three beneath them. */
:root[data-shell="ide"] {
  /* THE APP BAR IS DISABLED, not removed — 2026-09-06, at the maintainer's
     request, so it can come back later. Its one glyph was an inert placeholder
     (see the <span class="is-placeholder"> in ide_shell.js), so the strip cost
     44px and returned nothing. Width zero empties the grid track and the
     display:none below removes the node's border with it; everything else —
     the nav injection in ide_shell.js, the column numbering, the two media
     queries — is untouched, so restoring it is these two lines back. */
  --ide-appbar-w: 0px;
  /* 300 until 2026-10-02 (maintainer's request); dragging its border overrides it (#53). */
  --ide-bottom-h: var(--ide-bottom-size, 200px);
}
:root[data-shell="ide"] #ide-appbar { display: none; }
/* FOUR COLUMNS AND THREE ROWS: the app bar, the directory, the workspace and the
   inspector across; then the metadata section full width beneath all four, then
   the status bar. The metadata section used to sit inside the centre column,
   which made it as narrow as the panes and scrolled it away with them — it is a
   second inspector along the bottom, so it spans everything. */
:root[data-shell="ide"] #layout {
  display: grid;
  grid-template-columns:
    var(--ide-appbar-w) var(--ide-browser-w) minmax(0, 1fr) var(--ide-inspector-w);
  grid-template-rows: minmax(0, 1fr) auto var(--ide-footer-h);
  height: calc(100vh - var(--ide-topbar-h));
  gap: 0;
  transition: grid-template-columns .18s ease;
}
:root[data-shell="ide"] #ide-appbar    { grid-column: 1; grid-row: 1; }
:root[data-shell="ide"] #ide-browser   { grid-column: 2; grid-row: 1; }
:root[data-shell="ide"] #viewer        { grid-column: 3; grid-row: 1; min-width: 0; }
:root[data-shell="ide"] #ide-inspector { grid-column: 4; grid-row: 1; }
:root[data-shell="ide"] #ide-bottom    { grid-column: 1 / -1; grid-row: 2; }
:root[data-shell="ide"] #ide-footer    { grid-column: 1 / -1; grid-row: 3; }

/* --- the app bar ---------------------------------------------------------- */
/* Home, Browse, Compare, About. Placement for now, but wired to the site's own
   views rather than left dead. */
#ide-appbar {
  background: var(--phid-surface-inset);
  border-right: 1px solid var(--phid-border);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding-top: var(--phid-space-3);
  overflow: hidden;
}
.ide-appbtn {
  width: 32px; height: 32px;
  display: grid; place-items: center;
  border: 1px solid transparent;
  border-radius: var(--phid-radius-sm);
  background: none;
  color: var(--phid-text-muted);
  cursor: pointer;
  font: inherit;
  font-size: 16px;
  line-height: 1;
  text-decoration: none;
}
.ide-appbtn:hover { background: var(--phid-surface-hover); color: var(--phid-text); }
/* A placeholder must not invite a click. No hover, no pointer, no affordance. */
.ide-appbtn.is-placeholder { cursor: default; }
.ide-appbtn.is-placeholder:hover { background: none; color: var(--phid-text-muted); }

/* --- left: the data directory --------------------------------------------- */
:root[data-shell="ide"] #ide-browser {
  background: var(--phid-surface-card);
  border-right: 1px solid var(--phid-border);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-width: 0;
}
#ide-browser .ide-head {
  display: flex;
  align-items: center;
  gap: var(--phid-space-2);
  padding: var(--phid-space-3);
  border-bottom: 1px solid var(--phid-border-subtle);
  flex: none;
}
/* 2026-09-06: names, not shouting — the maintainer asked for "Data Directory"
   rather than DATA DIRECTORY, and the same reasoning applies to "Control" and to
   the inspector's section names below. The text now carries its own case. */
#ide-browser .ide-head .title {
  font-size: var(--phid-text-xs);
  letter-spacing: .02em;
  color: var(--phid-text-muted);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
}

.ide-iconbtn {
  width: 26px; height: 26px;
  display: grid; place-items: center;
  flex: 0 0 auto;
  background: none;
  border: 1px solid transparent;
  border-radius: var(--phid-radius-sm);
  cursor: pointer;
  color: var(--phid-text-muted);
  font: inherit;
  line-height: 1;
}
.ide-iconbtn:hover { background: var(--phid-surface-hover); }
.ide-iconbtn.on {
  background: var(--phid-accent-wash);
  border-color: var(--phid-accent-edge);
  color: var(--phid-accent);
}

#ide-filter { padding: 0 var(--phid-space-3) var(--phid-space-3); flex: none; }
#ide-filter input {
  width: 100%;
  font: inherit;
  font-size: var(--phid-text-sm);
  color: var(--phid-text);
  padding: 6px 8px;
  border: 1px solid var(--phid-border-control);
  border-radius: var(--phid-radius-sm);
  background: var(--phid-surface-inset);
}
#ide-filter .hint {
  font-size: var(--phid-text-xs);
  color: var(--phid-text-muted);
  margin-top: 6px;
}

#ide-tree {
  flex: 1;
  overflow: auto;
  padding: var(--phid-space-2) 0;
  font-size: var(--phid-text-sm);
}
#ide-tree .row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 3px var(--phid-space-3);
  cursor: pointer;
  white-space: nowrap;
}
#ide-tree .row:hover { background: var(--phid-surface-hover); }
#ide-tree .row.sel {
  background: var(--phid-accent-wash);
  color: var(--phid-accent);
  font-weight: var(--phid-weight-medium);
}
#ide-tree .row .tw { width: 10px; flex: none; color: var(--phid-text-muted); font-size: 9px; }
#ide-tree .row .ic { width: 14px; flex: none; text-align: center; opacity: .75; }
#ide-tree .row .lbl { overflow: hidden; text-overflow: ellipsis; }
/* THE MARK IS THE DATA PAGE'S LEGEND, SMALL — 2026-10-02, at the maintainer's
   request, replacing the coloured dot of 2026-09-06 (the team lead had already
   dropped colour from the Data page on 2026-09-28). A square with a ring on its
   corner for r >= 0.95, a plain square below, a dashed outline for not available:
   browse.css .browse-legend .sw, scaled to the rail. */
#ide-tree .row .ic:has(.mk) { opacity: 1; }
#ide-tree .row .mk {
  position: relative; display: inline-block; vertical-align: middle;
  width: 12px; height: 10px; border-radius: 2px; box-sizing: border-box;
  background: var(--phid-surface-card); border: 1px solid var(--phid-border);
}
#ide-tree .row .mk.ok::after {
  content: ''; position: absolute; top: -3px; right: -3px;
  width: 5px; height: 5px; box-sizing: border-box; border-radius: 50%;
  background: var(--phid-surface-card); border: 1.2px solid var(--phid-text-muted);
}
#ide-tree .row .mk.na { background: transparent; border: 1px dashed var(--phid-border-control); }
/* Listed, never opened: muted and not a pointer target. */
#ide-tree .row.na { color: var(--phid-text-muted); cursor: not-allowed; }
#ide-tree .row.na:hover { background: none; }
/* An accession with no finished leaf (#40): muted, but it opens to list them. */
#ide-tree .row.unavail { color: var(--phid-text-muted); }
#ide-tree .row .q {
  margin-left: auto;
  padding-left: 6px;
  font-family: var(--phid-font-mono);
  font-size: var(--phid-text-xs);
  color: var(--phid-text-muted);
  font-variant-numeric: tabular-nums;
}
#ide-tree .kids[hidden] { display: none; }
#ide-tree .loading { padding: 3px var(--phid-space-3); color: var(--phid-text-muted); font-size: var(--phid-text-xs); }

/* --- centre: tabs, the pinned strip, the panes, the bottom section -------- */
/* #viewer-sticky already exists and already pins the IGV strip and the caption.
   The tab strip joins it rather than sitting above it, so one sticky block
   carries everything that must not scroll away. */
:root[data-shell="ide"] #viewer-sticky {
  padding: 0 0 var(--phid-space-2);
  border-bottom: 1px solid var(--phid-border);
}
:root[data-shell="ide"] #igv-strip { margin: var(--phid-space-2) var(--phid-space-4) 0; }
/* THE CAPTION UNDER THE IGV STRIP IS DISABLED IN THE SHELL — 2026-09-06, at the
   maintainer's request. #viewer-header carried the accession/chromosome line and
   the species / build / normalization / tolerance / renderer line; both are in
   the full-width metadata section at the bottom, and the shell's own footer
   already names the open leaf, so it was a third statement of the same record
   costing height in the one block that must never scroll away.

   HIDDEN, NOT REMOVED, and scoped to the shell: #vh-title and #vh-sub are still
   written by app.js on every leaf open, so nothing there needs changing and
   ?ide=0 still shows the caption exactly as before. Restoring it is deleting
   this rule. */
:root[data-shell="ide"] #viewer-header { display: none; }

#ide-tabstrip {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 0 var(--phid-space-4);
  background: var(--phid-surface-inset);
  border-bottom: 1px solid var(--phid-border);
  min-height: 34px;
  overflow-x: auto;
}
/* EVERY TAB CARRIES ITS HAIRLINE, not just the selected one — 2026-09-06. An
   unselected tab was borderless text on the strip's own inset background, so
   nothing said it was a control until it was already chosen. Every weight here
   is one step up from the first attempt, which was too faint to see: the
   hairline is --phid-border and the selected tab answers with
   --phid-border-control, so the pair keeps its one-step separation and the
   selection still reads as the stronger edge. border-bottom stays 0
   throughout: the selected tab has to open onto the surface below it, and a
   bottom edge on the others would draw a second line directly above the
   strip's own. */
#ide-tabstrip .tab {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  font-size: var(--phid-text-sm);
  color: var(--phid-text-muted);
  cursor: pointer;
  white-space: nowrap;
  border: 1px solid var(--phid-border);
  border-bottom: 0;
  border-radius: 6px 6px 0 0;
  background: none;
}
/* Hover lifts the tab onto the card surface at the selected tab's border, so
   the gesture previews the result. */
#ide-tabstrip .tab:hover:not(.on) {
  background: var(--phid-surface-card);
  border-color: var(--phid-border-control);
  color: var(--phid-text);
}
#ide-tabstrip .tab:focus-visible {
  outline: 2px solid var(--phid-accent);
  outline-offset: -2px;
}
#ide-tabstrip .tab.on {
  background: var(--phid-surface-card);
  color: var(--phid-text);
  border-color: var(--phid-border-control);
}

/* --- documents in the workspace: About ------------------------------------ */
/* A DOCUMENT TAB IS THE SAME CONTROL, SET SLIGHTLY APART. About is prose, not a
   view of the open leaf, so it sits after the panels behind a rule rather than
   being coloured differently — the strip should read as one row of tabs, with
   the boundary showing where views of this result end. */
#ide-tabstrip .tab.is-doc {
  margin-left: var(--phid-space-3);
  position: relative;
}
#ide-tabstrip .tab.is-doc::before {
  content: "";
  position: absolute;
  left: calc(var(--phid-space-3) / -2 - 1px);
  top: 6px; bottom: 6px;
  border-left: 1px solid var(--phid-border);
}
/* ONE RULE, NOT ONE PER DOCUMENT — 2026-09-06. The separator marks the boundary
   between views of the open leaf and the documents that are not, so it belongs
   before the FIRST document tab only. Applied to every .is-doc it also drew a
   line between Data and About, which are the same kind of thing and read as a
   pair. */
#ide-tabstrip .tab.is-doc + .tab.is-doc { margin-left: 2px; }
#ide-tabstrip .tab.is-doc + .tab.is-doc::before { content: none; }

/* .doc-open — a document has the workspace. CSS owns the showing because
   app.js toggles .hidden on these same nodes from showView(); a class on the
   root settles it without either side knowing about the other, which is why
   !important appears here and nowhere else in this file.

   NOTHING IS UNMOUNTED. #panes is hidden and put back, so the open leaf, the
   running 3D scene and the IGV locus all survive — closing About is instant and
   returns the reader to exactly what they left. */
:root[data-shell="ide"].doc-open #panes { display: none; }
/* The IGV strip is navigation for a locus, and a document has none. Hidden, not
   unmounted: igv.js keeps its state and comes back on the same locus. */
:root[data-shell="ide"].doc-open #igv-strip { display: none; }
/* The viewer frame stays up even with no result open, because the tab strip
   lives inside it and the strip is the way back out of the document. */
:root[data-shell="ide"].doc-open #viewer-content { display: flex !important; }
:root[data-shell="ide"].doc-open #viewer-empty { display: none !important; }

/* ONE DOCUMENT AT A TIME, named by data-doc on the root. A document node is
   hidden by default and shown only when data-doc names it, so two can never be
   on screen at once — which they could if each had its own boolean.

   #about IS DELIBERATELY NOT LISTED HERE from 2026-09-11. It is no longer a
   document; it is a top-level view like #home, shown by app.js removing its
   .hidden class. Leaving it in the rule below would have hidden it forever
   under the shell — the shell is the default, data-doc would never name it,
   and the failure is silent: the nav link works, the URL changes, nothing
   appears. */
/* #browse LEFT THE LIST TOO, 2026-09-28, for the reason given above for #about:
   it is a full-page view now, shown by app.js removing .hidden, and the
   `display: none` that stood here hid it under the shell with no error - the
   URL changed to #data and the page stayed empty. The two data-doc rules below
   are kept for the day it becomes a document again, and match nothing today. */
/* The finder keeps its own internal layout (.browse-wrap sets the measure); the
   shell only gives it the column and lets it scroll inside it. */
:root[data-shell="ide"][data-doc="browse"] #browse {
  display: block !important;
  overflow: auto;
  min-height: 0;
}
/* LEFT-ALIGNED IN THE SHELL, like About — 2026-09-06. .browse-wrap centres
   itself with margin:0 auto for the full-page layout it was written for, which
   in a workspace column left the finder floating in the middle while every other
   document starts at the left edge. The max-width stays: a measure is still
   worth keeping, it just starts where the reader's eye already is. */
:root[data-shell="ide"][data-doc="browse"] #browse .browse-wrap {
  margin-left: 0;
  margin-right: 0;
  padding-left: var(--phid-space-4);
  padding-right: var(--phid-space-4);
}
/* app.js's showView hides #layout for the browse view — it predates the finder
   being a document, and the workspace (with the tab strip in it) must stay up. */
:root[data-shell="ide"].doc-open #layout { display: grid !important; }

/* CENTRED, safely. Both tracks are the panes' own widths now, so a window wider
   than the block leaves a remainder — split in two it reads as margin, where
   left-aligned it all collected against the inspector's border and read as a
   gap. `safe` so that a block WIDER than the workspace still starts at the left
   edge and stays reachable, rather than centring its overflow out of reach. */
:root[data-shell="ide"] #panes { justify-content: safe center; }

/* The pane grid, at the mockup's width budget: the three 2D views stack in a
   column of at most 404px and the scene takes everything left over, spanning
   their whole height. Metadata and the diagnostic pane are NOT here in this
   shell — ide_shell.js moves them into the bottom section. */
/* BOTH COLUMNS GROW. This was minmax(320px, 404px) for the left one, taken from
   the mockup's fixed width budget at 1440 — which meant the contact map, MSD and
   loss-tangent panels stayed at 404px however wide the window got and only the
   3D scene took the extra space. Reported as "only the 3D viewer changes size".

   Equal fractions instead, so the four panels grow and shrink together and the
   map column keeps pace with the scene. The rail and inspector toggles still
   give the scene more room — they now give it to every panel.

   The 404px figure is not lost, it moved: it is the FLOOR below which the maps
   stop being readable, so it is the minmax minimum on a wide layout and drops to
   280px before the columns stack. */
:root[data-shell="ide"] #panes {
  display: grid;
  grid-template-columns: minmax(280px, 1fr) minmax(280px, 1fr);
  column-gap: 16px; row-gap: 16px;
  padding: 12px 16px;
  align-items: start;
  width: auto; max-width: none;
}
:root[data-shell="ide"] #panes #msd-lt-stack { display: contents; }
:root[data-shell="ide"] #panes #heatmap-pane  { grid-column: 1; grid-row: 1; }
:root[data-shell="ide"] #panes #msd-pane      { grid-column: 1; grid-row: 2; }
:root[data-shell="ide"] #panes #lt-pane       { grid-column: 1; grid-row: 3; }
:root[data-shell="ide"] #panes #dynamics-pane { grid-column: 2; grid-row: 1 / span 3; }

/* --- THE WORKSPACE FITS THE COLUMN — no page scroll ------------------------
   An experiment, at the maintainer's request: size the four panels to the space
   that is actually there rather than to a 1:1 ratio against the viewport, so
   the workspace never scrolls.

   The chain is height-first, which is the reverse of the plain page. #viewer
   stops scrolling and becomes a flex column; #viewer-content fills it; the
   sticky head keeps its natural height; #panes takes what is left and divides
   it into three equal rows. Each drawing area then takes its HEIGHT from its row
   and its WIDTH from its own aspect ratio — so a short window gives smaller
   maps rather than a scrollbar.

   WHAT THIS COSTS, stated because it is a real trade: the 3D scene spans all
   three rows and gets the full column height, which is far more than a 1:1 map
   beside it can use, so the right column is generously empty. The maintainer's
   view is that this is the better design and I agree — the alternative is a
   workspace that scrolls, and a map you have to scroll to see is worse than a
   map with space around it. Revert by deleting this block; the fit layout's own
   rules are untouched underneath.

   THE MAPS STAY LEFT-ALIGNED AND EQUAL. The contact map, MSD and loss tangent
   share the genomic axis, so they must end up the same width — they get equal
   rows and the same aspect handling, which is what keeps that true. */
:root[data-shell="ide"] #viewer {
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
:root[data-shell="ide"] #viewer-content {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
:root[data-shell="ide"] #viewer-sticky { flex: 0 0 auto; }
/* ROWS 2 : 1 : 1, WHICH IS WHAT KEEPS THE THREE MAPS THE SAME WIDTH.
   The contact map is 1:1 and the two rheology maps are 2:1, so to finish at one
   shared width the square one needs twice the height of each of the others.
   Equal rows were what distorted them: the drawing area was stretched to fill
   its row and the ratio went with it.

   This is not cosmetic. The contact map, MSD and loss tangent share the GENOMIC
   AXIS, so they have to end up the same width or the axis reads at three
   different scales down the column — the misalignment this layout is careful
   about everywhere else. Equal width now falls out of the row proportions
   instead of being asserted. */
:root[data-shell="ide"] #panes {
  flex: 1 1 auto;
  min-height: 0;
  grid-template-rows: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr);
  align-items: stretch;
  /* SCROLLS RATHER THAN CLIPS — 2026-09-06. fitPanes now honours the 300px pane
     floor from browse.css, so in a short window the solved rows are taller than
     the space and the remainder has to go somewhere. It was `hidden`, which cut
     the loss-tangent map off at the bottom edge with no way to reach it. */
  overflow-y: auto;
  overflow-x: hidden;
}
:root[data-shell="ide"] #panes .pane {
  min-height: 0;
  overflow: hidden;
  /* flex-start, not stretch: a drawing area takes its width from its own ratio,
     so stretching it to the pane's width is exactly the distortion being fixed.
     It also keeps the three maps LEFT-aligned, which the shared axis needs. */
  align-items: flex-start;
}
/* The head is fixed, the drawing area takes the rest of the row. */
:root[data-shell="ide"] #panes .pane > .pane-head {
  flex: 0 0 auto;
  /* SPANS THE PANE, so the whole title bar selects the panel — 2026-09-06. The
     pane is align-items:flex-start (a drawing area must not be stretched off its
     ratio), which also shrank the head to the width of its text: the empty space
     to the RIGHT of "2D contact map" belonged to the pane, not to the head, and
     clicking there did nothing. align-self overrides that for the head alone and
     leaves the drawing area's alignment untouched. */
  align-self: stretch;
}
/* THE FOUR DRAWING AREAS ARE SIZED IN JS, NOT HERE — see fitPanes() in
   ide_shell.js. This block used to do it in CSS and could not.

   THE DEFECT WAS CIRCULAR SIZING. The pane was width:auto — shrink to your
   content — while the drawing area inside it was also width:auto with
   max-width:100% — fit your parent. Each depended on the other, so the browser
   resolved a circular constraint however it liked. That is why the map ended up
   drawn small in the corner of a much larger grey box, and why every further CSS
   adjustment produced a different wrong answer instead of converging.

   It is not expressible in CSS. A ratio inside a box that must satisfy BOTH a
   width limit and a height limit needs one of the two to be known, and the row
   height here comes from fr units a stylesheet cannot read back. So it is
   measured: reset, read, set. Deterministic, and it ends the argument.

   Everything that is not arithmetic stays here. */
:root[data-shell="ide"] #panes .pane > .canvas-wrap { margin-right: auto; }
/* A title must not widen a pane past its map — the pane's width is set from the
   map, so the head has to be able to shrink into it. */
:root[data-shell="ide"] #panes .pane-head > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The 3D pane sits at the top of its three-row span rather than centring in it,
   so its head lines up with the contact map's beside it. */
:root[data-shell="ide"] #panes #dynamics-pane { align-self: start; }


/* THE HEIGHT CAP IS LEFT ALONE, deliberately. style.css caps every drawing area
   at min(100%, calc(100vh - 300px)) so a 1:1 map cannot run below the fold. The
   shell does spend more vertical space than the plain page — a tab strip above
   the panes, a metadata section below — so a tighter cap would be defensible.
   It is not applied: at any ordinary window the COLUMN WIDTH is what binds
   (around 420px against the cap's 780px on a 1080p screen), so tightening the
   cap would change nothing there and would only shrink the maps on a SHORT
   window, which is the opposite of what was asked for. If a map ever runs past
   the visible column, the column scrolls — which it does anyway. */

/* The selected pane is the one the inspector is pointing at. */
:root[data-shell="ide"] #panes .pane { cursor: default; }
:root[data-shell="ide"] #panes .pane.ide-sel {
  border-color: var(--phid-accent);
  box-shadow: 0 0 0 1px var(--phid-accent);
}
/* The head keeps the panel's name and loses its controls to the inspector, so
   it is a title bar rather than a wrapping control strip. */
:root[data-shell="ide"] #panes .pane-head { cursor: pointer; }

/* --- the bottom section --------------------------------------------------- */
/* Full width, above the status bar: a second inspector that sits along the
   bottom rather than down the side. It has its own border and background so it
   reads as a section of the shell, not as part of the workspace above it. */
/* 300px, fixed — 2026-09-06, the maintainer's call, replacing max-height:34vh.
   A share of the viewport meant the metadata section grew on a tall screen and
   took height the workspace wanted; a fixed height gives the panes everything
   else. It scrolls when the record is longer, which is what overflow:auto was
   already for. */
#ide-bottom {
  margin: 0;
  border-top: 1px solid var(--phid-border);
  background: var(--phid-surface-card);
  height: var(--ide-bottom-h);
  overflow: auto;
  min-height: 0;
}
#ide-bottom .tabs {
  display: flex; gap: 2px;
  font-size: var(--phid-text-sm);
  padding: 4px var(--phid-space-4) 0;
  position: sticky; top: 0;
  background: var(--phid-surface-card);
  border-bottom: 1px solid var(--phid-border-subtle);
  z-index: 1;
}
/* THE SAME HAIRLINE THE WORKSPACE TABS CARRY — 2026-09-06. Metadata and JSON
   were borderless text, so the bottom section looked like a heading with a word
   beside it rather than a strip of tabs. Weights match #ide-tabstrip exactly:
   --phid-border unselected, --phid-border-control selected and on hover. The
   two strips are the same control and now read as it. */
#ide-bottom .tabs .t {
  padding: 4px 10px;
  color: var(--phid-text-muted);
  cursor: pointer;
  border-radius: 6px 6px 0 0;
  border: 1px solid var(--phid-border);
  border-bottom: 0;
  background: none;
  font: inherit;
  font-size: var(--phid-text-sm);
}
/* No surface change on hover, unlike the workspace tabs: this strip is already
   on the card surface, so the border and the text colour carry it alone. */
#ide-bottom .tabs .t:hover:not(.on) {
  border-color: var(--phid-border-control);
  color: var(--phid-text);
}
#ide-bottom .tabs .t:focus-visible {
  outline: 2px solid var(--phid-accent);
  outline-offset: -2px;
}
#ide-bottom .tabs .t.on {
  background: var(--phid-surface-card);
  color: var(--phid-text);
  border-color: var(--phid-border-control);
}
#ide-bottom .tabs .t.diag { color: var(--phid-accent); }
#ide-bottom .body { padding: var(--phid-space-3) var(--phid-space-4); }
/* WHAT IS MOVED IN HERE STOPS BEING A PANE. #metadata-panel and #diag-pane are
   .pane elements — a card with its own border, background and shadow — so
   dropped into this section they read as a card floating inside a panel rather
   than as the panel's content. Reported as "that bottom pane seems to be just
   floating on the entire page". Their ids and their nodes are untouched; only
   the pane costume goes. */
#ide-bottom .pane {
  border: 0; background: none; box-shadow: none; border-radius: 0;
  margin: 0; padding: 0; width: auto; max-width: none; height: auto;
}
#ide-bottom .pane .pane-head { display: none; }
#ide-bottom .pane[hidden] { display: none; }
#ide-bottom #diag-pane pre,
#ide-bottom #diag-pane code {
  margin: 0;
  font: 400 11px/1.6 var(--phid-font-mono);
  color: var(--phid-text);
  white-space: pre;
  overflow-x: auto;
  display: block;
}
/* The metadata and diagnostic panes keep their ids and their own markup — they
   are moved here, not rebuilt — so they must stop looking like panes. */
#ide-bottom .pane {
  border: 0; background: none; box-shadow: none; border-radius: 0;
  margin: 0; padding: 0; width: auto; height: auto;
}
#ide-bottom .pane .pane-head { display: none; }
#ide-bottom .pane[hidden] { display: none; }
#ide-bottom #diag-pane pre,
#ide-bottom #diag-pane code {
  margin: 0;
  font: 400 11px/1.6 var(--phid-font-mono);
  color: var(--phid-text);
  white-space: pre;
  overflow-x: auto;
  display: block;
}

/* --- right: the inspector -------------------------------------------------- */
:root[data-shell="ide"] #ide-inspector {
  background: var(--phid-surface-card);
  border-left: 1px solid var(--phid-border);
  overflow: auto;
  display: flex;
  flex-direction: column;
  min-width: 0;
}
#ide-inspector .ide-head {
  padding: var(--phid-space-3);
  border-bottom: 1px solid var(--phid-border-subtle);
  display: flex;
  align-items: flex-start;
  gap: var(--phid-space-2);
  flex: none;
}
#ide-inspector .ide-head .htext { flex: 1; min-width: 0; }
#ide-inspector .ide-head .title {
  font-size: var(--phid-text-xs);
  
  letter-spacing: var(--phid-tracking-caps);
  color: var(--phid-text-muted);
}
#ide-inspector .ide-head .ctx { font-weight: var(--phid-weight-medium); margin-top: 2px; }
#ide-ctlbody { padding: var(--phid-space-3); }
#ide-ctlbody .ide-empty { color: var(--phid-text-muted); font-size: var(--phid-text-sm); }

/* The controls are the panel's OWN nodes, moved here rather than rebuilt, so
   every listener app.js bound by id keeps working. They were laid out for a
   wide pane head, so the column re-flows them: one control per line, label
   above, and the .ctl-row grouping the 3D panel already carries becomes a
   titled block. */
#ide-ctlbody .controls { display: flex; flex-direction: column; gap: var(--phid-space-2); }
#ide-ctlbody .controls > label,
#ide-ctlbody .ctl-row > label {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
  font-size: var(--phid-text-sm);
  color: var(--phid-text-muted);
}
#ide-ctlbody .ctl-row {
  display: flex;
  flex-direction: column;
  gap: var(--phid-space-2);
  padding-bottom: var(--phid-space-3);
  margin-bottom: var(--phid-space-1);
  border-bottom: 1px solid var(--phid-border-subtle);
}
#ide-ctlbody .ctl-row:last-child { border-bottom: 0; }
#ide-ctlbody .ctl-row-label {
  /* FLEX-BASIS IS A HEIGHT HERE — 2026-09-06. style.css gives this span
     `flex: 0 0 68px` to hold a 68px LABEL COLUMN in the pane head, where the row
     runs horizontally. The inspector stacks the same row as a column, so that
     basis became 68px of height and left an empty band under every section name:
     Simulation, Strand, Colour, Scene, Labels. Reported by the maintainer as
     "huge space on each of the sections at the beginning".
     Content height, and the row's own gap does the spacing. */
  flex: 0 0 auto;
  font-size: var(--phid-text-xs);
  letter-spacing: .02em;
  color: var(--phid-text-muted);
  font-weight: var(--phid-weight-medium);
}
#ide-ctlbody select,
#ide-ctlbody input[type="number"],
#ide-ctlbody input[type="text"] {
  width: 100%;
  min-width: 0;
  font: inherit;
  font-size: var(--phid-text-sm);
  color: var(--phid-text);
  background: var(--phid-surface-card);
  border: 1px solid var(--phid-border-control);
  border-radius: var(--phid-radius-sm);
  padding: 3px 6px;
}
#ide-ctlbody input[type="range"] { width: 100%; accent-color: var(--phid-accent); }
/* .rng-num pairs a number box with its track on one line in the pane head; in a
   300px column the pair still fits and reads better than two lines. */
#ide-ctlbody .rng-num { display: grid; grid-template-columns: 5.5em 1fr; gap: 3px var(--phid-space-2); align-items: center; }
#ide-ctlbody .rng-num > input[type="number"] { width: 100%; }
/* The two-handle track's flex-basis is a WIDTH in the pane head's row; in this
   column it would be read as a height, and opened 200 px of empty space. */
#ide-ctlbody .dual-bounds { flex: none; min-width: 0; }
#ide-ctlbody .dual-bounds .bound input[type="number"] { width: 100%; }
#ide-ctlbody button {
  font: inherit;
  font-size: var(--phid-text-sm);
  cursor: pointer;
  padding: 4px 9px;
  border-radius: var(--phid-radius-sm);
  border: 1px solid var(--phid-border-control);
  background: var(--phid-surface-card);
  color: var(--phid-text);
}
#ide-ctlbody button:hover:not(:disabled) { background: var(--phid-surface-hover); }
#ide-ctlbody button:disabled { opacity: .5; cursor: default; }

/* The inspector's own collapse button and rail are gone: the footer owns that
   toggle now. #ide-insp-rail stays as a hidden label so nothing that writes the
   panel name into it has to know. */
#ide-insp-rail { display: none; }

/* A CONTROL BLOCK THAT IS NOT THE SELECTED PANEL'S. Hidden, never detached —
   app.js finds every control by getElementById and would not see a detached
   node, so parking them outside the document would break the page on load.
   !important because .controls carries display:flex from style.css. */
#ide-ctlbody .ide-ctl-off { display: none !important; }

/* --- the footer ------------------------------------------------------------ */
/* The status line. It carries what the accession caption used to say plus the
   run parameters, so the centre column spends none of its height on them. */
:root[data-shell="ide"] #ide-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--phid-space-4);
  padding: 0 var(--phid-space-4);
  background: var(--phid-surface-inset);
  border-top: 1px solid var(--phid-border);
  font: var(--phid-text-xs)/1 var(--phid-font-mono);
  color: var(--phid-text-muted);
  overflow: hidden;
}
#ide-footer > span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
#ide-footer b { color: var(--phid-text); font-weight: var(--phid-weight-medium); }

/* The three collapse toggles, at the right end of the status bar. They are the
   only controls in the shell that must never be inside the thing they control. */
#ide-f-toggles {
  margin-left: auto;
  display: flex;
  gap: 2px;
  flex: none;
  align-items: center;
}
/* Labelled, not glyph-only. Three unlabelled icons — two of which were the same
   character — could not be told apart, and the file-browser toggle was read as
   missing. The word is the control; the mark is decoration beside it. */
#ide-f-toggles .ide-iconbtn {
  width: auto; height: 22px;
  padding: 0 6px;
  gap: 4px;
  display: inline-flex;
  align-items: center;
  font-size: 11px;
}
#ide-f-toggles .ide-iconbtn b {
  font-family: var(--phid-font-mono);
  font-weight: var(--phid-weight-medium);
  letter-spacing: .02em;
}
#ide-f-toggles .ide-iconbtn[aria-pressed="false"] { opacity: .45; }
#ide-f-toggles .ide-iconbtn[aria-pressed="true"] { color: var(--phid-accent); }

/* --- drag a border to resize (#53) --------------------------------------- */
/* A thin strip on the inner border of the directory, the inspector and the
   metadata section. They are grid items of #layout, laid over the edge of their
   section's cell, rather than children of the sections: those scroll, and a
   handle inside one would scroll away with its content. The strip straddles the
   border by half its width and shows a line on hover and while dragged.
   ide_shell.js sets --ide-*-size on the root; hiding a section still wins, since
   .hide-* sets --ide-*-w itself. */
/* Above the viewer's sticky tab strip (40), below the narrow-screen inspector (50). */
:root[data-shell="ide"] .ide-sizer { position: relative; z-index: 45; touch-action: none; }
:root[data-shell="ide"] .ide-sizer::after {
  content: ''; position: absolute; background: var(--phid-accent);
  opacity: 0; transition: opacity .12s ease;
}
:root[data-shell="ide"] .ide-sizer:hover::after,
:root[data-shell="ide"] .ide-sizer.on::after { opacity: .6; }
:root[data-shell="ide"] #ide-sizer-browser,
:root[data-shell="ide"] #ide-sizer-inspector { grid-row: 1; width: 8px; cursor: col-resize; }
:root[data-shell="ide"] #ide-sizer-browser { grid-column: 2; justify-self: end; margin-right: -4px; }
:root[data-shell="ide"] #ide-sizer-inspector { grid-column: 4; justify-self: start; margin-left: -4px; }
:root[data-shell="ide"] #ide-sizer-browser::after,
:root[data-shell="ide"] #ide-sizer-inspector::after { top: 0; bottom: 0; left: 3px; width: 2px; }
:root[data-shell="ide"] #ide-sizer-bottom {
  grid-column: 1 / -1; grid-row: 2; align-self: start;
  height: 8px; margin-top: -4px; cursor: row-resize;
}
:root[data-shell="ide"] #ide-sizer-bottom::after { left: 0; right: 0; top: 3px; height: 2px; }
:root[data-shell="ide"].hide-browser #ide-sizer-browser,
:root[data-shell="ide"].hide-inspector #ide-sizer-inspector,
:root[data-shell="ide"].hide-bottom #ide-sizer-bottom { display: none; }
/* While dragging: the size follows the pointer at once rather than through the
   column transition, the cursor stays the resize cursor over whatever it crosses,
   and no text is selected on the way. */
:root[data-shell="ide"].ide-resizing #layout { transition: none; }
:root[data-shell="ide"].ide-resizing,
:root[data-shell="ide"].ide-resizing * { user-select: none; }
:root[data-shell="ide"].ide-resizing-x * { cursor: col-resize !important; }
:root[data-shell="ide"].ide-resizing-y * { cursor: row-resize !important; }

/* --- narrow screens -------------------------------------------------------- */
/* Step 1 — a narrow window opens with the file browser put away. THE RULE IS
   NOT HERE: ide_shell.js reads the same breakpoint once at startup and adds
   .hide-browser, which is the class the footer button toggles. A media query
   that also hid the column fought the button — below 1280px the button could
   never bring it back — so the breakpoint chooses the starting value and the
   button owns the state from then on. */
/* Step 2 — the inspector leaves the grid and slides over the centre. */
@media (max-width: 1100px) {
  /* The inspector slides over the centre at a fixed width: no border to drag. */
  :root[data-shell="ide"] #ide-sizer-inspector { display: none; }
  :root[data-shell="ide"] #layout {
    grid-template-columns: var(--ide-appbar-w) var(--ide-browser-w) minmax(0, 1fr);
  }
  :root[data-shell="ide"] #ide-inspector {
    position: fixed;
    top: var(--ide-topbar-h); bottom: var(--ide-footer-h); right: 0;
    width: 300px; z-index: 50;
    transform: translateX(100%);
    transition: transform .18s ease;
    box-shadow: var(--phid-shadow-pop);
  }
  :root[data-shell="ide"].insp-open #ide-inspector { transform: none; }
  :root[data-shell="ide"] #ide-insp-toggle { display: inline-flex; }
  :root[data-shell="ide"].insp-open #ide-insp-toggle {
    background: var(--phid-accent-wash);
    border-color: var(--phid-accent-edge);
    color: var(--phid-accent);
  }
}
/* Step 3 — one column, which is what the site does today. */
@media (max-width: 640px) {
  /* One column scrolls as a page: there is no border left to drag. */
  :root[data-shell="ide"] .ide-sizer { display: none; }
  :root[data-shell="ide"] #layout {
    grid-template-columns: var(--ide-appbar-w) minmax(0, 1fr);
    height: auto;
    /* height:auto ALONE DOES NOTHING HERE. #layout is a flex item of the page
       column, at flex:1 1 0%, so the flex layout sized it to the viewport and the
       declaration above was overridden — 820px of box holding 2291px of content,
       which spilled the panes over the metadata section and the status bar
       (2026-09-06). Taking it out of the flex sizing is what lets the DOCUMENT
       grow, which is the intent of this whole breakpoint. */
    flex: 0 0 auto;
  }
  :root[data-shell="ide"] #ide-browser { display: none; }
  :root[data-shell="ide"] #viewer { grid-column: 2; }
  :root[data-shell="ide"] #ide-bottom { max-height: none; }
  :root[data-shell="ide"] #viewer { overflow: visible; }
  /* NOT A GRID AT THIS WIDTH. The two-column layout's whole job is to solve four
     drawing areas against each other; with one column there is nothing to solve,
     and the three-row 2fr/1fr/1fr template — asked to hold four panes — collapsed
     every map to 2px with the scene drawn over them (2026-09-06, found by
     sweeping 640, 560 and 420px). As blocks they simply stack at their own
     heights and the page scrolls, which is what this breakpoint already does
     with #viewer set to overflow:visible. */
  /* THE PAGE GROWS AT THIS WIDTH, THE WORKSPACE DOES NOT SCROLL. #viewer is a
     flex column that hands #panes a slice of the height it was given, so making
     the panes overflow:visible without this spilled a 2000px stack out of a
     235px box and straight over the metadata section and the footer — reported
     by the maintainer, 2026-09-06. As blocks, all three take their content
     height, #layout is already height:auto here, and the document scrolls. */
  :root[data-shell="ide"] #viewer,
  :root[data-shell="ide"] #viewer-content {
    display: block;
    overflow: visible;
  }
  :root[data-shell="ide"] #panes {
    display: block;
    overflow: visible;
  }
  :root[data-shell="ide"] #panes .pane { margin-bottom: var(--phid-space-3); }
  :root[data-shell="ide"] #panes #heatmap-pane,
  :root[data-shell="ide"] #panes #msd-pane,
  :root[data-shell="ide"] #panes #lt-pane,
  :root[data-shell="ide"] #panes #dynamics-pane { grid-column: 1; grid-row: auto; }
}
/* Hidden until the inspector becomes an overlay — see the 1100px block above.
   margin-left:auto puts it at the right end of the tab strip. */
#ide-insp-toggle {
  display: none;
  margin-left: auto;
  align-items: center;
  flex: none;
  font: inherit;
  font-size: var(--phid-text-xs);
  cursor: pointer;
  padding: 2px 9px;
  border: 1px solid var(--phid-border-control);
  border-radius: var(--phid-radius-sm);
  background: var(--phid-surface-card);
  color: var(--phid-text);
}
