/* ====================================================================
   WCPE INSTRUCTOR STUDIO — STAGE / WINDOW ACTIONS / 16:9 LETTERBOX
   ====================================================================
   Cascade layer 4 of 7. Owns regions 6, 8-9 — window-action cluster, role
   views / work grid / stage, and the 16:9 letterbox contract.
   Region 6 has been RE-RULED since the mockup (R3, Tor 2026-07-30: ONE verb per
   cluster, not three; Full screen and Pop out retired), and region 9's camera
   "Online" badge was removed by ruling 54a. The shipped banners are the rule.

   LOAD ORDER IS LOAD-BEARING:
     tokens -> base -> shell -> stage -> panels -> producer -> responsive
   This file is #4. Nothing here may depend on a rule that loads later.
   The numbered region banners below are this file's own. The numbering is one
   flat sequence 1-22 shared across the seven cascade files and no two files
   repeat a number; it was inherited from the concept-6 "Studio Rail" mockup the
   cascade was first extracted from. That mockup is deleted and these banners
   are the source now. Region map: docs/CODEBASE.html#style-css-regions
   ==================================================================== */
/* ====================================================================
   6 · WINDOW-ACTION CLUSTER — R3 (Tor, 2026-07-30): one verb each
   Every cluster holds ONE quiet 44px button. Compact homes get Focus
   (#i-focus); the stage header gets the single expand-to-fill control
   (#i-fullscreen, the diagonal-arrows glyph — it reads as "expand").
   Compact tiles reveal their cluster on hover or focus-within, and
   touch devices always show it (the hover:none query below).
   .is-active marks the focused home / active fill mode. Full screen
   and Pop out are RETIRED — never revive them and never add verbs.
   ==================================================================== */
.window-actions{
  margin-left:auto;
  display:flex;
  align-items:center;
  justify-content:flex-end;
  gap:4px;
  /* An action overlay must WIN the paint order over the panel it floats
     beside: with both at z:auto the later-in-DOM panel painted over the
     Expand control the moment the 200%-zoom phone tier made them share
     pixels (the panel scrolls under this cluster there; measured
     2026-08-10, elementFromPoint at Expand's center hit the article). */
  position:relative;
  z-index:8;
}
.window-actions[hidden]{display:none}
.window-action{
  position:relative;
  width:44px;
  min-width:44px;
  min-height:44px;
  padding:8px;
  display:grid;
  place-items:center;
  border:1px solid var(--line);
  border-radius:var(--control-radius);
  background:color-mix(in srgb,var(--raised) 82%,transparent);
}
.window-action:hover,.window-action:focus-visible{border-color:var(--cyan);box-shadow:var(--glow)}
.window-action.is-active{border-color:var(--cyan);background:var(--blue);box-shadow:var(--glow)}
.window-action svg{width:21px;height:21px;fill:var(--cyan)}
/* ====================================================================
   8 · ROLE VIEWS, WORK GRID, STAGE + COMPACT TABS
   One .role-view per role (.is-active shows it). Compact grid rows:
   52px switcher · flexible stage · 56px utility dock. The stage shell
   pairs its header (title + knock alert + window-action cluster) with
   .stage-cell, a container-query box that bounds the active panel;
   panels swap via .is-active with a 200ms focus-in ease (motion removed
   under prefers-reduced-motion — the state change itself never is).
   .feature-switcher = compact Slides/Camera/Script tabs;
   .utility-dock = compact Marks/Transcript tabs. DOM-order contract:
   stage content comes before both tab bars (content first, navigation
   second — the static contract checks this).
   ==================================================================== */
/* Item-32 audit — the OUTER-HULL clip family: .role-view, .work-grid,
   .feature-stage, .stage-cell and .feature-panel are the frozen shell's
   nested layout boxes. Their overflow:hidden is the page-never-scrolls
   premise, and it is legitimate ONLY because every content region inside
   them either grows with its content or scrolls with its own scrollbar
   (panels.css); the ≤240px band releases all of them (responsive.css 18b).
   Never park content against these edges without an interior scrollport. */
