/* ====================================================================
   WCPE INSTRUCTOR STUDIO — PRODUCER WORKSPACE + DIALOGS
   ====================================================================
   Cascade layer 6 of 7. Owns region 12 — producer workspace and dialogs.
   Region 12 has been RE-RULED since the mockup: the file groups are
   recordings/ deck/ transcript/ script/ marks/ (the mockup said presentation/)
   and the documents ZIP is gone. The shipped banner is the rule.

   LOAD ORDER IS LOAD-BEARING:
     tokens -> base -> shell -> stage -> panels -> producer -> responsive
   This file is #6. 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
   ==================================================================== */
/* ====================================================================
   12 · PRODUCER WORKSPACE + DIALOGS
   Sessions/Files/Outbox panels; the file browser mirrors the session
   folder structure (recordings/ deck/ transcript/ script/ marks/) as
   the item-35 accordion table — the documents ZIP and its
   video-exclusion rule are gone, every file downloads by its own
   handle. Center-screen dialogs — the join reminder and the
   instructor knock card — use heavy cyan borders + glow, one dominant
   action, one quiet dismiss, and modal semantics.
   ==================================================================== */
/* The files-panel vocabulary that used to live here — .workspace-panel,
   .file-workspace-header, .file-groups/.file-group, .file-row,
   .file-detail/.file-name/.file-meta and .file-download — MOVED to panels.css.
   It is not producer-only any more (journey gap G3 gave the instructor the
   same panel) and this stylesheet is not loaded by the instructor shell, so
   every one of those rules was silently absent on that surface. See the
   S5b banner in panels.css for the measurement. (The ZIP's .zip-action
   rules were deleted with the ZIP itself — item 35.) The producer's
   item-35 table shape lives at the end of this file. */
.session-reminder{
  position:fixed;
  z-index:190;
  left:calc(var(--rail-w) + (100vw - var(--rail-w))/2);
  top:50%;
  width:min(520px,calc(100vw - var(--rail-w) - 24px));
  max-height:calc(100dvh - 24px);
  padding:20px;
  display:grid;
  gap:13px;
  overflow-y:auto;
  transform:translate(-50%,-50%);
  border:3px solid var(--cyan);
  border-radius:var(--card-radius);
  background:linear-gradient(145deg,rgba(15,27,48,.99),rgba(5,8,15,.99));
  box-shadow:var(--shadow),0 0 42px -8px var(--cyan);
  animation:reminderIn .2s ease-out;
}
@keyframes reminderIn{from{opacity:0;transform:translate(-50%,-47%) scale(.97)}to{opacity:1;transform:translate(-50%,-50%) scale(1)}}
.reminder-heading{display:flex;align-items:center;gap:11px}
.reminder-heading svg{width:34px;height:34px;fill:var(--cyan);flex:0 0 auto}
.reminder-heading h2{margin:0;font-size:24px;line-height:1.15;overflow-wrap:normal;word-break:normal}
/* Carries the session title (server data); heading above stays prose. */
.session-reminder p{margin:0;color:var(--dim);font-size:19px;line-height:1.5;overflow-wrap:anywhere;word-break:normal}
.reminder-actions{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:9px}
.reminder-actions button{min-height:52px;padding:9px 15px;border-radius:var(--control-radius);font-size:19px;font-weight:900}
.reminder-actions .join-now{border:2px solid var(--azure);background:var(--blue);box-shadow:var(--azure-glow)}
.outbox-list{display:grid;gap:10px}
/* The approvals/sessions table (docs/CODEBASE.html#style-sortable-tables — the
   booking-list table below is the reference implementation). A wide table
   scrolls inside its own keyboard-reachable wrap — the page never scrolls
   horizontally (docs/CODEBASE.html#style-long-token-policy);
   title/instructor/starts cells are server data and wrap via .user-text.
   Sort direction is an arrow glyph on the active th (aria-sort), never
   color alone. */
