/* The Deck tab of a session's workspace - src/views/editor/EditorDeckTab.vue and
 * everything under src/components/editor/deck/.
 *
 * ── THE FILENAME IS LOAD-BEARING, TWICE OVER
 *
 * `scripts/bundle-css.mjs` partitions the public and the admin CSS bundles on
 * the literal prefix `/css/admin` and on nothing else. A sheet named
 * `deck-editor.css` would therefore ship to every marketing visitor, with a
 * completely green build. And `tests/editorContentTranslation.spec.ts` reads
 * every `admin-editor*.css` by name, so this file must also carry the
 * `admin-editor` half or it drops out of the checks below.
 *
 * ── EVERY BARE CLASS HERE IS GLOBAL, AND THAT IS NOT A FIGURE OF SPEECH
 *
 * All of `public/css/admin*.css` concatenates into ONE admin bundle, loaded
 * whole by every admin page as well as by /editor, and the editor sheets are
 * linked late enough to win at equal specificity. Round 1 of this feature
 * shipped a bare `.edt-page` and a bare `.edt-header`; `admin-enrollment-detail.css`
 * uses both on the Person page's Hebrew sale panel, so opening a person with a
 * sale flipped that panel to left-to-right at 100dvh with a blue gradient behind
 * it. Nothing errored.
 *
 * The prefix here is therefore `edtd-`, checked against every other admin sheet
 * and against every static `class=""` in the non-editor components before a line
 * of this file was written. `tests/editorCssIsolation.spec.ts` fails any class
 * another admin sheet declares or any class a non-editor component wears.
 *
 * The three exceptions are DELIBERATE and are worn, never declared:
 *   - `.edt-btn` / `.edt-btn--primary` come from admin-editor-home.css. Two
 *     button kits on one page is worse than one shared name.
 *   - `.edt-canvas` / `.edt-canvas__inner` and the whole `:where(.edt-canvas__inner)`
 *     port of the projector's slide box live in admin-editor-templates.css, the
 *     GALLERY's sheet, and reach this tab only because the bundle concatenates.
 *     Do not narrow or re-scope that sheet. It also owns the page's ONE
 *     `container-type` and its ONE `direction: rtl`; both are pinned by count,
 *     so neither may be written here.
 *   - `.slide` / `.slide--phone` on the phone preview come from
 *     public/css/deck-slide-templates.css in the PUBLIC bundle, which the editor
 *     shell links. That is the same vocabulary the student's copy of the deck
 *     renders with, which is the whole point of the preview.
 *
 * ── NO `container-type`, ANYWHERE IN THIS FILE
 *
 * `cqw` resolves against the nearest container ancestor, and every rule in the
 * slide template vocabulary sizes itself in `cqw` against a box that is exactly
 * 1920px wide. A second container between here and a `.tpl` re-bases all of it
 * silently: the slide looks plausible at every size and is wrong at all of them,
 * which is the precise failure the whole canvas system exists to prevent.
 *
 * ── AND NO `direction: rtl`
 *
 * The editor is English and LTR (F10) and declares `direction` in exactly one
 * place - `.edt-canvas__inner` in admin-editor-templates.css, the slide box,
 * whose contents are Hebrew course DATA rather than the page's own copy.
 * `tests/editorContentTranslation.spec.ts:157` pins that at one island. Where
 * Hebrew content appears outside the canvas - the phone preview, a slide title
 * field - the `dir` ATTRIBUTE does the work instead: the UA stylesheet's `[dir]`
 * rule matches the element itself and therefore beats `.edt-shell`'s INHERITED
 * `direction: ltr`. `text-align` has no such UA rule, so it is restated below -
 * a different property, and not a second island.
 *
 * Read public/css/admin-editor.css first: it declares the `--edt-*` tokens used
 * throughout, establishes the LTR island and owns the page's scroll container
 * (`.edt-body`). Nothing here may introduce a second scroller at the tab's root.
 *
 * DESKTOP FIRST, usable on a tablet. There is deliberately no phone layout:
 * EditorShell.vue renders a blocking notice INSTEAD of <RouterView /> below
 * 767px for every route but the session list, so this component is never
 * constructed at that width and a phone-only rule here would be dead code.
 * Breakpoint: the repo's 1024px.
 */

/* ══════════════════════════════════════════════════════════════════════════
   1. THE THREE PANES
   ══════════════════════════════════════════════════════════════════════════ */

.edtd-tab {
  display: block;
  max-width: 2200px;
}

.edtd-tab__title {
  margin: 0 0 0.75rem;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-tab__empty {
  margin: 0;
  padding: 1rem 1.1rem;
  border-radius: 12px;
  background: var(--edt-surface-solid);
  box-shadow: inset 0 0 0 1px var(--edt-line);
  color: var(--edt-ink-2);
  font-size: 0.88rem;
  line-height: 1.6;
}

.edtd-tab__empty code {
  font-family: var(--font-mono);
  font-size: 0.82em;
}

/* The rail is a fixed reading width; the stage takes what is left; the side
   column is wide enough for a raw HTML textarea and the 390px phone preview to
   be usable in.

   295px RAIL, NOT 260. The old 260 held 240.8px of content inside 0.6rem of
   padding; the same characters at this page's 12.5% larger type need 270.9,
   and with the grown padding that is 292.5. The rail's titles are already
   ellipsised, so the alternative was clipping more of them, not a tighter rail.

   1.8fr : 1fr, NOT 1.35 : 1. The side column's floor is what the phone preview
   costs: 390px of screen plus 2 x 0.85rem of `.edtd-phone` padding is 420.6px,
   and anything narrower silently falls back to `max-inline-size: 100%` and
   stops being a true handset. At this ratio a MacBook Pro 16" gives the side
   column 479.6px - 59px of headroom over that floor - and hands the rest to
   the stage. */
.edtd-panes {
  display: grid;
  grid-template-columns: 295px minmax(0, 1.8fr) minmax(0, 1fr);
  gap: 1rem;
  align-items: start;
}

/* ── Past 1600px the stage stops sharing the surplus ──────────────────────
 *
 * A RATIO SPENDS EVERY NEW PIXEL PROPORTIONALLY, and past a point that is the
 * wrong answer: the side column holds a fixed 390px phone and a raw HTML
 * textarea that is comfortable well before 600px, while the stage is the slide
 * the owner is actually looking at. So above 1600 the side column takes a
 * SHARE-WITH-A-FLOOR-AND-A-CEILING and the stage takes everything else.
 *
 * 430px floor: the 420.6px the phone preview truly needs, plus 9px so a
 * rounding error cannot make it lie. 620px ceiling: past that the textarea is
 * only emptier. 24% is the value that makes this rule hand over from the ratio
 * above CONTINUOUSLY - at a 1600px viewport the ratio gives the side column
 * 434px and this gives it 430, so the stage grows by 4px across the boundary
 * instead of jumping backwards, which is what any rounder number does.
 *
 * The breakpoint is deliberately not one of the repo's two (1024 / 767). Those
 * two describe DEVICES; this one describes the width at which this particular
 * three-column layout has more room than its side column can use. */
@media (min-width: 1600px) {
  .edtd-panes {
    grid-template-columns: 295px minmax(0, 1fr) clamp(430px, 24%, 620px);
  }
}

@media (max-width: 1024px) {
  /* A tablet cannot carry three columns at a readable width, so the rail keeps
     its column and the stage and the side column stack under each other. */
  .edtd-panes {
    grid-template-columns: 250px minmax(0, 1fr);
  }

  .edtd-panes__side {
    grid-column: 1 / -1;
  }
}

.edtd-panes__rail,
.edtd-panes__stage,
.edtd-panes__side {
  min-width: 0;
}

.edtd-panes__stage,
.edtd-panes__side {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   2. THE SAVE BAR
   ══════════════════════════════════════════════════════════════════════════ */

.edtd-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem 1rem;
  margin-bottom: 1rem;
  padding: 0.75rem 1rem;
  border-radius: 12px;
  background: var(--edt-surface-solid);
  box-shadow: inset 0 0 0 1px var(--edt-line);
}

/* A hairline of state colour on the leading edge. Thin on purpose: the brand
   ramp and every strong fill belong on lines, never on a panel this size. */
.edtd-bar--dirty {
  box-shadow: inset 3px 0 0 var(--edt-pending-ink), inset 0 0 0 1px var(--edt-line);
}

.edtd-bar--conflict,
.edtd-bar--refused,
.edtd-bar--error {
  box-shadow: inset 3px 0 0 var(--edt-danger-ink), inset 0 0 0 1px var(--edt-line);
}

.edtd-bar__state {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  flex: 1 1 24rem;
  min-width: 0;
}

.edtd-bar__standing {
  color: var(--edt-ink);
  font-size: 0.9rem;
  font-weight: 500;
}

.edtd-bar__facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.9rem;
}