.role-view{display:none;min-width:0;min-height:0;overflow:hidden}
/* Accessibility beats mockup fidelity
   (docs/CODEBASE.html#style-accessibility-over-fidelity). The mockup states the
   switcher, the utility dock and the transport as constant row heights, because
   at its own text size that is exactly what they measure. All three are rows of
   labelled controls, so all three grow with the text while a constant cannot:
   at 200% text zoom the transport's two-line "Drop marker" wanted 64px of its
   58px and the tab labels the same, and every one of them was clipped by an
   ancestor that had already committed to the constant. A floor plus `auto`
   reproduces the mockup's rendering exactly at the design's text size and lets
   each row take the height its own controls need past it. The stage is the 1fr
   that pays, which is the 200% zoom gate's ruling in its own words
   (docs/CODEBASE.html#style-zoom-and-reflow): a letterboxed stage that
   shrinks is correct, a control the user cannot reach is not. */
.role-view.is-active{
  display:grid;
  grid-template-rows:minmax(0,1fr) minmax(var(--transport-h),auto);
  /* A size container so responsive.css can ask the work area the one question a
     media query cannot answer: how much room is left here measured in the text
     this view is currently carrying. The rail already does this at the other end
     of the shell; see the studio-rail container and its 36em fallback. It sits on
     the view rather than on .work-grid because a container query styles the
     container's descendants and never the container itself, and the rule that
     has to change is the work grid's own template. */
  container-type:size;
  container-name:studio-work;
}
.work-grid{
  min-width:0;
  min-height:0;
  padding:8px;
  display:grid;
  grid-template-rows:minmax(52px,auto) minmax(0,1fr) minmax(56px,auto);
  gap:8px;
  overflow:hidden;
}
/* Accessibility beats mockup fidelity
   (docs/CODEBASE.html#style-accessibility-over-fidelity). Three equal columns is a
   count, and a count cannot know how wide "Camera" is: at 200% text zoom the word
   alone wanted 104px of the 97px column and spilled over the next tab even after
   the tab itself wrapped. A wrapping row asks the question the other way round —
   the tabs keep equal shares of one line while their own min-content still fits,
   and step down to two lines when it stops fitting. At the design's text size the
   min-contents are a third of what the row has, so the mockup's 3-up row is what
   renders; the count is reproduced rather than declared. */
.feature-switcher{
  min-width:0;
  grid-row:1;
  display:flex;
  flex-wrap:wrap;
  gap:6px;
}
/* Accessibility beats mockup fidelity
   (docs/CODEBASE.html#style-accessibility-over-fidelity). Glyph and label share one
   line in the mockup because "Transcript" fits beside its icon at seed text size.
   At 200% text zoom it does not, and a one-word label has no break point to wrap
   at, so the label rendered straight out across the neighbouring tab — nothing
   clipped and nothing shrunk, just two labels drawn on top of each other. Letting
   the tab wrap puts the label on its own line under the glyph, which drops the
   tab's content width to the word alone and fits; the row above grows to match. */
.feature-switcher button,.utility-dock button{
  min-width:0;
  min-height:48px;
  padding:7px;
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  align-content:center;
  justify-content:center;
  gap:6px;
  border-radius:var(--control-radius);
  font-size:15px;
  font-weight:900;
  line-height:1.05;
  white-space:normal;
  overflow-wrap:normal;
  word-break:normal;
}
/* Equal shares of the row while every tab's own min-content still fits, and a
   wrapped row the moment one stops — `min-width:auto` is what makes the flex
   line break on the label instead of letting it spill (see the row above). */