.sessions-table-wrap{overflow-x:auto}
.sessions-table-wrap:focus-visible{outline:2px solid var(--azure);outline-offset:2px}
.sessions-table{width:100%;border-collapse:collapse;font-size:19px;text-align:left}
.sessions-table th,.sessions-table td{padding:10px 12px;vertical-align:middle}
.sessions-table th{color:var(--dim);font-size:15px;border-bottom:1px solid var(--line)}
.sessions-sort{
  display:inline-flex;align-items:center;gap:6px;
  min-width:44px;min-height:44px;padding:4px 8px;margin:-4px -8px;
  border:0;background:none;color:inherit;font:inherit;font-weight:900;cursor:pointer;
  border-radius:var(--control-radius);
}
.sessions-sort:hover,.sessions-sort:focus-visible{color:var(--text)}
/* The alt-text form keeps the arrow out of the accessible name (aria-sort
   already announces direction); the plain declaration is the fallback where
   `content` alt text is unsupported. */
th[aria-sort=ascending] .sessions-sort::after{content:"▲";content:"▲" / "";font-size:11px}
th[aria-sort=descending] .sessions-sort::after{content:"▼";content:"▼" / "";font-size:11px}
/* Each appointment is one tbody: a hairline above the record keeps the facts
   row and its actions row reading as ONE thing, with no line between them —
   the sortable-table rules' own-row allowance
   (docs/CODEBASE.html#style-sortable-tables) drawn so it cannot be misread
   as two records. */
.session-group{border-top:1px solid var(--line)}
.session-fact-row td{padding-bottom:6px}
/* Item 37: the instructor's own reading of the same start, a second line
   inside the Starts cell — the same fact in another zone, so it stays in the
   one column — one fact, one column, per the table rules
   (docs/CODEBASE.html#style-sortable-tables). 15px meets the floor; --dim
   keeps AA on this background (the th and status words already rely on the
   same pair). */