.edtd-bar__fact {
  color: var(--edt-ink-3);
  font-size: 0.78rem;
}

.edtd-bar__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.edtd-bar__note {
  flex: 1 0 100%;
  margin: 0;
  color: var(--edt-ink-2);
  font-size: 0.83rem;
  line-height: 1.55;
}

.edtd-bar__note--warn {
  color: var(--edt-warn-ink);
}

.edtd-bar__note--danger {
  color: var(--edt-danger-ink);
}

.edtd-bar__refusal {
  flex: 1 0 100%;
  padding: 0.6rem 0.8rem;
  border-radius: 10px;
  background: var(--edt-danger-bg);
  color: var(--edt-danger-ink);
  font-size: 0.83rem;
}

.edtd-bar__refusal-lead {
  margin: 0 0 0.35rem;
  font-weight: 600;
}

/* LTR: `padding-left`, not the `padding-right` a Hebrew sheet would use. */
.edtd-bar__refusal-list {
  margin: 0;
  padding-left: 1.1rem;
  line-height: 1.6;
}

.edtd-bar__refusal-note {
  margin: 0.35rem 0 0;
  color: var(--edt-ink-2);
}

/* ══════════════════════════════════════════════════════════════════════════
   3. THE SLIDE RAIL
   ══════════════════════════════════════════════════════════════════════════ */

.edtd-rail {
  padding: 0.6rem;
  border-radius: 12px;
  background: var(--edt-surface-solid);
  box-shadow: inset 0 0 0 1px var(--edt-line);
  /* Its own scroller, deliberately bounded: 111 slides otherwise push the
     stage and the side column off the bottom of a page that already scrolls. */
  max-height: calc(100dvh - var(--edt-shell-header-h) - 14rem);
  overflow-y: auto;
  overscroll-behavior-y: contain;
}

.edtd-rail__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0 0.35rem 0.4rem;
}

.edtd-rail__title {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-rail__count {
  color: var(--edt-ink-3);
  font-family: var(--font-mono);
  font-size: 0.75rem;
}

.edtd-rail__empty {
  margin: 0;
  padding: 0.5rem 0.35rem;
  color: var(--edt-ink-3);
  font-size: 0.8rem;
}

.edtd-rail__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

.edtd-rail__item {
  display: flex;
  align-items: stretch;
  gap: 0.2rem;
  border-radius: 8px;
}

.edtd-rail__item.is-active {
  background: rgba(0, 113, 227, 0.08);
}

.edtd-rail__open {
  appearance: none;
  border: 0;
  background: none;
  display: grid;
  grid-template-columns: 1.6rem minmax(0, 1fr);
  gap: 0.15rem 0.45rem;
  flex: 1 1 auto;
  min-width: 0;
  padding: 0.4rem 0.35rem;
  font: inherit;
  text-align: left;
  cursor: pointer;
  color: var(--edt-ink-2);
}

.edtd-rail__open:hover {
  color: var(--edt-ink);
}

.edtd-rail__order {
  grid-row: 1 / span 2;
  color: var(--edt-ink-3);
  font-family: var(--font-mono);
  font-size: 0.72rem;
  line-height: 1.5;
}

.edtd-rail__name {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  min-width: 0;
}

.edtd-rail__slide-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.82rem;
  /* The title is Hebrew data inside an English list, and the span carries
     `dir="auto"` (DeckSlideRail.vue) so a Hebrew one resolves to `direction:
     rtl` and a Latin one to ltr. `text-align: start` is what finishes the job:
     the shell inherits a physical `text-align: left`, and left-aligning an RTL
     line that is wider than its box pushes the line's START - the title's first
     character - off the right edge and puts the ellipsis over the opening
     words. `start` follows the resolved direction, so every title is anchored
     at its own beginning and clipped at its own end. */
  text-align: start;
}

.edtd-rail__slide-title.is-untitled {
  color: var(--edt-ink-3);
  font-style: italic;
}

.edtd-rail__meta {
  display: flex;
  gap: 0.5rem;
  color: var(--edt-ink-3);
  font-size: 0.7rem;
}

.edtd-rail__kind {
  font-family: var(--font-mono);
}

.edtd-rail__marks {
  grid-column: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
}

.edtd-rail__ops {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 0.1rem;
  opacity: 0;
  transition: opacity 0.12s;
}

.edtd-rail__item:hover .edtd-rail__ops,
.edtd-rail__item:focus-within .edtd-rail__ops {
  opacity: 1;
}

.edtd-rail__op {
  appearance: none;
  border: 0;
  background: none;
  padding: 0.12rem 0.3rem;
  color: var(--edt-ink-3);
  font-size: 0.68rem;
  cursor: pointer;
}

.edtd-rail__op:hover:not(:disabled) {
  color: var(--edt-ink);
}

.edtd-rail__op:disabled {
  opacity: 0.3;
  cursor: not-allowed;
}

.edtd-rail__op--danger:hover:not(:disabled) {
  color: var(--edt-danger-ink);
}

.edtd-rail__add-end {
  width: 100%;
  margin-top: 0.5rem;
}

/* ── The slides with no row: what this screen has deleted. ───────────────── */

.edtd-rail__absent {
  margin-top: 0.75rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--edt-line);
}