.feature-switcher button{flex:1 1 0;min-width:auto}
.feature-switcher button svg,.utility-dock button svg{width:22px;height:22px;fill:var(--cyan);flex:0 0 auto}
.feature-switcher button.is-active,.utility-dock button.is-active{
  border:2px solid var(--cyan);
  background:var(--blue);
  box-shadow:var(--glow);
}
.utility-dock{
  min-width:0;
  grid-row:3;
  display:grid;
  grid-template-columns:repeat(2,minmax(0,1fr));
  gap:6px;
}
.feature-stage{
  position:relative;
  grid-row:2;
  min-width:0;
  min-height:0;
  padding:8px;
  display:grid;
  grid-template-rows:auto minmax(0,1fr);
  gap:6px;
  overflow:hidden;
  border:2px solid var(--steel);
  border-radius:var(--major-radius);
  background:linear-gradient(145deg,rgba(15,27,48,.98),rgba(5,8,15,.97));
  box-shadow:inset 0 0 48px rgba(7,76,142,.18);
}
/* Accessibility beats mockup fidelity
   (docs/CODEBASE.html#style-accessibility-over-fidelity; open item 15). The
   frozen header is a single flex row; at 200% text zoom on narrow compact
   viewports (390x844, 410x590, 422x195) the heading's text outgrows the
   space left beside the fixed-width window-action cluster (140px when it
   held three verbs; one 44px button since R3 — the wrap stays because the
   same squeeze still exists, just later), and because the
   h2 carries min-width:0 its glyphs painted straight across the cluster's
   buttons (measured: 104px of "Presentation" behind the actions at
   producer@390x844). Truncation is forbidden and "Presentation" is one
   unbreakable word, so the row wraps instead: while heading + cluster fit
   on one line this renders the mockup's row exactly, and when they no
   longer fit the cluster drops to its own line — the stage cell below
   absorbs the growth (its row is minmax(0,1fr)), which is the zoom gate's
   "the letterbox yields" outcome
   (docs/CODEBASE.html#style-zoom-and-reflow). Except when the stage has no
   height to give: see the pinched-stage exception right below the h2 rule. */
.stage-header{
  min-width:0;
  min-height:42px;
  display:flex;
  flex-wrap:wrap;
  align-items:center;
  gap:9px;
}
.stage-header h2{
  min-width:0;
  margin:0;
  font-size:21px;
  line-height:1.1;
  overflow-wrap:normal;
  word-break:normal;
}
/* The pinched-stage exception to the wrap above (accessibility over mockup
   fidelity, docs/CODEBASE.html#style-accessibility-over-fidelity; open item
   15). On a short-landscape window, and in the 18a compact band where the
   work area is under 14em of its own text (responsive.css measures the same
   container), a second header row is paid for by the one row that holds
   the 16:9 surface — measured at 410x590 @200% text, the wrap left the
   stage cell 4px and pushed the letterbox out through the cell's clip.
   There the row stays single and the heading scrolls INSIDE ITS OWN BOX —
   the sanctioned form under the long-token ruling
   (docs/CODEBASE.html#style-long-token-policy: "a wide element scrolls
   inside its own container"): nothing truncated, nothing hidden, no glyphs
   painted over the action cluster, and the surface keeps the height the wrap
   would have spent. nowrap keeps a two-word title from re-growing the row. */