.session-starts-main{display:block}
.session-starts-note{display:block;margin-top:2px;font-size:15px;color:var(--dim)}
.session-actions-row td{padding-top:0;padding-bottom:14px}
.session-actions{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.session-actions button{
  min-height:48px;
  padding:8px 14px;
  border:1px solid var(--azure);
  border-radius:var(--control-radius);
  background:var(--blue);
  font-size:19px;
  font-weight:900;
}
/* Status is a WORD first — the glyph beside it adds a distinct shape per
   state (filled dot = granted, haloed dot = on air) so no state ever rides on
   colour alone (docs/CODEBASE.html#style-glyph-needs-word).

   THIS PILL NOW SERVES THE HAPPENING NOW BAND ONLY. The sessions table's
   Status column is gone (ruling 121(a)); the cancelled / denied / completed
   variants went with it, because the band is only ever live or imminent and a
   selector nothing can match is a rule that reads as a promise. The table's
   equivalent is .session-row-state below. */
.session-status{display:inline-flex;align-items:center;gap:8px;font-size:15px;font-weight:900;color:var(--dim);white-space:nowrap}
.session-status::before{content:"";width:10px;height:10px;flex:0 0 auto;border-radius:50%;border:2px solid var(--steel)}
.session-status.is-approved::before{border-color:var(--cyan);background:var(--cyan)}
.session-status.is-live{color:var(--red)}
.session-status.is-live::before{border-color:var(--red);background:var(--red);box-shadow:0 0 0 3px rgba(255,59,107,.25),0 0 10px -2px var(--red)}

/* ---- Ruling 121(a): the row carries its own state -----------------------
   "The session greyed with 'Cancelled' or 'Ended' beside it, and a normal
   upcoming session carries nothing."

   THE GREYING IS DECORATION AND OWNS NO FACT. The word is real text in the
   Session cell (dom.js sessionTitleCell), so a screen reader hears
   "Cancelled" whether or not any of this loads. What the grey adds is a
   scan-speed signal for a sighted producer running down the list.

   THE 65+ FLOOR IS NOT SPENT ON IT. "Greyed" here means --dim (#b9c8db),
   which measures 10.9:1 on --surface (#0b1322) — well clear of the 4.5:1
   floor that binds at these sizes, and the same token the table's own
   headers already use. Nothing drops to a washed-out grey that would put a
   65-year-old reader below AA to save a producer a glance; the row is
   quieter, never fainter than legible.

   THE WORD ITSELF STAYS AT FULL STRENGTH, and each state carries a distinct
   GLYPH SHAPE as well as a word, so the mark never rides on colour alone
   (docs/CODEBASE.html#style-glyph-needs-word): cross = called off, square =
   finished, hollow ring = still waiting on a producer, hourglass = the slot
   went by unapproved. The glyphs use the alt-text content form so they
   stay out of the accessible name,
   exactly like the sort arrows. */
.session-row-state{
  display:inline-flex;
  align-items:center;
  gap:6px;
  margin-left:10px;
  padding:1px 8px;
  border:1px solid var(--line);
  border-radius:999px;
  color:var(--text);
  font-size:15px;
  font-weight:900;
  white-space:nowrap;
  vertical-align:baseline;
}
.session-row-state::before{font-size:13px;line-height:1}
.session-row-state.is-cancelled::before{content:"\2715";content:"\2715" / "";color:var(--red)}
.session-row-state.is-cancelled{border-color:var(--red)}
.session-row-state.is-ended::before{content:"\25A0";content:"\25A0" / "";color:var(--steel)}
/* No new colour is invented for "expired": the palette in tokens.css is a
   closed set and adding a sixth hue is a design decision, not a build one.
   The word and the hourglass shape carry it, which is what the
   glyph-or-colour-needs-a-word rule asks for
   (docs/CODEBASE.html#style-glyph-needs-word). */
.session-row-state.is-expired::before{content:"\231B";content:"\231B" / "";color:var(--dim)}
.session-row-state.is-requested::before{content:"\25CB";content:"\25CB" / "";color:var(--cyan)}
.session-row-state.is-requested{border-color:var(--cyan)}
/* A LIVE row is marked but never greyed — it is the loudest row on the page,
   not a quiet one — and it borrows the haloed dot the band's own card uses so
   the two surfaces cannot drift in how a running room looks. */
.session-row-state.is-live{color:var(--red);border-color:var(--red)}
.session-row-state.is-live::before{
  content:"";
  width:10px;height:10px;border-radius:50%;
  background:var(--red);
  box-shadow:0 0 0 3px rgba(255,59,107,.25),0 0 10px -2px var(--red);
}
/* THE GREYING IS ENUMERATED, not applied to "any marked row". Only the three
   states that mean "there is nothing to prepare for here" go quiet: a live
   room must stay loud, and a legacy request still waiting on a producer is
   the one row on the page that most needs attention. Applied to the FACTS
   only — the verbs keep their full contrast, because a Files button on an
   ended session is exactly the control ruling 121(b) wants a producer to
   press. */
.session-row[data-session-state="cancelled"] .session-title-main,
.session-row[data-session-state="cancelled"] .session-cell-instructor,
.session-row[data-session-state="cancelled"] .session-cell-starts,
.session-row[data-session-state="ended"] .session-title-main,
.session-row[data-session-state="ended"] .session-cell-instructor,
.session-row[data-session-state="ended"] .session-cell-starts,
.session-row[data-session-state="expired"] .session-title-main,
.session-row[data-session-state="expired"] .session-cell-instructor,
.session-row[data-session-state="expired"] .session-cell-starts,
.session-group[data-session-state="cancelled"] .session-title-main,
.session-group[data-session-state="cancelled"] .session-cell-instructor,
.session-group[data-session-state="cancelled"] .session-cell-starts,
.session-group[data-session-state="ended"] .session-title-main,
.session-group[data-session-state="ended"] .session-cell-instructor,
.session-group[data-session-state="ended"] .session-cell-starts,
.session-group[data-session-state="expired"] .session-title-main,
.session-group[data-session-state="expired"] .session-cell-instructor,
.session-group[data-session-state="expired"] .session-cell-starts{color:var(--dim)}
/* A documented addition to the frozen mockup
   (docs/CODEBASE.html#style-accessibility-over-fidelity): the open session is
   a fact the producer must be able to find again; aria-current carries it to
   AT and this tint + cyan edge is its one visual home. */
.session-group[aria-current="true"] td{background:rgba(7,76,142,.22)}
.session-group[aria-current="true"] .session-fact-row td:first-child{box-shadow:inset 3px 0 0 var(--cyan)}
.sessions-empty{margin:0;color:var(--dim);font-size:19px}
/* The whitelist add form borrows the booking form's control set for the same
   reason the booking card does (panels.css §S10): the rail's FULL-mode rules
   hide .setup-control labels and pin the reused controls to 15px, and a form
   the producer reads all day gets the same 19px/48px treatment (Ruling 7's
   page default, not the older 17px figure). */
.whitelist-add{display:grid;gap:12px;margin:12px 0 16px}
.whitelist-add .setup-control>span{display:block;font-size:19px}
.whitelist-add .setup-select,.whitelist-add .setup-action{font-size:19px;min-height:48px}
.whitelist-add .setup-action{justify-self:start;padding:8px 16px}
/* The booking-list table (Tor's request, 2026-07-27). A wide table scrolls
   inside its own wrap — never the page
   (docs/CODEBASE.html#style-sortable-tables); name and email cells are user
   data and wrap via .user-text. Sort direction is an arrow glyph on the
   active th (aria-sort), never color alone. The Remove
   button is visually compact but keeps the 44px hit floor — "small" may
   shrink chrome, never the target. */
/* The wrap is a keyboard-reachable scroll region (review F2): at narrow
   widths the Remove column scrolls into view, and a keyboard user needs a
   tab stop to drive that scroll. */
.whitelist-table-wrap{overflow-x:auto}
.whitelist-table-wrap:focus-visible{outline:2px solid var(--azure);outline-offset:2px}
.whitelist-table{width:100%;border-collapse:collapse;font-size:19px;text-align:left}
.whitelist-table th,.whitelist-table td{padding:8px 12px;border-bottom:1px solid var(--line);vertical-align:middle}
.whitelist-table th{color:var(--dim);font-size:15px}
.whitelist-col-remove{min-height:44px}
.whitelist-sort{
  display:inline-flex;align-items:center;gap:6px;
  min-width:44px;min-height:44px;padding:4px 8px;margin:-4px -8px;
  border:0;background:none;color:inherit;font:inherit;font-weight:900;cursor:pointer;
  border-radius:var(--control-radius);
}
.whitelist-sort:hover,.whitelist-sort:focus-visible{color:var(--text)}
/* The alt-text form keeps the arrow out of the accessible name (review F4 —
   aria-sort already announces direction); the plain declaration is the
   fallback where `content` alt text is unsupported. */
th[aria-sort=ascending] .whitelist-sort::after{content:"▲";content:"▲" / "";font-size:11px}
th[aria-sort=descending] .whitelist-sort::after{content:"▼";content:"▼" / "";font-size:11px}
.whitelist-remove,.whitelist-keep{
  min-height:44px;padding:4px 14px;
  border:1px solid var(--line);border-radius:var(--control-radius);
  background:var(--surface);color:var(--text);font-size:15px;font-weight:900;cursor:pointer;
}
.whitelist-remove{border-color:var(--red);background:rgba(255,59,107,.14)}
.whitelist-confirm{display:inline-flex;align-items:center;gap:8px;flex-wrap:wrap}
.whitelist-confirm-copy{font-size:15px;color:var(--dim)}
.whitelist-empty{margin:0;color:var(--dim);font-size:19px}
.workspace-card{
  padding:14px;
  display:grid;
  gap:7px;
  border:1px solid var(--line);
  border-radius:var(--card-radius);
  background:var(--surface);
}
/* Session and outbox cards render instructor names, emails and session
   titles — an unbroken address wraps rather than overflowing
   (docs/CODEBASE.html#style-long-token-policy). */
.workspace-card h3,.workspace-card p{margin:0;overflow-wrap:anywhere;word-break:normal}
.workspace-card h3{font-size:20px}
.workspace-card p{color:var(--dim);font-size:19px}
.workspace-card button{
  justify-self:start;
  min-height:48px;
  padding:8px 13px;
  border:1px solid var(--azure);
  border-radius:var(--control-radius);
  background:var(--blue);
  font-size:15px;
  font-weight:900;
}
/* G1 — the session cancel verb and its two-step confirm. Cancelling releases a
   booked slot and emails both parties, so the destructive verb is set apart
   from the ordinary blue actions: a danger border AND the word "cancel" in
   the label, never colour alone
   (docs/CODEBASE.html#style-glyph-needs-word). The SAFE verb keeps the
   normal primary look, because it is where focus lands and what a mis-press
   should land on.
   All three inherit .session-actions button's 48px floor. */
.session-actions button.session-cancel,
.session-actions button.session-cancel-confirm{
  border:2px solid var(--red);
  background:transparent;
  color:var(--red);
}
.session-actions button.session-cancel:hover,
.session-actions button.session-cancel-confirm:hover{box-shadow:0 0 18px -8px var(--red)}
.session-actions button.session-cancel-keep{border:2px solid var(--azure)}
.session-cancel-copy{font-size:19px;color:var(--dim)}

/* Every .topbar template (shell base and each responsive band) declares
   THREE explicit columns — title, air status, settings gear — and grid's
   default auto-flow is ROW, so a fourth child became an implicit SECOND ROW:
   measured at 1440x900 when R2 added its user button, that button landed at
   x288,y69 under the title and the bar grew 60px -> 123px. Column flow makes
   every extra child an implicit same-row column instead. STILL LOAD-BEARING
   after item-60 S1 retired the user button and S2 deleted the Switch-session
   button, though for a narrower reason than before: the producer bar's
   server-rendered children now match its three declared columns exactly, so
   nothing overflows today, and this declaration is a GUARD rather than a
   correction — the runtime gear is appended by ui/settings-modal.js after
   the markup's children, and anything added to producer.php's header later
   becomes an implicit COLUMN rather than a second row that grows the bar
   60px -> 123px. Verified inert on this tree: three children, three explicit
   columns, nothing moves with or without it. Page-scoped by construction:
   only producer.php loads this stylesheet, and no template declares
   grid-auto-flow, so no band's later rules undo it. */
.topbar{grid-auto-flow:column;grid-auto-columns:max-content}
/* ITEM 60 (R8) — the topbar user menu and its dropdown are RETIRED. Tor:
   "merge settings and user buttons. Keep only the Settings Icon." Identity
   and Sign out now live in the one settings hub behind the gear
   (ui/settings-modal.js); .topbar-user, .user-menu, .user-menu-identity,
   .user-menu-label, .user-menu-email and .user-menu-signout are gone with
   it, along with the portrait-phone rule that shifted the gear 58px left to
   sit beside the user button — with nothing beside it, the gear goes back to
   responsive.css's own 7px margin in that band.

   Sign out's home now, styled here rather than in shell.css because the
   PRODUCER is the only role whose manifest emits the row — the hub is one
   card builder over a per-role row list
   (docs/CODEBASE.html#style-settings-hub), the settings-hub design left
   instructor sign-out open, and studio.php does not load this file.
   A full-width grid item, so it never reads as a second Done —
   .settings-modal-close is end-justified and carries the check glyph. */
.settings-signout{
  display:flex;align-items:center;justify-content:center;gap:8px;
  min-height:48px;padding:8px 14px;
  border:1px solid var(--azure);border-radius:var(--control-radius);
  background:var(--blue);color:var(--text);font-size:19px;font-weight:900;cursor:pointer;
}
.settings-signout:hover,.settings-signout:focus-visible{border-color:var(--cyan);box-shadow:0 0 0 1px var(--cyan),0 0 18px -6px var(--cyan)}
.settings-signout svg{width:20px;height:20px;fill:currentColor;flex:0 0 auto}

/* Shared join-card rules live in panels.css because both roles render one;
   studio.php does not load this producer-only file. */
[data-role="producer"] .presentation-panel .local-slide-nav{display:none}

/* ====================================================================
   ITEM 35 · PRODUCER FILES SURFACE (Tor, 2026-07-29)
   Session header + filter + one real sortable table
   (docs/CODEBASE.html#style-sortable-tables) whose category tbodys
   are an accordion. This block lives HERE, not panels.css, because the
   table shape is producer-only — the instructor keeps the shared
   .file-group/.file-row rules in panels.css. Sort button + arrow-glyph
   rules mirror .sessions-sort exactly (the reference implementation).
   ==================================================================== */
.files-session-header{display:grid;gap:4px}
.files-session-title{margin:0;font-size:22px;line-height:1.2;overflow-wrap:anywhere;word-break:normal}
.files-session-id{margin:0;color:var(--dim);font:700 15px/1.3 ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.08em}
.files-session-topic{margin:0;color:var(--dim);font-size:19px;line-height:1.5;overflow-wrap:anywhere;word-break:normal}
.files-lead{margin:0;color:var(--dim);font-size:19px;line-height:1.5}
.files-filter{display:grid;gap:6px;justify-items:start}
.files-filter>span{font-size:19px;font-weight:900}
.files-filter input{
  min-height:48px;
  width:min(340px,100%);
  padding:8px 12px;
  border:1px solid var(--line);
  border-radius:var(--control-radius);
  background:var(--raised);
  color:var(--text);
  font-size:19px;
}
.files-filter input:focus-visible{outline:none;border-color:var(--cyan);box-shadow:0 0 0 1px var(--cyan),0 0 18px -6px var(--cyan)}
/* VISIBLE scrollbars (item 35, tied to item 32): macOS overlay scrollbars
   fade, and three controls have already been lost below invisible edges —
   when a files region scrolls it must LOOK scrollable. Explicit webkit
   styling plus Firefox's scrollbar-width/color, on both the panel (the
   vertical scroller, panels.css .workspace-panel) and the table wrap (the
   horizontal one). */
[data-role="producer"] .files-panel,
.files-table-wrap{scrollbar-width:auto;scrollbar-color:var(--steel) var(--raised)}
[data-role="producer"] .files-panel::-webkit-scrollbar,
.files-table-wrap::-webkit-scrollbar{width:12px;height:12px}
[data-role="producer"] .files-panel::-webkit-scrollbar-track,
.files-table-wrap::-webkit-scrollbar-track{background:var(--raised);border-radius:7px}
[data-role="producer"] .files-panel::-webkit-scrollbar-thumb,
.files-table-wrap::-webkit-scrollbar-thumb{background:var(--steel);border-radius:7px;border:2px solid var(--raised)}
.files-table-wrap{overflow-x:auto}
.files-table-wrap:focus-visible{outline:2px solid var(--azure);outline-offset:2px}
/* min-width matters (review F3): under auto layout, overflow-wrap:anywhere
   reduces the name cells' min-content width to ONE CHARACTER, so without a
   floor the layout starves the File column instead of ever overflowing —
   measured 55px wide / 21 lines tall for a 25-char name at 390×844, while
   the unbreakable Added timestamp held 182px. The floor makes the wrap's
   own overflow-x:auto (keyboard-reachable, visibly scrollbarred) do that
   work, and the name column keeps room for the one fact that identifies
   the file. */
.files-table{width:100%;min-width:820px;border-collapse:collapse;font-size:19px;text-align:left}
.files-cell-name{min-width:240px}
.files-table th,.files-table td{padding:10px 12px;vertical-align:middle}
.files-table th{color:var(--dim);font-size:15px;border-bottom:1px solid var(--line)}
.files-actions-head{font-weight:900}
.files-sort{
  display:inline-flex;align-items:center;gap:6px;
  min-width:44px;min-height:44px;padding:4px 8px;margin:-4px -8px;
  border:0;background:none;color:inherit;font:inherit;font-weight:900;cursor:pointer;
  border-radius:var(--control-radius);
}
.files-sort:hover,.files-sort:focus-visible{color:var(--text)}
/* The alt-text form keeps the arrow out of the accessible name (aria-sort
   already announces direction); the plain declaration is the fallback where
   `content` alt text is unsupported. */
th[aria-sort=ascending] .files-sort::after{content:"▲";content:"▲" / "";font-size:11px}
th[aria-sort=descending] .files-sort::after{content:"▼";content:"▼" / "";font-size:11px}
/* Accordion category header: the cyan mono eyebrow the instructor's group
   headings use, as a full-width toggle. The chevron ALSO carries the state
   (points right closed, down open) so expanded is never colour alone. */
.files-category-row td{padding:6px 0 0}
.files-category-toggle{
  display:flex;align-items:center;gap:9px;
  width:100%;min-height:48px;padding:8px 12px;
  border:0;background:none;cursor:pointer;text-align:left;
  color:var(--cyan);font:900 16px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;
  border-radius:var(--control-radius);
}
.files-category-toggle:hover,.files-category-toggle:focus-visible{color:var(--text)}
.files-category-toggle:focus-visible{outline:2px solid var(--cyan);outline-offset:-2px}
.files-category-chevron{width:18px;height:18px;fill:currentColor;flex:0 0 auto;transition:transform .2s ease}
.files-category.is-expanded .files-category-chevron{transform:rotate(90deg)}
@media (prefers-reduced-motion:reduce){.files-category-chevron{transition:none}}
.files-category-count{margin-left:auto;color:var(--dim);font:700 15px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace}
.files-row td{border-top:1px solid var(--line)}
/* The .files-view affordance moved to panels.css (Tor, 2026-08-17): the
   instructor's file rows wear the same view button now, and panels.css is
   the stylesheet both pages load. */
.files-name-plain{font-size:19px;font-weight:900;overflow-wrap:anywhere;word-break:normal}
.files-cell-kind{color:var(--dim)}
.files-cell-size,.files-cell-added{white-space:nowrap;color:var(--dim)}
.files-cell-state{color:var(--dim);font-size:15px}
/* Icon-only download: the accessible name is the aria-label; the target
   keeps the 44px floor with the icon optically centered. */
.files-action{
  display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:8px;
  border:1px solid var(--azure);border-radius:var(--control-radius);
  background:var(--blue);cursor:pointer;
}
.files-action:hover,.files-action:focus-visible{box-shadow:0 0 0 1px var(--cyan),0 0 18px -6px var(--cyan);border-color:var(--cyan)}
.files-action svg{width:22px;height:22px;fill:currentColor}
/* The producer's empty/refusal message sits outside .file-groups (the
   instructor's host), so panels.css's rule cannot reach it. */
.files-panel>.file-empty-note{margin:0;color:var(--dim);font-size:19px;line-height:1.5}

/* ====================================================================
   ITEM 48 · R1 (Tor, 2026-07-30) — WORKSPACE SECTIONS USE THE WHOLE PAGE
   "The producer only wants to see the instructor info when in the Live
   view. Other menu items use the whole page." roles/producer.js keeps
   .is-workspace-view on the producer's .role-view while Files, Outbox,
   Instructor list or Dashboard is on stage; Live (and every live-monitor
   feature) removes it, restoring today's rack + transport layout.

   SPECIFICITY IS LOAD-BEARING: this file is #6 and responsive.css (#7)
   restates the rack/transport grid per mode at (0,1,0)–(0,2,0), so every
   rule here carries the extra .is-workspace-view class to outrank those
   later declarations instead of losing to the cascade.
   ==================================================================== */
/* The Live-only tenants. The switcher and utility dock are the compact
   modes' HOMES of the same live-monitor features (Slides/Camera/Script,
   Marks/Transcript), so they leave with the rack — the left rail is the
   whole navigation while a workspace section owns the page. */
.role-view.is-workspace-view .side-rack,
.role-view.is-workspace-view .transport,
.role-view.is-workspace-view .feature-switcher,
.role-view.is-workspace-view .utility-dock{display:none}
/* One column, one row: without this the emptied rack column (FULL mode's
   clamp(238px,24vw,320px)) and the transport row's minmax(--transport-h,auto)
   floor would keep reserving dead bands beside and under the stage. */
.role-view.is-active.is-workspace-view{
  grid-template-columns:minmax(0,1fr);
  grid-template-rows:minmax(0,1fr);
}
/* The compact/short-landscape work-grid templates reserve rows and columns
   for the switcher and dock (stage.css, responsive.css 390px/520px bands);
   with both gone the stage is the only tenant left. Harmless in FULL mode,
   where the work-grid is display:contents. */
.role-view.is-workspace-view .work-grid{
  grid-template-columns:minmax(0,1fr);
  grid-template-rows:minmax(0,1fr);
}
.role-view.is-workspace-view .feature-stage{grid-column:1;grid-row:1}

/* ====================================================================
   ITEM 47 · F2 — A PAST SESSION'S LIVE VIEW IS READ-ONLY-HONEST
   roles/producer.js keeps .is-readonly-session on the producer's
   .role-view while the selected session is over (selectSession's
   access:'readonly'). The live verbs and the join prompt go; the
   [data-producer-readonly] sentence (built by initReadonlySession,
   reusing panels.css's .transport-readonly shape) stands in the
   transport instead. The server refuses these ops on a dead session
   regardless — this closes the surface that OFFERED them.
   ==================================================================== */
.role-view.is-readonly-session .producer-action-bar{display:none}
/* The "Join this session" card. display beats presence.js's hidden-attr
   toggling either way — a poll may un-hide it, the rule keeps it gone. */
.role-view.is-readonly-session [data-knock-request]{display:none}
/* Review paging: the producer's local-slide-nav ships hidden (the live
   monitor pages nothing) — a readonly deck is paged locally, so the nav
   returns. Same-file override: this rule is later than the display:none
   above at equal (0,3,0) specificity, and outranks responsive.css's
   FULL-mode (0,1,0) hide. */
.role-view.is-readonly-session .presentation-panel .local-slide-nav{
  display:flex;
  /* TOR, 2026-08-03, verbatim: "the previous and next buttton under the
     producer presentation/session window need to be side by side instead of
     stacked."
     MEASURED CAUSE, not guessed: stage.css caps this band at
     max-width:max-content, and on a WRAPPING flex container that resolves to
     roughly ONE button's width — the band computed 203px inside a 784px
     parent, so two buttons asking for calc(50% - 7px) each could never share
     a row and wrapped. Widening the band alone is not enough (the wrap is
     still legal), so the wrap is forbidden here too.
     Scoped to the readonly review pager deliberately: the instructor's live
     transport and responsive.css's overlaid narrow-viewport pager are
     different bands with their own rules, and neither is what Tor was
     looking at. min-width:0 lets the pair shrink rather than overflow if
     this band is ever rendered somewhere narrower than itself. */
  flex-wrap:nowrap;
  max-width:min(100%,420px);
}
.role-view.is-readonly-session .presentation-panel .local-slide-nav button{min-width:0}

/* ====================================================================
   ITEM 48/47 · POPULATED-AT-200%-ZOOM FIXES (measured by the new
   producer-shell suite — these panels had never been audited POPULATED)
   ==================================================================== */
/* A table wrap is a scroll container, and a scroll container's automatic
   minimum size is ZERO — inside the .workspace-panel grid (definite
   height in most modes) its row therefore compressed to whatever was
   left after the panel's other tenants grew: measured at 1440x900 @200%
   text zoom, the booking-list wrap held 23px with 57px headers scrolling
   inside it, and the sort buttons' centers landed outside it (audit:
   "unreachable control Name"). min-content restores the natural height,
   so the PANEL (overflow-y:auto, panels.css) becomes the one vertical
   scroller — a tall element scrolls in its own container, which is the
   assumed mechanism, not the failure
   (docs/CODEBASE.html#style-zoom-and-reflow). */
.sessions-table-wrap,
.whitelist-table-wrap,
.files-table-wrap{min-height:min-content}
/* The category toggle used to span the files table's full 820px
   min-width, so inside the horizontally-scrolled wrap at 390px its
   CENTER sat off-viewport (audit: "recordings/1 file center -41").
   Sticky pins it to the wrap's left edge at its own label width: the
   toggle rides the horizontal scroll and its center is always where the
   reader is. 48px height floor unchanged. */
.files-category-toggle{position:sticky;left:0;width:max-content;max-width:100%}
/* The 16:9 letterbox may SHRINK but never leave its cell (the zoom gate's
   tie-breaker, docs/CODEBASE.html#style-zoom-and-reflow: "a letterboxed
   stage that shrinks is correct; a control the user cannot reach is
   not"). At 844x390 @200% text the short-landscape band's
   squeezed stage row (41px) lost to the surface's own border-box FLOOR:
   height caps (100%, max-height) cannot go under padding+border, and
   the clamp'd slide padding alone measured 45px — the box crossed the
   cell edge by that arithmetic, whatever height was declared. With a
   deck up the padding decorates NOTHING (the seed nodes are hidden and
   the page image is absolute inset:0, object-fit:contain), so it goes;
   measured after: 72x41 inside the 41px cell, 16:9 intact. Producer
   only — the instructor's surface hosts in-flow module overlays
   (deck.js's progress/error lines) that still want the padding. */
[data-role="producer"] .presentation-panel .wcpe-slide.has-deck{padding:0}
/* Item 53 (Tor's R4): the PowerPoint sheet's upload-progress line reuses
   deck.js's .deck-progress vocabulary (panels.css), but that rule is a
   lower-third strip absolutely centered over the slide surface; inside the
   rail sheet's grid it must be an ordinary row, or it paints over the
   sheet's own controls from the .setup-item positioning context. Colors,
   border, the is-error alert glyph and the 17px floor all stay inherited. */
.producer-rail .setup-sheet .deck-progress{
  position:static;
  transform:none;
  max-width:none;
  max-height:none;
}
/* ITEM 60 S5. `.producer-rail .setup-sheet>p{display:block}` used to close
   this file. It existed because full mode hid `.setup-sheet>p` and, for the
   producer's two sheets, the paragraph WAS the state — it carried the
   choose-an-appointment / session-ended / upload sentences renderSetup kept
   honest. Under Tor's R8 the state is the ROW's own subtitle ("Found · 34
   pages" / "None yet" / "Read-only") and the panel carries facts and options
   instead, so the rule was re-showing a paragraph that no longer exists.
   Deleted with its cause: responsive.css tier 19 no longer hides
   `.setup-sheet>p` either, so nothing needs putting back. */