.edtd-rail__absent-title {
  margin: 0;
  color: var(--edt-ink-2);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

.edtd-rail__absent-note {
  margin: 0.25rem 0 0.4rem;
  color: var(--edt-ink-3);
  font-size: 0.7rem;
  line-height: 1.4;
}

.edtd-rail__absent-list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* The same two columns as a rail row, so a deleted slide lines up with the
   deck above it rather than reading as a different kind of thing. */
.edtd-rail__absent-item {
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  gap: 0.1rem 0.4rem;
  padding: 0.3rem 0.2rem;
  opacity: 0.75;
}

.edtd-rail__absent-where {
  color: var(--edt-ink-3);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  text-align: center;
}

/* ── The marks. Two dirt axes, two words, never one dot for both. ────────── */

.edtd-mark {
  display: inline-flex;
  align-items: center;
  gap: 0.2rem;
  padding: 0.05rem 0.35rem;
  border-radius: 999px;
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* Unsaved is fixed by Save, and so is moved: ONE axis, so one colour. The two
   words are what separate them, because a moved slide's fields can be exactly
   what the stored draft holds. */
.edtd-mark--unsaved,
.edtd-mark--moved {
  background: var(--edt-pending-bg);
  color: var(--edt-pending-ink);
}

/* Pending is fixed by Publish. A different colour AND a different word. */
.edtd-mark--pending {
  background: rgba(0, 113, 227, 0.1);
  color: var(--edt-accent-ink);
}

.edtd-mark--source {
  background: rgba(25, 26, 35, 0.06);
  color: var(--edt-ink-2);
  font-family: var(--font-mono);
}

.edtd-mark--over {
  background: var(--edt-danger-bg);
  color: var(--edt-danger-ink);
}

.edtd-mark--partial {
  background: var(--edt-warn-bg);
  color: var(--edt-warn-ink);
}

.edtd-mark--ok {
  background: var(--edt-ok-bg);
  color: var(--edt-ok-ink);
}

/* ══════════════════════════════════════════════════════════════════════════
   4. THE STAGE - the true-scale canvas and the slide's own columns
   ══════════════════════════════════════════════════════════════════════════ */

.edtd-stage__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

.edtd-stage__title {
  margin: 0;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-stage__steps {
  display: flex;
  align-items: center;
  gap: 0.25rem;
  /* Pushed to the end explicitly. The head carries three children now - title,
     position, steps - and `space-between` alone would strand the position in
     the middle of the row. */
  margin-inline-start: auto;
}

/* Which slide of how many, beside the stage's own heading. */
.edtd-stage__pos {
  color: var(--edt-ink-3);
  font-family: var(--font-mono);
  font-size: 0.75rem;
}

.edtd-stage__steps-label {
  color: var(--edt-ink-3);
  font-size: 0.75rem;
}

.edtd-stage__step {
  appearance: none;
  border: 0;
  border-radius: 6px;
  min-width: 1.7rem;
  padding: 0.15rem 0.35rem;
  background: rgba(255, 255, 255, 0.85);
  box-shadow: inset 0 0 0 1px var(--edt-line);
  color: var(--edt-ink-2);
  font: inherit;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  cursor: pointer;
}

.edtd-stage__step.is-active {
  background: var(--edt-accent);
  box-shadow: none;
  color: #fff;
}

/* ── The clicker: two arrows flanking the canvas ──────────────────────────
 *
 * LIFTED FROM class-app's presenter console (`.cl-clicker`, `.cl-stagewrap`,
 * `.cl-nav` in class-app/public/css/app.css) WITH ONE DELIBERATE INVERSION.
 *
 * The room's row is `flex-direction: row-reverse`, and that is an RTL
 * CORRECTION rather than part of the design: class-app's container is
 * `dir="rtl"`, where plain `row` would run the main axis right-to-left and put
 * Next on the LEFT, against its own glyph. This page is an LTR island
 * (admin-editor.css), so `row` already produces the physical order the DOM is
 * written in - back, slide, next - and copying the reversal would silently give
 * the owner two arrows pointing the wrong way.
 *
 * NOT LIFTED: `pointer-events: none` on the canvas and the click-to-advance
 * behind it. The room's preview is a picture; this one is the thing being
 * edited, and a click on it must reach the slide. That is also why the arrows
 * stand BESIDE the canvas rather than over it - `.edt-canvas` is
 * `overflow: hidden`, so an arrow inside it would be clipped, and one floated
 * over it would cover the slide the owner is reading. */
.edtd-clicker {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: var(--space-sm);
}

/* The stage takes whatever the two arrows leave and keeps its own 16:9 box, so
   the buttons cannot change how the slide is fitted. `align-items: stretch`
   above gives each arrow the canvas's full height, which makes the hit area the
   whole side of the slide rather than a chip beside it. */
.edtd-stagewrap {
  position: relative;
  flex: 1 1 auto;
  min-inline-size: 0;
}

/* A TINT RATHER THAN A CARD, which is the room's own reasoning: these stretch
   to 400px and more beside the slide, and a white panel with a shadow at that
   size reads as an empty card instead of as the side you press.
   `min-inline-size` / `min-block-size` are the 44px touch target. */
.edtd-nav {
  appearance: none;
  flex: none;
  inline-size: 54px;
  min-inline-size: 44px;
  min-block-size: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid var(--primary-border);
  border-radius: var(--radius-md);
  background: var(--primary-light);
  color: var(--primary-color);
  cursor: pointer;
  transition:
    background 0.12s ease,
    border-color 0.12s ease,
    color 0.12s ease,
    opacity 0.12s ease;
}

.edtd-nav:hover:not(:disabled) {
  background: var(--fsp-hover-bg);
  border-color: var(--primary-color);
}

/* Solid on the press. The slide changes on `click`, which fires on release, so
   this is the only feedback between the press landing and the canvas moving. */
.edtd-nav:active:not(:disabled) {
  background: var(--primary-color);
  border-color: var(--primary-color);
  color: #fff;
}

.edtd-nav:focus-visible {
  outline: 2px solid var(--primary-color);
  outline-offset: 2px;
}

/* DISABLED AT THE ENDS, which the room's arrows are not, and the difference is
   real rather than cosmetic. There, advancing is an INTENT the server clamps
   and the client never knows it is on the last slide. Here the slide list is a
   local, synchronous array and `deck.select` clamps nothing, so the press is
   inert exactly when there is nowhere to go and the button says so. */
.edtd-nav:disabled {
  opacity: 0.35;
  cursor: default;
}

.edtd-nav__glyph {
  inline-size: 28px;
  block-size: 28px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.edtd-stage__fields {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem 0.9rem;
}

.edtd-field {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  flex: 1 1 14rem;
  min-width: 0;
}

.edtd-field--narrow {
  flex: 0 0 9rem;
}

.edtd-field--static {
  flex: 0 0 7rem;
}

.edtd-field__label {
  color: var(--edt-ink-3);
  font-size: 0.72rem;
}

.edtd-field__value {
  color: var(--edt-ink-2);
  font-family: var(--font-mono);
  font-size: 0.8rem;
}

.edtd-field__input {
  appearance: none;
  border: 0;
  border-radius: 8px;
  padding: 0.35rem 0.55rem;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--edt-line);
  color: var(--edt-ink);
  font: inherit;
  font-size: 0.85rem;
  /* `text-align: start` follows the element's own `dir`, so a Hebrew title
     typed into a `dir="auto"` field sits at the right edge of its box without
     this sheet declaring a direction anywhere. */
  text-align: start;
}

.edtd-field__input:focus-visible {
  outline: 2px solid var(--edt-accent);
  outline-offset: 1px;
}

.edtd-field__input[readonly],
.edtd-field__input:disabled {
  background: rgba(25, 26, 35, 0.03);
  color: var(--edt-ink-2);
}

/* ══════════════════════════════════════════════════════════════════════════
   5. THE SIDE COLUMN - fit, raw HTML, the phone
   ══════════════════════════════════════════════════════════════════════════ */

.edtd-fit,
.edtd-raw,
.edtd-phone {
  padding: 0.7rem 0.85rem;
  border-radius: 12px;
  background: var(--edt-surface-solid);
  box-shadow: inset 0 0 0 1px var(--edt-line);
}

.edtd-fit__head,
.edtd-raw__head,
.edtd-phone__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  margin-bottom: 0.5rem;
}

.edtd-fit__title,
.edtd-raw__title,
.edtd-phone__title {
  margin: 0;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-fit__clean,
.edtd-fit__waiting,
.edtd-fit__refusal {
  margin: 0;
  color: var(--edt-ink-2);
  font-size: 0.8rem;
  line-height: 1.55;
}

.edtd-fit__refusal {
  color: var(--edt-danger-ink);
}

.edtd-fit__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.edtd-fit__row {
  padding-left: 0.6rem;
  border-left: 2px solid var(--edt-warn-ink);
  font-size: 0.78rem;
  color: var(--edt-ink-2);
  line-height: 1.5;
}

.edtd-fit__check {
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-fit__px {
  margin-left: 0.35rem;
  color: var(--edt-danger-ink);
  font-family: var(--font-mono);
}

.edtd-fit__at {
  margin-left: 0.35rem;
  color: var(--edt-ink-3);
}

.edtd-fit__offenders,
.edtd-fit__unmeasured {
  margin: 0.2rem 0 0;
  padding-left: 1rem;
}

.edtd-fit__offender {
  overflow-wrap: anywhere;
}

.edtd-fit__sel {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.edtd-fit__flag {
  margin-left: 0.3rem;
  color: var(--edt-ink-3);
  font-size: 0.9em;
}

.edtd-fit__suspect,
.edtd-fit__mascots,
.edtd-fit__title-lines {
  margin: 0.25rem 0 0;
  color: var(--edt-ink-3);
}

.edtd-side__probe {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  margin: 0;
  padding: 0 0.2rem;
  font-size: 0.76rem;
  color: var(--edt-ink-2);
}

.edtd-side__probe-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  cursor: pointer;
}

.edtd-side__probe-progress {
  color: var(--edt-ink-3);
  font-family: var(--font-mono);
}

.edtd-side__phone-mode {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.4rem 0.8rem;
  padding: 0 0.2rem;
}

/* WIDER THAN `--narrow`, because this one is a SELECT and a select is as wide
   as its longest option needs to be or it lies about what is chosen. At 9rem
   "Show a phone rewrite" and "Show the slide body" were both cut mid-word, so
   the control could not say which of the three modes the slide was in. The
   modifier itself stays at 9rem: its other use is a static "Kind" readout with
   one short word in it. */
.edtd-side__phone-mode .edtd-field--narrow {
  flex-basis: 11.5rem;
}

.edtd-side__phone-note {
  flex: 1 1 16rem;
  color: var(--edt-ink-3);
  font-size: 0.74rem;
  line-height: 1.5;
}

/* ── The raw HTML panel ─────────────────────────────────────────────────── */

.edtd-raw__gate {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.08rem 0.45rem;
  border-radius: 999px;
  font-size: 0.7rem;
  font-weight: 500;
}

.edtd-raw__gate.is-visual {
  background: var(--edt-ok-bg);
  color: var(--edt-ok-ink);
}

.edtd-raw__gate.is-source {
  background: rgba(25, 26, 35, 0.06);
  color: var(--edt-ink-2);
}

.edtd-raw__gate-busy {
  color: var(--edt-ink-3);
  font-weight: 400;
}

.edtd-raw__reason {
  margin: 0 0 0.5rem;
  color: var(--edt-ink-2);
  font-size: 0.76rem;
  line-height: 1.55;
}

.edtd-raw__at {
  color: var(--edt-ink-3);
  font-family: var(--font-mono);
}

.edtd-raw__text {
  display: block;
  width: 100%;
  min-height: 14rem;
  resize: vertical;
  border: 0;
  border-radius: 8px;
  padding: 0.55rem 0.6rem;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--edt-line);
  color: var(--edt-ink);
  font-family: var(--font-mono);
  font-size: 0.76rem;
  line-height: 1.55;
  /* The markup is LTR even when the words inside it are Hebrew: what is being
     edited here is a tag stream, and mirroring it puts the closing angle
     bracket of every tag at the wrong end of the line.
     `isolate`, NOT `plaintext`. `plaintext` re-derives each bidi paragraph's
     direction from its own first strong character, and with `white-space:
     pre-wrap` every newline starts a new paragraph - so a source line that
     begins with Hebrew became an RTL line and its tags were painted in reverse
     byte order: measured on `    כל תקלת <span class="tpl-code">...`, the `<`
     of the opening tag painted 116px to the RIGHT of that same tag's `>`.
     `isolate` takes the paragraph direction from `direction: ltr` above, so
     every line is drawn in the order its bytes are stored, whatever it starts
     with, and a Hebrew run inside it is still isolated from the tags. */
  text-align: left;
  unicode-bidi: isolate;
}

.edtd-raw__text:focus-visible {
  outline: 2px solid var(--edt-accent);
  outline-offset: 1px;
}

.edtd-raw__blocked {
  margin: 0.4rem 0 0;
  color: var(--edt-ink-3);
  font-size: 0.76rem;
}

.edtd-raw__reformat {
  margin: 0.5rem 0 0;
  padding: 0.5rem 0.65rem;
  border-radius: 10px;
  background: var(--edt-warn-bg);
  color: var(--edt-warn-ink);
  font-size: 0.76rem;
  line-height: 1.55;
}

.edtd-raw__reformat-yes {
  display: block;
  margin-top: 0.4rem;
}

.edtd-raw__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.45rem;
  margin-top: 0.55rem;
}

.edtd-raw__diff {
  color: var(--edt-ink-3);
  font-size: 0.74rem;
}

/* ── The phone preview ──────────────────────────────────────────────────── */

.edtd-phone__source {
  color: var(--edt-ink-3);
  font-size: 0.72rem;
}

.edtd-phone__frame {
  display: flex;
  justify-content: center;
}

/* 390px is the width the room's `.slide--phone` branch was calibrated at, and
   the width the student's own page reports as a phone. It is a fixed pixel box
   on purpose: a preview that stretched with the column would show a layout no
   handset produces. */
.edtd-phone__screen {
  inline-size: 390px;
  max-inline-size: 100%;
  max-block-size: 34rem;
  overflow-y: auto;
  overscroll-behavior-y: contain;
  padding: 0.9rem 1rem;
  border-radius: 18px;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--edt-line);
}

.edtd-phone__slide-title {
  margin: 0 0 0.5rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--edt-ink);
  /* Follows the element's own `dir="rtl"`. No `direction` declaration: see the
     header, and tests/editorContentTranslation.spec.ts. */
  text-align: start;
}