@media(max-height:520px){
  .stage-header{flex-wrap:nowrap}
  .stage-header h2{overflow-x:auto;white-space:nowrap}
}
@container studio-work (max-height:14em){
  .stage-header{flex-wrap:nowrap}
  .stage-header h2{overflow-x:auto;white-space:nowrap}
}
.knock-alert{
  min-width:72px;
  min-height:44px;
  padding:7px 9px;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:5px;
  border:2px solid var(--red);
  border-radius:var(--control-radius);
  background:rgba(255,59,107,.13);
  font-size:15px;
  font-weight:900;
  white-space:nowrap;
  box-shadow:0 0 18px -10px var(--red);
}
.knock-alert svg{width:20px;height:20px;fill:var(--red)}
.stage-cell{
  min-width:0;
  min-height:0;
  display:grid;
  overflow:hidden; /* item-32 audit: outer-hull family — see .role-view */
  container-type:size;
}
.feature-panel{
  min-width:0;
  min-height:0;
  display:none;
  overflow:hidden;
}
.feature-panel.is-active{
  display:grid;
  animation:stageFocusIn .2s cubic-bezier(.2,.8,.2,1) both;
}
/* A LIVE ANNOUNCEMENT OUTRANKS A CONVENIENCE CONTROL. History, in full,
   because this rule was wrong once and its justification wrong twice:
   z-index:9 on the PANEL did nothing — .stage-cell's container-type:size
   imposes layout containment, making the CELL a stacking context no panel
   z-index can leave — so the rule lifts the CELL that contains an open knock
   card. Proof is by MEASURED PAINT ORDER, not z arithmetic: with a real
   visible overlap engineered, elementFromPoint resolves into the card
   (review-verified, three samples). And the "cluster painted over the open
   announcement" claim an earlier version of this comment carried is
   DISSOLVED: at the 200%-zoom phone tier the card's paint is clipped by the
   cell to a band the cluster never touches (15.5px gap, measured) — the
   border-box overlap everyone kept probing is a region the card cannot paint.
   The card is dismissible, so the corner returns to the cluster when the
   announcement is answered. The real open item at that tier is the card being
   clipped to ~195px of its ~547px zoomed box — pre-existing, pinned sizes,
   verified against the pre-harmonisation stylesheets. */
.stage-cell:has([data-knock-card]:not([hidden])){position:relative;z-index:9}
@keyframes stageFocusIn{
  from{opacity:.42;transform:translateX(12px) scale(.985)}
  to{opacity:1;transform:none}
}
/* ====================================================================
   9 · VISUAL PANELS — the 16:9 LETTERBOX CONTRACT
   .visual-surface is always 16:9 and always fully inside its cell:
   aspect-ratio + max bounds, refined by container queries (cqh) so the
   box fits at ANY window size — never scrolled, never clipped, never
   distorted. Slide type scales with cqw clamps so a slide reads like a
   slide at every size. The camera frame keeps its cyan glow; its badge
   says Online only (one home per fact — no self-name duplication).
   ==================================================================== */