.edtd-phone__body {
  color: var(--edt-ink);
  font-size: 15px;
  line-height: 1.6;
  text-align: start;
}

.edtd-phone__empty {
  margin: 0;
  color: var(--edt-ink-3);
  font-size: 0.78rem;
}

/* ── The student's own blocks, inside the 390px column ──────────────────────
   The ask block, the two link rows and the note wear their REAL `.stu-deck__*`
   classes (see DeckPhonePreview.vue's header), so their drawing arrives whole
   from public/css/student-session-deck.css and nothing here restyles them.

   The two rules below are POSITION only, and both are scoped to this column so
   they cannot reach the student area itself - the leading compound is an
   editor-owned class, which is what tests/editorCssIsolation.spec.ts asks for.
   On the student's page the note sits OUTSIDE the padded stage and its rule
   therefore spans the full width of the phone; inside this padded screen it
   would be inset on both sides and read as a card. It is pulled back out to the
   screen's edges so the rule spans the column exactly as it does on a phone. */
.edtd-phone__screen .stu-deck__notes {
  margin: 0.9rem -1rem -0.9rem;
}

/* First child of the column: the ask block's own top margin is measured against
   a body above it, and there is not always one here. */
.edtd-phone__screen > .stu-deck__ask:first-child,
.edtd-phone__screen > .stu-deck__link-row:first-child {
  margin-top: 0;
}

.edtd-phone__limit {
  margin: 0.5rem 0 0;
  color: var(--edt-ink-3);
  font-size: 0.72rem;
  line-height: 1.5;
}

/* ══════════════════════════════════════════════════════════════════════════
   6. THE CONFLICT PANEL
   ══════════════════════════════════════════════════════════════════════════ */

.edtd-conflict {
  position: fixed;
  inset: 0;
  z-index: 1200;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
}

.edtd-conflict__scrim {
  position: absolute;
  inset: 0;
  background: rgba(12, 22, 40, 0.35);
}

.edtd-conflict__panel {
  position: relative;
  inline-size: min(34rem, 100%);
  max-block-size: 85dvh;
  overflow-y: auto;
  padding: 1.1rem 1.25rem;
  border-radius: 14px;
  background: #fff;
  box-shadow: 0 20px 60px rgba(8, 30, 70, 0.25);
}

.edtd-conflict__title {
  margin: 0 0 0.5rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-conflict__lead,
.edtd-conflict__who,
.edtd-conflict__unknown,
.edtd-conflict__overlap,
.edtd-conflict__clean,
.edtd-conflict__later {
  margin: 0 0 0.55rem;
  color: var(--edt-ink-2);
  font-size: 0.84rem;
  line-height: 1.6;
}

.edtd-conflict__lead {
  color: var(--edt-ok-ink);
  font-weight: 500;
}

.edtd-conflict__overlap {
  color: var(--edt-warn-ink);
}

.edtd-conflict__later {
  margin: 0.6rem 0 0;
  color: var(--edt-ink-3);
  font-size: 0.78rem;
}

.edtd-conflict__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   7. THE MEASURING STAGE

   Parked off the left edge of the viewport, NOT hidden. `display: none` and
   `visibility: hidden` both remove layout, so every rect would be 0x0 and every
   scroll port 0 - which the overflow walk reads as a slide that fits perfectly.
   The one failure mode this instrument must not have is a false clean.
   ══════════════════════════════════════════════════════════════════════════ */

.edtd-probe {
  position: fixed;
  top: 0;
  left: -20000px;
  z-index: -1;
  pointer-events: none;
}

/* Exactly 1920px, so SlideCanvas measures a scale of 1 and the transformed
   rects agree with the untransformed scroll heights. `measureCanvas` re-checks
   this and refuses rather than reporting a scaled number. */
.edtd-probe__frame {
  inline-size: 1920px;
}

/* ══════════════════════════════════════════════════════════════════════════
   9. THE EDITING SURFACE - track T7b
   ══════════════════════════════════════════════════════════════════════════
   Everything below draws the fields, the pickers and the two panels the Deck
   tab gained on top of T7a's shell. Same prefix, same rules: every class here
   is `edtd-`, every state class is written as a COMPOUND (`.edtd-chip.is-on`)
   and never bare, and nothing declares `container-type` or `direction`.

   The state-class rule is not style. A bare `.is-on { }` in an admin sheet is a
   global rule on a name half the panel already uses, and this bundle is one
   file loaded by every admin page.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── 9.1 The refusal that belongs to the whole body ───────────────────────── */

.edtd-stage__refusal {
  margin: 0;
  padding: 0.6rem 0.8rem;
  border-radius: 10px;
  background: rgba(200, 40, 40, 0.06);
  box-shadow: inset 0 0 0 1px rgba(200, 40, 40, 0.22);
  color: #96231f;
  font-size: 0.82rem;
  line-height: 1.6;
}

.edtd-stage__source-only {
  margin: 0;
  padding: 0.7rem 0.85rem;
  border-radius: 10px;
  background: var(--edt-surface-solid);
  box-shadow: inset 0 0 0 1px var(--edt-line);
  color: var(--edt-ink-2);
  font-size: 0.82rem;
  line-height: 1.6;
}

.edtd-stage__assets {
  display: flex;
  flex-direction: column;
  gap: 0.7rem;
  align-items: flex-start;
}

.edtd-stage__assets-toggle {
  align-self: flex-start;
}

/* ── 9.2 The slot inspector ───────────────────────────────────────────────── */

.edtd-slots,
.edtd-layout,
.edtd-assets,
.edtd-steps {
  padding: 0.85rem 0.95rem;
  border-radius: 12px;
  background: var(--edt-surface-solid);
  box-shadow: inset 0 0 0 1px var(--edt-line);
}

.edtd-slots__head,
.edtd-layout__head,
.edtd-assets__head,
.edtd-steps__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.6rem;
  margin-bottom: 0.5rem;
}