.visual-panel{place-items:center}
.presentation-panel.is-active{grid-template-rows:minmax(0,1fr) auto;gap:7px}
[data-role="producer"] .presentation-panel.is-active{grid-template-rows:minmax(0,1fr);gap:0}
.visual-surface{
  position:relative;
  min-width:0;
  min-height:0;
  width:100%;
  height:auto;
  max-width:100%;
  max-height:100%;
  /* Safe centring, because the letterbox has a floor it cannot go under: its own
     border and padding are 28px whatever the ratio says, so once the stage row
     yields past that — 200% text zoom on a short viewport, where the slide is a
     sliver either way — plain centring hangs half the box out through the top of
     the stage cell. `safe` keeps the mockup's centring wherever there is room and
     falls back to the start edge only where centring would put the box outside
     the box it is being centred in. */
  place-self:safe center;
  aspect-ratio:16/9;
  overflow:hidden; /* item-32 audit: media letterbox — real content only; the empty ACTION states escape it entirely (panels.css S9) */
  border:2px solid var(--steel);
  border-radius:var(--card-radius);
  background:var(--void);
  object-fit:cover;
}
@supports(width:1cqh){
  .visual-surface{width:min(100%,calc(100cqh * 16 / 9))}
  /* Accessibility beats mockup fidelity
     (docs/CODEBASE.html#style-accessibility-over-fidelity). The mockup sized this
     surface from the CELL height less a flat 55px, its measurement of the slide
     nav row underneath. 55px is a text-sized row expressed as a constant, so at
     200% text zoom the nav grows past it, the derived width overshoots the space
     row 1 actually has, and max-height clamps the box into a distorted 1.84
     letterbox (the 16:9 stage invariant,
     docs/CODEBASE.html#arch-stage-invariant, broken by the very thing meant to
     protect it). Driving the box from its own row instead — height capped by the
     row (100%) and by the ratio of the cell width — needs no constant, so the
     letterbox stays true and simply shrinks as the nav grows, which is the zoom
     gate's outcome (docs/CODEBASE.html#style-zoom-and-reflow). Producer and full
     mode need no override: they hide or overlay the nav, and 100% is then the
     whole cell. */
  .presentation-panel .visual-surface{width:auto;height:min(100%,calc(100cqw * 9 / 16))}
  .self-test-preview{width:min(100%,calc(100cqh * 16 / 9));place-self:start center}
}
.wcpe-slide{
  padding:clamp(12px,3cqw,34px);
  display:flex;
  flex-direction:column;
  justify-content:center;
  box-shadow:inset 8px 0 0 var(--cyan),0 0 24px -14px var(--cyan);
}
.wcpe-slide::after{
  content:"";
  position:absolute;
  width:46%;
  aspect-ratio:1;
  right:-20%;
  top:-66%;
  border:1px solid rgba(34,211,238,.18);
  border-radius:50%;
  box-shadow:0 0 0 20px rgba(34,211,238,.025),0 0 0 42px rgba(65,117,147,.025);
}
.slide-brand{
  position:relative;
  z-index:1;
  color:var(--cyan);
  font:900 clamp(15px,1.7cqw,18px)/1.1 ui-monospace,SFMono-Regular,Menlo,monospace;
  letter-spacing:.08em;
  text-transform:uppercase;
}
.wcpe-slide h3{
  position:relative;
  z-index:1;
  max-width:88%;
  margin:7px 0 0;
  font-size:clamp(20px,4.1cqw,46px);
  line-height:1.02;
  overflow-wrap:normal;
  word-break:normal;
}
.wcpe-slide .slide-subtitle{
  position:relative;
  z-index:1;
  max-width:88%;
  margin:7px 0 0;
  color:var(--cyan);
  font-size:clamp(15px,2cqw,21px);
  font-weight:900;
  line-height:1.12;
}
.slide-bullets{
  position:relative;
  z-index:1;
  max-width:88%;
  margin:9px 0 0;
  padding-left:22px;
  display:grid;
  gap:3px;
  font-size:clamp(15px,1.55cqw,18px);
  line-height:1.15;
}
.slide-page{
  position:absolute;
  z-index:2;
  right:12px;
  bottom:9px;
  color:var(--dim);
  font:900 15px/1 ui-monospace,SFMono-Regular,Menlo,monospace;
}
/* Accessibility beats mockup fidelity
   (docs/CODEBASE.html#style-accessibility-over-fidelity). Two fixed columns cannot
   hold Previous and Next once their labels double: neither may be truncated and
   neither may shrink under 44px, so the pair pushed the panel wider than the
   stage cell and Next left the clipped region entirely. Wrapping is the reflow the
   200% zoom gate asks for (docs/CODEBASE.html#style-zoom-and-reflow) — the row
   stacks, both controls stay whole and reachable, and at the mockup's own text
   size the flex basis reproduces the 1fr 1fr row exactly. */
/* Accessibility beats mockup fidelity
   (docs/CODEBASE.html#style-accessibility-over-fidelity). The panel centres its grid
   items so the 16:9 surface letterboxes, and the nav silently inherited that: it
   shrink-wrapped to the pair's max-content and stayed there, so once the labels
   outgrew it the two buttons split onto separate lines — 120px of nav inside a
   76px cell, Next hanging out through the panel's clip and the collapsed row
   above carrying the slide out of the stage cell with it. Capping the width at
   max-content instead of fixing it there reproduces the mockup's centred 234px
   row exactly while there is room, and spends the width the cell already has the
   moment the pair needs more than that — one line of paging instead of two. */