.edtd-slots__title,
.edtd-layout__title,
.edtd-assets__title,
.edtd-steps__title {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-slots__count,
.edtd-steps__count {
  font-size: 0.76rem;
  color: var(--edt-ink-3);
}

.edtd-slots__note,
.edtd-slots__empty,
.edtd-assets__note,
.edtd-layout__note,
.edtd-layout__hint,
.edtd-steps__note,
.edtd-steps__cap {
  margin: 0 0 0.6rem;
  color: var(--edt-ink-2);
  font-size: 0.78rem;
  line-height: 1.6;
}

.edtd-slots__missing,
.edtd-steps__disagree {
  margin: 0 0 0.6rem;
  padding: 0.5rem 0.7rem;
  border-radius: 8px;
  background: rgba(200, 140, 0, 0.08);
  box-shadow: inset 0 0 0 1px rgba(200, 140, 0, 0.25);
  color: #7a5300;
  font-size: 0.78rem;
  line-height: 1.6;
}

.edtd-slots__refusal,
.edtd-assets__refusal,
.edtd-steps__live-refusal,
.edtd-rich__refusal {
  margin: 0 0 0.6rem;
  padding: 0.5rem 0.7rem;
  border-radius: 8px;
  background: rgba(200, 40, 40, 0.06);
  box-shadow: inset 0 0 0 1px rgba(200, 40, 40, 0.22);
  color: #96231f;
  font-size: 0.78rem;
  line-height: 1.6;
}

.edtd-slots__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.edtd-slot {
  border-radius: 8px;
}

.edtd-slot.is-open {
  background: rgba(0, 113, 227, 0.04);
  box-shadow: inset 0 0 0 1px rgba(0, 113, 227, 0.18);
}

/* The indent is the SLOT depth, not the source depth: two elements at the same
   nesting level in the markup can sit at different depths in the vocabulary,
   and it is the vocabulary the reader is following. */
.edtd-slot--d1 .edtd-slot__head { padding-left: 0.9rem; }
.edtd-slot--d2 .edtd-slot__head { padding-left: 1.8rem; }
.edtd-slot--d3 .edtd-slot__head { padding-left: 2.7rem; }
.edtd-slot--d4 .edtd-slot__head { padding-left: 3.6rem; }

.edtd-slot__head {
  display: flex;
  align-items: baseline;
  gap: 0.45rem;
  width: 100%;
  padding: 0.35rem 0.5rem;
  border: 0;
  border-radius: 8px;
  background: transparent;
  text-align: left;
  cursor: pointer;
  font: inherit;
}

.edtd-slot__head:hover {
  background: rgba(25, 26, 35, 0.04);
}

.edtd-slot__key {
  font-family: var(--font-mono);
  font-size: 0.76rem;
  font-weight: 600;
  color: var(--edt-ink);
  white-space: nowrap;
}

.edtd-slot__index,
.edtd-slot__kind {
  font-size: 0.7rem;
  color: var(--edt-ink-3);
  white-space: nowrap;
}

.edtd-slot__tier {
  padding: 0 0.35rem;
  border-radius: 999px;
  font-size: 0.63rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  background: rgba(25, 26, 35, 0.06);
  color: var(--edt-ink-3);
  white-space: nowrap;
}

.edtd-slot__tier--template {
  background: rgba(0, 113, 227, 0.1);
  color: var(--edt-accent-ink);
}

.edtd-slot__tier--template-foreign {
  background: rgba(200, 140, 0, 0.12);
  color: #7a5300;
}

.edtd-slot__preview {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.76rem;
  color: var(--edt-ink-2);
}

.edtd-slot__body {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  padding: 0.2rem 0.6rem 0.7rem;
}

.edtd-slot__tag-note,
.edtd-slot__structural,
.edtd-slot__unknown {
  margin: 0;
  color: var(--edt-ink-3);
  font-size: 0.74rem;
  line-height: 1.6;
}

.edtd-slot__tag-note code,
.edtd-slot__unknown code {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.edtd-slot__field,
.edtd-slot__sub {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.edtd-slot__label {
  font-size: 0.72rem;
  color: var(--edt-ink-3);
}

.edtd-slot__input {
  width: 100%;
  padding: 0.4rem 0.55rem;
  border: 0;
  border-radius: 8px;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--edt-line);
  color: var(--edt-ink);
  font: inherit;
  font-size: 0.84rem;
  text-align: start;
  resize: vertical;
}

.edtd-slot__input:focus-visible {
  outline: 2px solid var(--edt-accent);
  outline-offset: 1px;
}

.edtd-slot__input--mono {
  font-family: var(--font-mono);
  font-size: 0.78rem;
}

.edtd-slot__thumb {
  max-width: 180px;
  border-radius: 8px;
  box-shadow: inset 0 0 0 1px var(--edt-line);
}

.edtd-slot__items {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.edtd-slot__item {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.edtd-slot__op {
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  border: 0;
  border-radius: 7px;
  background: rgba(25, 26, 35, 0.05);
  color: var(--edt-ink-2);
  cursor: pointer;
  font-size: 0.72rem;
}

.edtd-slot__op--danger:hover {
  background: rgba(200, 40, 40, 0.1);
  color: #96231f;
}

.edtd-slot__add,
.edtd-slot__pick {
  align-self: flex-start;
}

/* ── 9.3 Raw blocks: named, never given a field ───────────────────────────── */

.edtd-raws {
  margin-top: 0.8rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--edt-line);
}

.edtd-raws__title {
  margin: 0 0 0.3rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-raws__note {
  margin: 0 0 0.5rem;
  color: var(--edt-ink-3);
  font-size: 0.74rem;
  line-height: 1.6;
}

.edtd-raws__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.edtd-raw-chip {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.3rem 0.5rem;
  border-radius: 7px;
  background: rgba(25, 26, 35, 0.04);
  font-size: 0.74rem;
  color: var(--edt-ink-2);
}

.edtd-raw-chip.is-passthrough {
  opacity: 0.7;
}

.edtd-raw-chip__label {
  font-family: var(--font-mono);
  font-size: 0.9em;
  color: var(--edt-ink);
}

.edtd-raw-chip__at {
  margin-left: auto;
  color: var(--edt-ink-3);
  font-size: 0.68rem;
}

/* ── 9.4 The rich-text surface ────────────────────────────────────────────── */

.edtd-rich {
  border-radius: 8px;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--edt-line);
}

.edtd-rich__bar {
  display: flex;
  gap: 2px;
  padding: 3px;
  border-bottom: 1px solid var(--edt-line);
}

.edtd-rich__btn {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem 0.45rem;
  border: 0;
  border-radius: 6px;
  background: transparent;
  color: var(--edt-ink-2);
  cursor: pointer;
  font-size: 0.72rem;
}

.edtd-rich__btn:hover {
  background: rgba(25, 26, 35, 0.05);
}

.edtd-rich__btn.is-on {
  background: rgba(0, 113, 227, 0.12);
  color: var(--edt-accent-ink);
}

.edtd-rich__btn-label {
  font-family: var(--font-mono);
  font-size: 0.68rem;
}

/* The bold-weight measurement, on screen rather than in a `title`. A tooltip
   needs a pointer and this editor is declared usable on a tablet, so the only
   disclosure that the stage headed "On the projector" draws <b> at the
   student's weight and not the room's cannot be a hover. Quiet, not red: it is
   a fact about two surfaces disagreeing, not a refusal.

   BELOW the surface, hence a top border and not a bottom one. It used to sit
   between the toolbar and the editing surface, where its 65.7px of flow (at a
   488px column; more at 380px, where it wraps to another line) pushed the line
   under the caret down the panel every time it appeared. It appears far less
   often now - the component gates it on the slot CONTAINING <b> rather than on
   the caret sitting inside one - but below the surface it cannot move the text
   being edited even then. Nothing here may be `position: absolute`: an overlay
   would stop the shift by covering the next field instead, which is a mis-click
   where this is a scroll. */
.edtd-rich__note {
  margin: 0;
  padding: 0.35rem 0.55rem;
  border-top: 1px solid var(--edt-line);
  background: rgba(0, 113, 227, 0.05);
  color: var(--edt-ink-2);
  font-size: 0.72rem;
  line-height: 1.55;
}

/* The editable surface itself. `text-align: start` rather than a direction
   declaration: the `dir` attribute on the element carries the bidi, and a
   `direction` here would be a second RTL island on a page pinned at one. */
.edtd-rich__surface {
  min-height: 2.4em;
  padding: 0.4rem 0.55rem;
  text-align: start;
  font-size: 0.84rem;
  line-height: 1.7;
}

.edtd-rich__surface:focus-visible {
  outline: 2px solid var(--edt-accent);
  outline-offset: -2px;
  border-radius: 8px;
}

/* ── 9.5 The layout picker ────────────────────────────────────────────────── */

.edtd-layout__classes {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--edt-ink-3);
  text-align: start;
}

.edtd-layout__group {
  margin-top: 0.8rem;
  padding-top: 0.7rem;
  border-top: 1px solid var(--edt-line);
}

.edtd-layout__sub {
  margin: 0 0 0.35rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-layout__tiles {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(169px, 1fr));
  gap: 0.4rem;
}

.edtd-tile {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  padding: 0.5rem 0.6rem;
  border: 0;
  border-radius: 9px;
  background: rgba(25, 26, 35, 0.03);
  box-shadow: inset 0 0 0 1px var(--edt-line);
  text-align: left;
  cursor: pointer;
  font: inherit;
}

.edtd-tile:disabled {
  cursor: default;
  opacity: 0.55;
}

.edtd-tile.is-current {
  background: rgba(0, 113, 227, 0.08);
  box-shadow: inset 0 0 0 1px rgba(0, 113, 227, 0.3);
  opacity: 1;
}

.edtd-tile.is-candidate {
  background: rgba(0, 113, 227, 0.14);
  box-shadow: inset 0 0 0 2px var(--edt-accent);
}

.edtd-tile--extended .edtd-tile__tier {
  color: #7a5300;
}

.edtd-tile__name {
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-tile__purpose {
  font-size: 0.7rem;
  line-height: 1.5;
  color: var(--edt-ink-2);
}

.edtd-tile__meta {
  display: flex;
  gap: 0.4rem;
  font-size: 0.64rem;
  color: var(--edt-ink-3);
}

.edtd-plan {
  margin-top: 0.7rem;
  padding: 0.7rem 0.8rem;
  border-radius: 10px;
  background: rgba(0, 113, 227, 0.05);
  box-shadow: inset 0 0 0 1px rgba(0, 113, 227, 0.22);
}

.edtd-plan__title {
  margin: 0 0 0.35rem;
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--edt-ink);
}

/* BEFORE AND AFTER, SIDE BY SIDE. Two `SlideCanvas`es, so each keeps its own
   1920x1080 coordinate system and its own 16:9 frame. Nothing here sizes the
   slide, and nothing here may ever declare `container-type`: `cqw` resolves
   against the nearest container ancestor, and a second one between this panel
   and a `.tpl` would silently re-base every size in the template vocabulary. */
.edtd-plan__preview {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.5rem;
  margin: 0 0 0.55rem;
}

.edtd-plan__shot {
  margin: 0;
}

.edtd-plan__shot-cap {
  margin: 0 0 0.25rem;
  font-size: 0.7rem;
  color: var(--edt-ink-3);
}

.edtd-plan__budget,
.edtd-plan__safe,
.edtd-plan__borrowed,
.edtd-plan__missing {
  margin: 0 0 0.45rem;
  font-size: 0.76rem;
  line-height: 1.6;
  color: var(--edt-ink-2);
}

/* ── The second press: what a rebuild would do ────────────────────────────
 *
 * Set apart from the lines above it, because those describe a change that
 * touches no byte inside the slide and this one rewrites the slide's whole
 * interior. Two presses sit under this block and the owner has to be able to
 * tell at a glance which paragraph belongs to which.
 *
 * A TINT AND A RULE, not a card with a shadow. The plan panel is already a
 * panel; a second card inside it reads as a modal and this is a paragraph. */
.edtd-plan__build {
  margin: 0.6rem 0 0.55rem;
  padding: 0.55rem 0.7rem;
  border-radius: 8px;
  border-inline-start: 2px solid var(--primary-color);
  background: var(--primary-light);
}

.edtd-plan__build-lead {
  margin: 0;
  font-size: 0.78rem;
  line-height: 1.6;
  color: var(--edt-ink);
}

/* What the rebuild AUTHORED, and what it dropped. Kept in the tinted block
   rather than beside the loss list above, because those losses are about
   fields and these are about the slide itself. */
.edtd-plan__wrote {
  margin: 0.35rem 0 0;
  font-size: 0.74rem;
  line-height: 1.6;
  color: var(--edt-ink-2);
}

.edtd-plan__wrote code {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.edtd-plan__wrote code + code::before {
  content: ', ';
  font-family: var(--font-body);
}

/* WHY THERE IS NO SECOND BUTTON. A sentence, not a warning: the press above it
   is unaffected and still does exactly what it says. */
.edtd-plan__norebuild {
  margin: 0.6rem 0 0.55rem;
  font-size: 0.76rem;
  line-height: 1.6;
  color: var(--edt-ink-3);
}

.edtd-plan__safe code,
.edtd-plan__missing code {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

/* The list of parts that stop having a field. It is a warning and it is
   deliberately the loudest thing in the panel: nothing is deleted by a template
   change, but this is the half an owner would otherwise discover by hunting for
   a field that is no longer there. */
.edtd-plan__loss {
  margin: 0 0 0.5rem;
  padding: 0.5rem 0.65rem;
  border-radius: 8px;
  background: rgba(200, 140, 0, 0.1);
  box-shadow: inset 0 0 0 1px rgba(200, 140, 0, 0.3);
}

.edtd-plan__loss-lead {
  margin: 0 0 0.35rem;
  font-size: 0.76rem;
  line-height: 1.6;
  color: #7a5300;
}

.edtd-plan__loss-list,
.edtd-plan__over {
  margin: 0;
  padding-left: 1.1rem;
  font-size: 0.74rem;
  line-height: 1.6;
  color: var(--edt-ink-2);
}

.edtd-plan__loss-list code {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.edtd-plan__loss-text {
  margin-left: 0.4rem;
  color: var(--edt-ink-3);
}

.edtd-plan__actions {
  display: flex;
  gap: 0.4rem;
  margin-top: 0.6rem;
}

.edtd-layout__chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.edtd-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.28rem 0.6rem;
  border: 0;
  border-radius: 999px;
  background: rgba(25, 26, 35, 0.05);
  color: var(--edt-ink-2);
  cursor: pointer;
  font-size: 0.74rem;
}

.edtd-chip:disabled {
  cursor: default;
  opacity: 0.45;
}

.edtd-chip.is-on {
  background: rgba(0, 113, 227, 0.14);
  color: var(--edt-accent-ink);
  font-weight: 600;
}

.edtd-chip--reserved {
  box-shadow: inset 0 0 0 1px rgba(200, 140, 0, 0.4);
}

.edtd-chip__lock {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #7a5300;
}

.edtd-layout__reserved {
  margin-top: 0.5rem;
  padding: 0.55rem 0.7rem;
  border-radius: 8px;
  background: rgba(200, 140, 0, 0.07);
  box-shadow: inset 0 0 0 1px rgba(200, 140, 0, 0.25);
}

.edtd-layout__confirm {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.78rem;
  color: #7a5300;
}

.edtd-layout__rules,
.edtd-layout__ladder,
.edtd-steps__problems {
  margin: 0.4rem 0 0;
  padding-left: 1.1rem;
  font-size: 0.73rem;
  line-height: 1.6;
  color: var(--edt-ink-2);
}

.edtd-layout__rules li.is-error,
.edtd-steps__problems li.is-error {
  color: #96231f;
}

/* ── 9.6 The asset picker ─────────────────────────────────────────────────── */

.edtd-assets {
  width: 100%;
}

.edtd-assets__tabs {
  display: flex;
  gap: 2px;
}

.edtd-assets__tab {
  padding: 0.25rem 0.6rem;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--edt-ink-2);
  cursor: pointer;
  font-size: 0.74rem;
}

.edtd-assets__tab.is-on {
  background: rgba(0, 113, 227, 0.12);
  color: var(--edt-accent-ink);
  font-weight: 600;
}

.edtd-assets__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(124px, 1fr));
  gap: 0.4rem;
  margin: 0 0 0.6rem;
  padding: 0;
  list-style: none;
  max-height: 320px;
  overflow-y: auto;
}

.edtd-assets__grid--mascot {
  grid-template-columns: repeat(auto-fill, minmax(95px, 1fr));
}

.edtd-asset {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.25rem;
  width: 100%;
  padding: 0.35rem;
  border: 0;
  border-radius: 8px;
  background: rgba(25, 26, 35, 0.03);
  box-shadow: inset 0 0 0 1px var(--edt-line);
  cursor: pointer;
  font: inherit;
}

.edtd-asset.is-on {
  background: rgba(0, 113, 227, 0.12);
  box-shadow: inset 0 0 0 2px var(--edt-accent);
}

.edtd-asset__img {
  width: 100%;
  height: 62px;
  object-fit: contain;
}

.edtd-asset__label {
  font-size: 0.66rem;
  line-height: 1.35;
  text-align: center;
  color: var(--edt-ink-2);
}

.edtd-assets__form {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 0.6rem;
}

.edtd-assets__field {
  display: flex;
  flex: 1 1 11rem;
  flex-direction: column;
  gap: 0.25rem;
}

.edtd-assets__label {
  font-size: 0.7rem;
  color: var(--edt-ink-3);
}

.edtd-assets__check {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.76rem;
  color: var(--edt-ink-2);
}

.edtd-assets__preview {
  padding: 0.6rem 0.7rem;
  border-radius: 9px;
  background: rgba(25, 26, 35, 0.03);
  box-shadow: inset 0 0 0 1px var(--edt-line);
}

.edtd-assets__mount {
  margin: 0 0 0.4rem;
  font-size: 0.74rem;
  line-height: 1.6;
  color: var(--edt-ink-2);
}

.edtd-assets__markup {
  margin: 0 0 0.4rem;
  padding: 0.5rem 0.6rem;
  border-radius: 7px;
  background: #fff;
  box-shadow: inset 0 0 0 1px var(--edt-line);
  overflow-x: auto;
  text-align: start;
  font-family: var(--font-mono);
  font-size: 0.7rem;
  line-height: 1.55;
  white-space: pre;
}

.edtd-assets__warnings {
  margin: 0 0 0.5rem;
  padding-left: 1.1rem;
  font-size: 0.73rem;
  line-height: 1.6;
  color: #7a5300;
}

/* ── 9.7 The build panel ──────────────────────────────────────────────────── */

.edtd-steps__note code,
.edtd-steps__cap code,
.edtd-steps__disagree code,
.edtd-steps__problems code {
  font-family: var(--font-mono);
  font-size: 0.9em;
}

.edtd-steps__fix {
  margin-left: 0.4rem;
}

.edtd-steps__live {
  margin-top: 0.6rem;
  padding: 0.55rem 0.7rem;
  border-radius: 8px;
  background: rgba(0, 113, 227, 0.06);
  box-shadow: inset 0 0 0 1px rgba(0, 113, 227, 0.22);
  font-size: 0.76rem;
  line-height: 1.6;
  color: var(--edt-ink-2);
}

.edtd-steps__live p {
  margin: 0 0 0.3rem;
}

.edtd-steps__live.is-blocked {
  background: rgba(200, 40, 40, 0.06);
  box-shadow: inset 0 0 0 1px rgba(200, 40, 40, 0.25);
}

/* ── 9.8 The shape check, and the ladder it gates ─────────────────────────── */

.edtd-mark--shape {
  background: rgba(200, 140, 0, 0.16);
  color: #7a5300;
}

.edtd-shape {
  margin-bottom: 0.6rem;
  padding: 0.6rem 0.75rem;
  border-radius: 10px;
  background: rgba(200, 140, 0, 0.09);
  box-shadow: inset 0 0 0 1px rgba(200, 140, 0, 0.3);
}

.edtd-shape__title {
  margin: 0 0 0.4rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: #7a5300;
}

.edtd-shape__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.edtd-shape__detail {
  margin: 0 0 0.25rem;
  font-size: 0.77rem;
  line-height: 1.6;
  color: var(--edt-ink);
}

.edtd-shape__rule {
  margin: 0;
  font-size: 0.71rem;
  line-height: 1.55;
  color: var(--edt-ink-3);
}

.edtd-shape__rule code,
.edtd-shape__sel code {
  font-family: var(--font-mono);
  font-size: 0.92em;
}

.edtd-shape__gate {
  margin: 0.5rem 0 0;
  font-size: 0.73rem;
  line-height: 1.6;
  color: #7a5300;
}

.edtd-shape__other {
  margin: 0 0 0.6rem;
  padding-left: 1.1rem;
  font-size: 0.74rem;
  line-height: 1.6;
  color: var(--edt-ink-2);
}

.edtd-ladder {
  margin-top: 0.7rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--edt-line);
}

.edtd-ladder__title {
  margin: 0 0 0.35rem;
  font-size: 0.79rem;
  font-weight: 600;
  color: var(--edt-ink);
}

.edtd-ladder__list {
  margin: 0;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.edtd-ladder__rung {
  font-size: 0.74rem;
  line-height: 1.6;
  color: var(--edt-ink-2);
}

.edtd-ladder__do {
  display: inline-block;
  margin-left: 0.4rem;
}

.edtd-ladder__forbidden {
  margin: 0.5rem 0 0;
  padding: 0.45rem 0.6rem;
  border-radius: 8px;
  background: rgba(200, 40, 40, 0.05);
  font-size: 0.71rem;
  line-height: 1.55;
  color: #96231f;
}

/* ── 9.6b Upload and search: pictures that are not in the repo ────────────── */

/* The two tabs that bring a picture in from outside carry a standing caveat
 * that the three catalogue tabs do not: the file exists on THIS site only, so
 * the lecture-room projector draws it only once class-app forwards
 * /deck-media/ to this origin. It is a warning rather than a refusal because
 * most decks are read on the site and on phones, where it is simply correct. */
.edtd-assets__note--warn {
  padding: 0.5rem 0.7rem;
  border-radius: 8px;
  background: var(--edt-warn-bg);
  color: var(--edt-warn-ink);
}

/* The file input itself is never shown: a bare <input type="file"> renders as
 * the platform's own control, which cannot be styled and reads as a hole in
 * the panel. The label is the button, so a press anywhere on it opens the
 * picker and the keyboard path still works through the real input. */
.edtd-upload {
  display: block;
  margin: 0 0 0.6rem;
}

.edtd-upload__input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}