/* The same accessibility-over-fidelity ruling
   (docs/CODEBASE.html#style-accessibility-over-fidelity), the vertical axis
   (open item 14). With no deck the nav also carries the two .support-reason
   notes, and at 200% text zoom those notes grow the band past the whole
   stage cell (566px of nav in a 313px cell at instructor@390x844) — the auto
   row cannot shrink, so the surface collapsed to a sliver and Next was
   clipped dead behind the panel's overflow. The cap keeps the band to half
   the stage cell (cqh resolves against .stage-cell, the container the panel
   lives in) but never below one wrapped button row plus a peek (em, so the
   floor scales with the text the way the buttons themselves do), and
   overflow-y:auto makes everything past the cap honestly scrollable.
   Precision (Hudson, S9): in the deckless state these buttons are
   HTML-disabled by design (the support gate's on-control form — disable only
   the affected control and put the reason on it,
   docs/CODEBASE.html#arch-browser-support-gate; S4), so nothing focusable
   sits past the cap and keyboard focus never needs to scroll it — what the
   scroll actually reaches is the full-row .support-reason notes below the
   button row, while each disabled control stays visible beside its reason
   instead of painted into the clip. With a deck the buttons are enabled and
   fit inside the cap, so the question does not arise. The outer min() bounds
   the band by the stage cell
   itself: in the short-landscape overlay mode the band is centred on the
   panel, and a band taller than the panel would hang out through the
   panel's clip at both ends. With a deck loaded the band is two 48px
   buttons and the cap never binds; at the mockup's own text size nothing
   changes. */
.local-slide-nav{
  justify-self:center;
  width:100%;
  max-width:max-content;
  max-height:min(100cqh,max(50cqh,calc(2.4em + 38px)));
  overflow-y:auto;
  display:flex;
  flex-wrap:wrap;
  gap:7px;
}
.local-slide-nav button{
  flex:1 1 calc(50% - 7px);
  min-height:48px;
  padding:7px 10px;
  display:flex;
  align-items:center;
  justify-content:center;
  gap:7px;
  border-radius:var(--control-radius);
  font-size:15px;
  font-weight:900;
}
.local-slide-nav svg{width:21px;height:21px;fill:currentColor}
/* Two exceptions to the band cap above, keyed to bands responsive.css owns:
   · Below 240px of viewport the shell itself scrolls (responsive.css 240
     band flips the spine to block flow), so the honest height is the
     band's full content height — capping it there would trade the shell's
     real scrollbar for a nested one and re-clip what the band already
     made reachable.
   · In the 18a compact band (which only ever exists under text zoom — 16em
     is the tested matrix's floor at the design size,
     docs/CODEBASE.html#arch-viewport-matrix) the stage cell is ~150px and
     both paging rows plus the letterbox floor overshoot it by five
     pixels; trimming the buttons' vertical padding under zoom is what
     lets both rows sit whole inside the cell. 48px min-height keeps the
     target floor regardless. */
@media(max-height:240px){
  .local-slide-nav{max-height:none;overflow-y:visible}
}
@container studio-work (max-height:14em){
  .local-slide-nav button{padding-top:4px;padding-bottom:4px}
}
.camera-surface{
  isolation:isolate;
  border-color:var(--cyan);
  box-shadow:var(--glow);
  background:
    radial-gradient(circle at 50% 34%,var(--dim) 0 7%,transparent 7.5%),
    radial-gradient(ellipse at 50% 96%,var(--steel) 0 28%,transparent 28.5%),
    linear-gradient(145deg,var(--navy),var(--surface) 72%);
}
.camera-surface::before{
  content:"";
  position:absolute;
  inset:4%;
  z-index:-1;
  border:1px solid rgba(34,211,238,.25);
  border-radius:var(--card-radius);
}
/* The mockup's "Online" pill is gone — 54a (Tor, 2026-07-30): a live
   self-view IS the online statement. Only the readout narrates facts. */
.camera-readout{
  position:absolute;
  right:10px;
  bottom:9px;
  min-height:40px;
  padding:7px 10px;
  display:flex;
  align-items:center;
  border-radius:var(--control-radius);
  background:rgba(5,8,15,.9);
  font-size:15px;
  font-weight:900;
}