.edtd-upload__cta {
  display: block;
  padding: 0.85rem 0.7rem;
  border-radius: 9px;
  background: rgba(25, 26, 35, 0.03);
  box-shadow: inset 0 0 0 1px var(--edt-line);
  color: var(--edt-ink-2);
  cursor: pointer;
  font-size: 0.8rem;
  text-align: center;
}

.edtd-upload__input:focus-visible + .edtd-upload__cta {
  box-shadow: inset 0 0 0 2px var(--edt-accent);
}

.edtd-upload__input:disabled + .edtd-upload__cta {
  cursor: default;
  opacity: 0.55;
}

/* The picture that is about to go in, with its size, its licence and its two
 * fields. Bounded height: a 1920px-wide preview inside a side panel would push
 * the caption fields and the insert button below the fold. */
.edtd-brought {
  margin: 0 0 0.6rem;
  padding: 0.6rem 0.7rem;
  border-radius: 9px;
  background: rgba(25, 26, 35, 0.03);
  box-shadow: inset 0 0 0 1px var(--edt-line);
}

.edtd-brought__preview {
  display: block;
  width: 100%;
  max-height: 180px;
  margin: 0 0 0.5rem;
  border-radius: 7px;
  background: #fff;
  object-fit: contain;
}

/* No `direction` here: this sheet declares one in exactly zero places and
 * `tests/editorDeckTab.spec.ts` pins that. The page is already LTR from
 * admin-editor-templates.css, which owns the editor's one direction rule. */
.edtd-brought__facts {
  margin: 0 0 0.35rem;
  color: var(--edt-ink-3);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  text-align: start;
}

.edtd-brought__credit {
  margin: 0 0 0.5rem;
  color: var(--edt-ink-2);
  font-size: 0.74rem;
  line-height: 1.6;
}

/* ── The search tab ───────────────────────────────────────────────────────── */

.edtd-imgsearch {
  width: 100%;
}

.edtd-imgsearch__bar {
  display: flex;
  gap: 0.4rem;
  margin: 0 0 0.6rem;
}

.edtd-imgsearch__input {
  flex: 1 1 auto;
  min-width: 0;
}

/* Wider cells than the catalogue grid, and taller: a photograph needs the room
 * that an icon does not, and each cell carries three lines of licence under it
 * rather than one label. */
.edtd-imgsearch__grid {
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  max-height: 380px;
}

.edtd-imgsearch__cell {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.edtd-imgsearch__pick {
  flex: 1 1 auto;
}

/* The licence line. It is deliberately ON the card rather than behind a hover
 * or an info button: these pictures go on the slides of a course people pay
 * for, and a condition nobody read is a condition nobody met. */
.edtd-imgsearch__meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem 0.4rem;
  margin: 0;
  font-size: 0.64rem;
  line-height: 1.4;
  color: var(--edt-ink-3);
}

.edtd-imgsearch__provider {
  color: var(--edt-ink-2);
  font-weight: 600;
}

.edtd-imgsearch__by {
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.edtd-imgsearch__licence {
  color: var(--edt-accent-ink);
}

.edtd-imgsearch__must {
  padding: 0 0.3rem;
  border-radius: 999px;
  background: var(--edt-warn-bg);
  color: var(--edt-warn-ink);
}
