/* ============================================================================
   Sketch Layout — a fixed vocabulary of arrangements for interactive sketches.

   The canvas occupies one named slot ("stage"); every other slot is real HTML
   laid out by CSS Grid and rearranged by container queries. No JS measures
   anything: the browser does the layout, and sketch-runner's existing
   ResizeObserver resizes the canvas from whatever the stage cell became.

   Layout sets (see xLayout.js):
     figure    stage
     explorer  stage + aside
     player    stage + aside + bar
     prompt    head + stage + bar
     trace     code + stage + out + bar
   ========================================================================== */

/* Defaults on :root so the layout still renders somewhere no ancestor carries
   a .theme-* class (the sketch editor preview, the headless render harness).
   The theme blocks below override them wherever a theme is actually set. */
:root {
    --sl-chrome-bg:   rgba(255, 255, 255, 0.035);
    --sl-rule:        rgba(255, 255, 255, 0.08);
    --sl-text:        rgba(255, 255, 255, 0.90);
    --sl-text-soft:   rgba(255, 255, 255, 0.52);
    --sl-text-faint:  rgba(255, 255, 255, 0.32);
    --sl-accent:      rgb(160, 218, 0);
    --sl-accent-dim:  rgba(160, 218, 0, 0.16);
    --sl-overlay-bg:  rgba(20, 21, 15, 0.92);
    --sl-fade-to:     #14150f;
}

.theme-dark {
    --sl-chrome-bg:   rgba(255, 255, 255, 0.035);
    --sl-rule:        rgba(255, 255, 255, 0.08);
    --sl-text:        rgba(255, 255, 255, 0.90);
    --sl-text-soft:   rgba(255, 255, 255, 0.52);
    --sl-text-faint:  rgba(255, 255, 255, 0.32);
    --sl-accent:      rgb(160, 218, 0);
    --sl-accent-dim:  rgba(160, 218, 0, 0.16);
    --sl-overlay-bg:  rgba(20, 21, 15, 0.92);
    --sl-fade-to:     #14150f;
}
.theme-light {
    --sl-chrome-bg:   rgba(0, 0, 0, 0.03);
    --sl-rule:        rgba(0, 0, 0, 0.10);
    --sl-text:        rgba(0, 0, 0, 0.85);
    --sl-text-soft:   rgba(0, 0, 0, 0.55);
    --sl-text-faint:  rgba(0, 0, 0, 0.38);
    --sl-accent:      rgb(130, 180, 0);
    --sl-accent-dim:  rgba(130, 180, 0, 0.18);
    --sl-overlay-bg:  rgba(238, 238, 238, 0.92);
    --sl-fade-to:     #eeeeee;
}

/* --------------------------------------------------------------- shell --- */
/* The shell is the query container; .sl-grid inside is what responds to it.
   An element cannot query itself, so these must be two elements. */
.sl-shell {
    container-type: inline-size;
    width: 100%;
    position: relative;
}
.sl-grid {
    display: grid;
    height: 100%;
}

/* ---------------------------------------------------------------- slots --- */
/* --sl-ar gives the stage a definite height from its own width, mirroring the
   sketch's heightExpr. In the default (content-governed) mode the row grows to
   whichever slot is tallest and the stage stretches with it, so a docked slot
   never has to scroll — aspect-ratio yields to the stretched height. */
.sl-stage {
    position: relative;
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* The width comes from the grid column and nothing else. Without these,
       --sl-min-h feeds back through aspect-ratio into an implied *width*
       (300px tall x 1.6 = 480px wide) which becomes the item's automatic
       minimum size — min-width:0 does not suppress a ratio-derived one. The
       stage then overflowed its column and painted over the aside. A definite
       width makes the ratio compute height from width, so a floor makes the
       stage taller, never wider, which is the whole point of it. */
    width: 100%;
    max-width: 100%;
    /* min-height:0 is not cosmetic. Without it the grid row takes its
       min-content height from the canvas, so sizing the canvas to the stage
       grew the stage, which resized the canvas again — the two fought and the
       layout visibly oscillated between two states. */
    overflow: hidden;
}
/* The drawing box: the stage minus whatever the readings band took. Every
   sketch is sized from this box (the runner's observer watches it), so a
   reading laid on the figure costs the drawing exactly its own height and
   nothing overlaps — including in the 41 of 43 sketches that never ask where
   they may draw. With no readings, --sl-read-h is 0 and this is the stage. */
.sl-field {
    position: relative;
    flex: 1 1 auto;
    width: 100%;
    max-width: 100%;
    min-height: var(--sl-min-h, 0px);
    /* The ratio is a floor, not a mandate: past --sl-max-h the drawing box
       stops growing taller and only gets wider. */
    max-height: var(--sl-max-h, none);
    aspect-ratio: var(--sl-ar, 1.7);
}

/* Readings laid straight on the figure's ground: no panel, no border. They
   read as part of the drawing, which is the point — the value belongs to the
   picture, not to a column of chrome beside it. */
.sl-readings {
    position: relative;
    z-index: 4;
    flex: none;
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 2px 14px;
    padding: 2px 0 8px;
    pointer-events: none;
}
.sl-readings.sl-top-right { justify-content: flex-end; text-align: right; }
.sl-readings > .sl-status { margin: 0; flex: 0 1 auto; min-width: 0; }
/* A reading on the figure is a reading, not a heading: one step down in size
   from the aside, so it sits on the drawing without shouting over it. */
.sl-readings > .sl-status { font-size: 12.5px; }

/* A reading's second line is context for the first — and sometimes it holds
   the result itself ("OA' = 20 cm" under the Descartes relation). Hiding it on
   the figure made the answer visible nowhere, so it runs on inline instead,
   one step quieter. Height is then governed by the demotion rule in
   xLayout: a reading that still doesn't fit goes back to the column whole,
   rather than being shown with a piece missing.
   Specificity note: .sl-status .sl-sub sets display:block further down this
   file, so these selectors have to outrank it, not merely follow it. */
.sl-readings > .sl-status .sl-sub { display: inline; margin-top: 0; font-size: 12px; }
.sl-readings > .sl-status .sl-sub::before { content: ' · '; color: var(--sl-text-faint); }
.sl-readings.sl-no-sub > .sl-status .sl-sub { display: none; }

/* Out of flow entirely, so nothing inside the stage can ever feed back into
   the row's height. The stage's size is decided by the grid alone: the ratio,
   --sl-min-h, and whatever the tallest slot stretched the row to. */
.sl-stage .sketch-placeholder {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    min-height: 0;
}

/* Chrome carries no surface by default: a sketch already sits inside a
   .block, which is itself a card. Filling the slots too would nest a card in
   a card — visibly wrong inside a théorème/définition block, which has its
   own tint and border. Slots are separated by hairlines instead, and a
   hairline in currentColor composes with any block type. `surface: 'card'`
   opts back in for a sketch that stands alone. */
/* Slots stack their contents with the same rhythm a floating panel body
   uses (flex + gap), rather than relying on each component's own margins —
   which is why controls sat flush against each other before. */
.sl-inner,
.sl-head,
.sl-out,
.sl-code {
    display: flex;
    flex-direction: column;
    gap: var(--sui-spacing-lg, 10px);
}

.sl-chrome,
.sl-overlay {
    background: none;
    min-width: 0;
    color: var(--sl-text);
    /* Chrome is UI, not prose — it must not inherit the document's serif. */
    font-family: var(--ui-font, 'IBM Plex Sans', system-ui, sans-serif);
}
.sl-grid.sl-card .sl-chrome { background: var(--sl-chrome-bg); }

/* --------------------------------------------------------- inset surface -
   `surface: 'inset'` gives the sketch the treatment the document already uses
   for a code block — the same radius, top hairline and inset shadows, taken
   from prism.css's `code` rule rather than re-invented. Note there is no drop
   shadow: a code block is recessed, not raised, and in dark mode it carries no
   fill at all — the inset shadows alone describe the surface. That matters
   because it avoids laying a second opaque panel inside a .block that already
   has one. The point is that this is not a new visual language: a reader
   already accepts a code block as a distinct-but-native surface inside any
   block type, so a sketch borrowing it inherits that acceptance. */
.sl-shell.sl-inset {
    background: var(--sl-inset-bg);
    border-top: 1px solid var(--sl-inset-edge);
    border-radius: 10px;
    box-shadow: var(--sl-inset-shadow);
    overflow: hidden;
}
:root,
.theme-dark {
    --sl-inset-bg: transparent;
    --sl-inset-edge: rgba(255, 255, 255, 0.12);
    --sl-inset-shadow: 0 0 4px inset rgba(100, 100, 100, 0.2),
                       0 0 56px inset rgba(100, 100, 100, 0.1);
}
.theme-light {
    --sl-inset-bg: #f8f8f8;
    --sl-inset-edge: #e0e0e0;
    --sl-inset-shadow: 0 0 4px inset rgba(0, 0, 0, 0.05),
                       0 0 56px inset rgba(0, 0, 0, 0.02);
}
/* an inset sketch needs its own breathing room, since it no longer shares
   the block's padding */
.sl-shell.sl-inset > .sl-grid { padding: 14px 15px; }
.sl-shell.sl-inset .sl-bar { padding-left: 0; padding-right: 0; }
.sl-inner  { padding: 14px 15px; }

.sl-aside { border-top: 1px solid var(--sl-rule); }
.sl-head  { border-bottom: 1px solid var(--sl-rule); padding: 12px 15px; }
.sl-out   { border-top: 1px solid var(--sl-rule); padding: 12px 15px; min-height: 70px; }
.sl-code  { border-bottom: 1px solid var(--sl-rule); padding: 14px 15px; }
.sl-bar {
    border-top: 1px solid var(--sl-rule);
    padding: 10px 12px;
    display: flex;
    align-items: center;
    gap: 8px;
    flex-wrap: nowrap;
}

/* ---------------------------------------------------- ratio-locked mode --- */
/* Opt-in, for sketches whose drawing genuinely depends on its aspect ratio:
   the docked slot leaves flow so the stage's ratio decides the row exactly,
   and the slot scrolls if it must — with a fade to say so. Scrolling inside a
   slot is a last resort, which is why this is not the default. */
.sl-grid.sl-locked .sl-aside { position: relative; }
.sl-grid.sl-locked .sl-aside > .sl-inner {
    position: absolute;
    inset: 0;
    overflow-y: auto;
}
.sl-aside::after {
    content: '';
    position: absolute;
    left: 0; right: 0; bottom: 0;
    height: 24px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s ease;
    background: linear-gradient(transparent, var(--sl-fade-to));
}
.sl-aside.sl-scrolls::after { opacity: 1; }

/* ------------------------------------------------- bare edge alignment ---
   With no surface behind them, slots must not hold their own outer padding:
   the enclosing .block already supplies the margin, and a second inset makes
   the sketch float free of the text column above it. So padding survives only
   on the edges *between* slots — a bar's buttons sit flush to the bottom-left
   corner, an aside's text lines up with the block's right edge. `surface:
   'card'` keeps the padding all round, because there the fill needs it. */
.sl-grid:not(.sl-card) .sl-bar   { padding: 10px 0 0 0; border-top: none; }
.sl-grid:not(.sl-card) .sl-head  { padding: 0 0 11px 0; }
.sl-grid:not(.sl-card) .sl-out   { padding: 11px 0 0 0; }
.sl-grid:not(.sl-card) .sl-code  { padding: 0 0 12px 0; }
.sl-grid:not(.sl-card) .sl-inner { padding: 0; }

/* Docked beside the stage, an aside needs breathing room from the divider —
   but only on that inner edge. */
@container (min-width: 520px) {
    .sl-grid:not(.sl-card).sl-explorer .sl-inner { padding: 0 0 0 15px; }
}
@container (min-width: 560px) {
    .sl-grid:not(.sl-card).sl-player .sl-inner { padding: 0 0 0 15px; }
}
/* Stacked *under* the stage, an aside sits straight on the divider with no
   room at all — the first control's label touched the hairline. It needs the
   same breathing room the docked variant gets on its inner edge. */
@container (max-width: 519px) {
    .sl-grid:not(.sl-card).sl-explorer .sl-inner { padding: 11px 0 0 0; }
}
@container (max-width: 559px) {
    .sl-grid:not(.sl-card).sl-player .sl-inner { padding: 11px 0 0 0; }
}

/* A column of controls is right beside the stage, where the slot is narrow
   and tall. Stacked underneath it is the wrong shape: the slot is now wide
   and short, and a single column wastes the width while making the sketch
   scroll. Flow them into rows instead, each control taking a sane minimum
   before wrapping. */
@container (max-width: 519px) {
    .sl-explorer .sl-inner { flex-direction: row; flex-wrap: wrap; align-items: flex-end; gap: 10px 16px; }
    .sl-explorer .sl-inner > * { flex: 1 1 160px; min-width: 0; }
}
@container (max-width: 559px) {
    .sl-player .sl-inner { flex-direction: row; flex-wrap: wrap; align-items: flex-end; gap: 10px 16px; }
    .sl-player .sl-inner > * { flex: 1 1 160px; min-width: 0; }
}
/* A running commentary and a colour key are prose, not controls: sat in a
   column beside a select they read as a caption for it. They take the whole
   row so the controls pair up underneath. */
@container (max-width: 559px) {
    .sl-inner > .sl-status,
    .sl-inner > .sl-legend { flex: 1 1 100%; }
}

@container (min-width: 600px) {
    .sl-grid:not(.sl-card).sl-trace .sl-code { padding: 0 0 0 15px; }
}
@container (min-width: 900px) {
    .sl-grid:not(.sl-card).sl-trace .sl-code { padding: 0 15px 0 0; }
    .sl-grid:not(.sl-card).sl-trace .sl-out  { padding: 0 0 0 15px; }
}

/* ================================ THE SETS ================================ */

/* figure — one stage, at every width. */
.sl-figure { grid-template-areas: "stage"; }
.sl-figure .sl-stage { grid-area: stage; }

/* explorer — stage + aside. Aside docks right when there is room. */
.sl-explorer { grid-template-columns: 1fr; grid-template-areas: "stage" "aside"; }
.sl-explorer .sl-stage { grid-area: stage; }
.sl-explorer .sl-aside { grid-area: aside; }
@container (min-width: 520px) {
    .sl-explorer { grid-template-columns: 1fr var(--sl-aside-w, 208px); grid-template-areas: "stage aside"; }
    .sl-explorer .sl-aside { border-left: 1px solid var(--sl-rule); border-top: none; }
}
@container (max-width: 519px) {
    .sl-explorer .sl-legend { flex-direction: row; flex-wrap: wrap; gap: 5px 14px; }
}

/* player — stage + aside + a bar pinned across the bottom at every width. */
.sl-player { grid-template-columns: 1fr; grid-template-areas: "stage" "aside" "bar"; }
.sl-player .sl-stage { grid-area: stage; }
.sl-player .sl-aside { grid-area: aside; }
.sl-player .sl-bar   { grid-area: bar; }
@container (min-width: 560px) {
    .sl-player { grid-template-columns: 1fr var(--sl-aside-w, 196px); grid-template-areas: "stage aside" "bar bar"; }
    .sl-player .sl-aside { border-left: 1px solid var(--sl-rule); border-top: none; }
}
@container (max-width: 519px) {
    .sl-player .sl-legend { flex-direction: row; flex-wrap: wrap; gap: 5px 14px; }
}

/* A set whose slot was never filled collapses to what is actually there, at
   every width — no reserved column, no divider, no empty row. */
.sl-explorer.sl-no-aside,
.sl-player.sl-no-aside.sl-no-bar { grid-template-columns: 1fr; grid-template-areas: "stage"; }
.sl-player.sl-no-aside { grid-template-columns: 1fr; grid-template-areas: "stage" "bar"; }
.sl-player.sl-no-bar   { grid-template-areas: "stage" "aside"; }
@container (min-width: 520px) {
    .sl-explorer.sl-no-aside { grid-template-columns: 1fr; grid-template-areas: "stage"; }
}
@container (min-width: 560px) {
    .sl-player.sl-no-aside { grid-template-columns: 1fr; grid-template-areas: "stage" "bar"; }
    .sl-player.sl-no-aside.sl-no-bar { grid-template-columns: 1fr; grid-template-areas: "stage"; }
    .sl-player.sl-no-bar { grid-template-columns: 1fr var(--sl-aside-w, 196px); grid-template-areas: "stage aside"; }
}
.sl-prompt.sl-no-head { grid-template-areas: "stage" "bar"; }
.sl-prompt.sl-no-bar  { grid-template-areas: "head" "stage"; }

/* prompt — a status strip above the stage, plus a bar below it. */
.sl-prompt { grid-template-columns: 1fr; grid-template-areas: "head" "stage" "bar"; }
.sl-prompt .sl-head  { grid-area: head; }
.sl-prompt .sl-stage { grid-area: stage; }
.sl-prompt .sl-bar   { grid-area: bar; }

/* trace — the step-by-step execution layout: the program, the world it acts
   on, what it emitted, and the transport.
     narrow  : code / stage / out / bar   (reading order)
     medium  : stage | code, out and bar full-width beneath
     wide    : code | stage | out, bar as a full-width lip  */
.sl-trace { grid-template-columns: 1fr; grid-template-areas: "code" "stage" "out" "bar"; }
.sl-trace .sl-code  { grid-area: code; }
.sl-trace .sl-stage { grid-area: stage; }
.sl-trace .sl-out   { grid-area: out; }
.sl-trace .sl-bar   { grid-area: bar; }
@container (min-width: 600px) {
    .sl-trace {
        grid-template-columns: 1fr var(--sl-code-w, 260px);
        grid-template-areas: "stage code" "out out" "bar bar";
    }
    .sl-trace .sl-code { border-left: 1px solid var(--sl-rule); border-bottom: none; }
}
/* 900px was set by eye and was too high: the two side panes are fixed-width,
   so what actually decides whether three columns work is the remainder left
   for the stage. At 820 with a 260 + 220 pair the stage still gets ~340px,
   which is enough for the world it draws — so the three-pane reading (program,
   world, output) appears where there is genuinely room for it. */
@container (min-width: 820px) {
    .sl-trace {
        grid-template-columns: var(--sl-code-w, 260px) 1fr var(--sl-out-w, 220px);
        grid-template-areas: "code stage out" "bar bar bar";
    }
    .sl-trace .sl-code { border-left: none; border-right: 1px solid var(--sl-rule); }
    .sl-trace .sl-out  { border-top: none; border-left: 1px solid var(--sl-rule); }
}

/* the bar gets terser rather than disappearing */
@container (max-width: 430px) {
    .sl-bar .sl-btn-label { display: none; }
    .sl-bar .sl-btn { padding: 7px 9px; }
    .sl-bar .sl-read { display: none; }
}

/* ------------------------------------------------------- bare chrome ----- */
/* Marks laid straight on the sketch ground: no panel, no border, no blur.
   `.sl-opaque` gives it the ground colour so it occludes what passes behind;
   without it the drawing shows straight through. Overlays register with the
   layout as occupied rects, so ui.safeArea() inside the stage accounts for
   them and the sketch can move its content aside on a narrow canvas. */
.sl-overlay {
    position: absolute;
    z-index: 5;
    pointer-events: none;
    padding: 9px 11px;
    color: var(--sl-text);
}
.sl-overlay.sl-top-left     { top: 0; left: 0; }
.sl-overlay.sl-top-right    { top: 0; right: 0; }
.sl-overlay.sl-bottom-left  { bottom: 0; left: 0; }
.sl-overlay.sl-bottom-right { bottom: 0; right: 0; }
.sl-overlay.sl-opaque { background: var(--sl-overlay-bg); border-radius: 9px; }
.sl-overlay > * { pointer-events: auto; }

/* --------------------------------------------------------- chrome bits --- */
.sl-label {
    font-family: var(--ui-font, system-ui, sans-serif);
    font-size: 10px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--sl-text-faint);
    margin: 0 0 9px;
}
.sl-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: 12px;
    padding: 3px 0;
    font-size: 12.5px;
    font-family: var(--ui-font, system-ui, sans-serif);
}
.sl-row > span { color: var(--sl-text-soft); }
.sl-row > b { color: var(--sl-text); font-weight: 600; font-variant-numeric: tabular-nums; }
.sl-sep { height: 1px; background: var(--sl-rule); margin: 12px 0; }

.sl-legend { display: flex; flex-direction: column; gap: 5px; }
.sl-legend-item {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11.5px;
    color: var(--sl-text-soft);
    font-family: var(--ui-font, system-ui, sans-serif);
}
.sl-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }

.sl-status { font-size: 14px; color: var(--sl-text); margin: 0; text-wrap: pretty; }
.sl-status .sl-sub { display: block; color: var(--sl-text-soft); font-size: 12.5px; margin-top: 2px; }

.sl-spacer { flex: 1; min-width: 0; }
.sl-read {
    font-family: var(--ui-font, system-ui, sans-serif);
    font-size: 11px;
    color: var(--sl-text-faint);
    white-space: nowrap;
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    min-width: 0;
}

/* --------------------------------------------------------- code pane ----- */
/* Uses the document's own --code-* tokens so a program inside a sketch is
   coloured exactly like a code block in the surrounding chapter. */
.sl-code-list { list-style: none; margin: 0; padding: 0; }
.sl-code-line {
    display: flex;
    gap: 10px;
    align-items: baseline;
    font-family: var(--code-font, ui-monospace, monospace);
    font-size: 12.5px;
    line-height: 1.85;
    color: var(--code-default-color, var(--sl-text-soft));
    border-radius: 5px;
    padding: 0 6px;
    margin: 0 -6px;
    transition: background 0.18s ease, color 0.18s ease;
}
.sl-code-line .sl-ln {
    color: var(--sl-text-faint);
    font-size: 10.5px;
    min-width: 12px;
    text-align: right;
    flex: none;
    font-variant-numeric: tabular-nums;
}
.sl-code-line .tok-kw   { color: var(--code-operator-orange); font-weight: 600; }
.sl-code-line .tok-fn   { color: var(--code-function-color); }
.sl-code-line .tok-num  { color: var(--code-number-orange); }
.sl-code-line .tok-str  { color: var(--code-string-yellow); }
.sl-code-line .tok-com  { color: var(--code-comment-grey); font-style: italic; }
.sl-code-line.sl-active { background: var(--sl-accent-dim); color: var(--sl-text); }
.sl-code-line.sl-active .sl-ln { color: var(--sl-accent); }
.sl-indent-1 { padding-left: 24px; }
.sl-indent-2 { padding-left: 42px; }
.sl-indent-3 { padding-left: 60px; }

/* ------------------------------------------------------------ output ----- */
.sl-out-lines {
    font-family: var(--code-font, ui-monospace, monospace);
    font-size: 12.5px;
    line-height: 1.6;
    color: var(--sl-accent);
    white-space: pre;
    overflow-x: auto;
}
.sl-out-empty {
    font-family: var(--ui-font, system-ui, sans-serif);
    font-size: 11.5px;
    color: var(--sl-text-faint);
}

@media (prefers-reduced-motion: reduce) {
    .sl-code-line, .sl-aside::after { transition: none; }
}

/* ------------------------------------------------------- label pills ----- */
/* A value tucked against the shape it belongs to (a distance under a graph
   node, a reading beside a point) rather than floating off in space.
   Use via `new Label(txt, x, y, { className: 'sl-pill', anchor: 'top' })`. */
.sketch-label.sl-pill {
    background: var(--sl-overlay-bg);
    border: 1px solid var(--sl-rule);
    border-radius: 999px;
    padding: 0 7px;
    line-height: 1.5;
    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.35);
}

/* ============================== FULLSCREEN ============================== */
/* The whole arrangement goes to the overlay, not just the canvas. The shell
   fills the overlay's card and the stage row takes whatever height the other
   rows leave: the ratio and the floor stand aside, since here the height is
   given rather than derived. The stage observer sees the cell change and the
   canvas follows — nothing is measured in JS. */
.sl-shell.sl-fullscreen { width: 100%; height: 100%; }
.sl-shell.sl-fullscreen > .sl-grid { height: 100%; }
/* The ratio and the floor live on the drawing box now, so fullscreen has to
   stand them down there — on the stage alone they would leave the figure
   locked to its ratio on a screen that just offered it the whole height. */
/* Fullscreen hands the sketch a height, so the ratio, the floor AND the ceiling
   all stand aside — a capped drawing box would otherwise keep its cap on a
   screen that just offered it everything. */
.sl-shell.sl-fullscreen .sl-stage,
.sl-shell.sl-fullscreen .sl-field { aspect-ratio: auto; min-height: 0; max-height: none; }
.sl-shell.sl-fullscreen .sl-figure,
.sl-shell.sl-fullscreen .sl-explorer { grid-template-rows: minmax(0, 1fr); }
.sl-shell.sl-fullscreen .sl-player,
.sl-shell.sl-fullscreen .sl-trace    { grid-template-rows: minmax(0, 1fr) auto; }
.sl-shell.sl-fullscreen .sl-prompt   { grid-template-rows: auto minmax(0, 1fr) auto; }
/* a docked aside may be taller than a short window; it scrolls rather than
   pushing the stage row past the card */
/* overflow-y alone would force overflow-x to auto too, and a slider thumb at
   its maximum bleeds a few pixels past its track — a stray horizontal
   scrollbar. Clip that axis and give the thumb room instead. */
.sl-shell.sl-fullscreen .sl-aside { overflow-y: auto; overflow-x: hidden; }
.sl-shell.sl-fullscreen .sl-aside > .sl-inner { padding-right: 8px; }

/* ------------------------------------------------- fullscreen overlay ---
   These override the runner's baseline rules in document.css (this sheet
   loads after it). The app shell behind a document is black in both themes
   and a sketch clears to transparent, so the overlay is the shell and the
   container is the card the sketch sat on — same fill and inset shadows as
   .theme-dark .block / .theme-light .block — rather than a flat, darker
   ground the drawing was never designed against. */
.sketch-fullscreen-overlay {
    background: #000;
    animation: sketch-fs-in 200ms ease-out both;
}
.sketch-fullscreen-container {
    background: rgba(255, 255, 255, 0.05);
    border-radius: 15px;
    box-shadow: 0px 4px 0px rgba(0, 0, 0, 0.2), 0 0 32px inset rgba(255, 255, 255, 0.05), 0 0 3px inset rgba(255, 255, 255, 0.1);
    animation: sketch-fs-card-in 220ms cubic-bezier(0.2, 0.7, 0.2, 1) both;
}
.sketch-fullscreen-overlay.theme-light .sketch-fullscreen-container {
    background: #eeeeee;
    box-shadow: none;
}
/* a layout sketch brings its whole arrangement; the wrapper fills the card so
   the shell has something definite to size against */
.sketch-fullscreen-wrapper.sketch-fullscreen-layout { width: 100%; height: 100%; }

.sketch-fullscreen-overlay.closing { animation: sketch-fs-out 180ms ease-in both; pointer-events: none; }
.sketch-fullscreen-overlay.closing .sketch-fullscreen-container { animation: sketch-fs-card-out 180ms ease-in both; }

@keyframes sketch-fs-in       { from { opacity: 0; } to { opacity: 1; } }
@keyframes sketch-fs-out      { from { opacity: 1; } to { opacity: 0; } }
@keyframes sketch-fs-card-in  { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: none; } }
@keyframes sketch-fs-card-out { from { opacity: 1; transform: none; } to { opacity: 0; transform: scale(0.98); } }

@media (prefers-reduced-motion: reduce) {
    .sketch-fullscreen-overlay,
    .sketch-fullscreen-container,
    .sketch-fullscreen-overlay.closing,
    .sketch-fullscreen-overlay.closing .sketch-fullscreen-container { animation: none; }
}

/* ------------------------------------------------------------ HTML sketches -
   A sketch that defines mount(ctx) builds elements instead of painting. Its
   root lays over the canvas, which stays in place but hidden, so it takes
   exactly the size the runner and the layout gave the canvas. Being inside the
   canvas wrapper, it also sits beneath the fullscreen button and any bare
   overlays, which come later in the stage. Sketch styles build on these. */
.xd-root {
    position: absolute;
    inset: 0;
    container-type: size;
    color: var(--sl-text);
    font-family: var(--ui-font, 'IBM Plex Sans', system-ui, sans-serif);
    font-size: 14px;
    line-height: 1.4;
}
.xd-host > canvas { visibility: hidden !important; }
/* Document list styles (document.css gives `ol > li` a margin) must not reach
   inside a sketch: lists start bare. :where() keeps this at class weight, so
   a sketch's own rules still win. */
.xd-root :where(ol, ul) { margin: 0; padding: 0; list-style: none; }
.xd-root :where(li) { margin: 0; }
/* A figure is a drawing, not a text: nothing inside it is selectable. Without
   this, dragging a handle paints the tick labels blue, and a double-click on an
   axis name selects it. Chrome pinned around the figure (corner readings,
   buttons, dials) sits outside .xd-figure and stays selectable. */
.xd-figure { position: absolute; inset: 0; -webkit-user-select: none; user-select: none; }
.xd-svg    { position: absolute; inset: 0; overflow: visible; }
.xd-labels { position: absolute; inset: 0; pointer-events: none; }
.xd-label  { position: absolute; left: 0; top: 0; white-space: nowrap; will-change: transform; }

/* Figure primitives (ctx.figure: arrow, axes, handle). Colours come from the
   sketch tokens, so both themes work; a sketch restyles them under its own class. */
/* One weight per arrow: the head is drawn with the same stroke as the shaft,
   exactly as xArrow() does in p5 (line() and arc() share strokeWeight). A
   sketch sets --arrow-w once and the whole arrow follows, so a hairline axis
   cannot end in the same head as a 3px subject arrow. */
.xd-arrow { color: var(--sl-text); --arrow-w: 1.5; }
.xd-arrow-shaft { stroke: currentColor; stroke-width: var(--arrow-w); }
/* The house arrowhead is two quarter circles meeting at the tip: stroked and
   open, never filled. See arrowHead() in sketch-runner.js and xArrow() in
   xTools.js — filled triangles and straight-stroke chevrons are not variants. */
.xd-arrow-head { fill: none; stroke: currentColor; stroke-width: var(--arrow-w); stroke-linecap: round; }

.xd-axes .xd-axis-lines line,
.xd-axes .xd-axis-head { stroke: var(--sl-text-soft); stroke-width: 1; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.xd-axes .xd-tick { stroke: var(--sl-text-soft); stroke-width: 1; }
.xd-axes .xd-grid line { stroke: var(--sl-rule); stroke-width: 1; }
.xd-axes text { fill: var(--sl-text-soft); font-size: 11px; font-variant-numeric: tabular-nums; }
.xd-axis-name { color: var(--sl-text-soft); font-size: 13px; }

.xd-handle { cursor: grab; touch-action: none; outline: none; }
.xd-handle.is-dragging { cursor: grabbing; }
.xd-handle-hit { fill: transparent; stroke: none; }
.xd-handle-dot { fill: var(--xd-accent, var(--sl-accent)); stroke: none; transition: r 120ms ease; }
.xd-handle-ring { fill: none; stroke: var(--xd-accent, var(--sl-accent)); stroke-width: 2; opacity: 0; transition: opacity 120ms ease; }
/* A bare handle: all of the target, none of the dot. The thing it sits on is
   already lime, and two marks for one grab point is one too many. */
.xd-handle-bare .xd-handle-dot { display: none; }
.xd-handle-bare .xd-handle-ring { fill: none; opacity: 0; }
.xd-handle-bare:hover .xd-handle-ring,
.xd-handle-bare:focus-visible .xd-handle-ring { opacity: .5; }

.xd-handle:hover .xd-handle-ring,
.xd-handle.is-dragging .xd-handle-ring { opacity: 0.35; }
.xd-handle:focus-visible .xd-handle-ring { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .xd-handle-dot, .xd-handle-ring { transition: none; }
}

/* Vertices: one look for every graph and every tree (the graph and tree
   components). A tinted disc ringed in its role's colour, its name in the
   document's serif; the same role words as every component. */
.xd-vertex { outline: none; }
.xd-vertex circle { fill: color-mix(in srgb, var(--xd-neutral) 24%, transparent); stroke: color-mix(in srgb, var(--xd-neutral) 75%, transparent);
                    stroke-width: 1.5; transition: fill .3s, stroke .2s, stroke-width .2s; }
.xd-vertex text { fill: var(--sl-text); font-family: CMSerif, 'Times New Roman', serif; text-anchor: middle; dominant-baseline: central;
                  pointer-events: none; user-select: none; }
.xd-vertex.zone circle { fill: color-mix(in srgb, var(--xd-warning) 40%, transparent); stroke: var(--xd-warning); }
.xd-vertex.active circle { fill: color-mix(in srgb, var(--xd-accent) 58%, transparent); stroke: var(--xd-accent); stroke-width: 2.5; }
.xd-vertex.success circle { fill: color-mix(in srgb, var(--xd-success) 42%, transparent); stroke: var(--xd-success); }
.xd-vertex.danger circle { fill: color-mix(in srgb, var(--xd-danger) 40%, transparent); stroke: var(--xd-danger); }
.xd-vertex.info circle { fill: color-mix(in srgb, var(--xd-info) 40%, transparent); stroke: var(--xd-info); }
.xd-vertex.dim { opacity: .45; }
.xd-vertex.interactive { cursor: pointer; }
.xd-vertex.interactive:hover circle, .xd-vertex:focus-visible circle, .xd-vertex.highlight circle { stroke: var(--xd-accent); stroke-width: 3; }
@media (prefers-reduced-motion: reduce) {
    .xd-vertex circle { transition: none; }
}

/* ------------------------------------------------------ unpublished sketches -
   A sketch no human has reviewed is hidden from students; staff see it with
   this marker so they know it isn't published. */
/* Staff-only marker for a sketch nobody has reviewed yet: a dashed frame
   headed by its tag. The runner puts the frame in before measuring the
   sketch, so the sketch fits inside it — nothing covers its content, and a
   fold (overflow: hidden) has nothing sticking out to clip. */
.sketch-unpublished-frame {
    margin: 1em 0;
    padding: 6px 8px 8px;
    border: 1px dashed rgba(255, 179, 71, 0.55);
    border-radius: 12px;
}
.sketch-unpublished-frame > .sketch-placeholder { margin: 0; }
.theme-light .sketch-unpublished-frame { border-color: rgba(168, 98, 0, 0.45); }
.sketch-unpublished-tag {
    display: table;
    margin: 0 0 6px;
    padding: 1px 8px;
    border: 1px solid rgba(255, 179, 71, 0.45);
    border-radius: 999px;
    background: #2a2118;
    color: #ffb347;
    font: 600 11px/1.6 var(--ui-font, 'IBM Plex Sans', system-ui, sans-serif);
    cursor: pointer;
    -webkit-appearance: none;
    appearance: none;
    transition: border-color 0.15s, background-color 0.15s;
}
/* A button: it opens the review of this sketch (validate, or signal a problem). */
.sketch-unpublished-tag:hover { border-color: rgba(255, 179, 71, 0.85); background: #3a2d1d; }
.sketch-unpublished-tag:focus-visible { outline: 2px solid #ffb347; outline-offset: 2px; }
.theme-light .sketch-unpublished-tag { background: #fff4e5; color: #9a5800; }
.theme-light .sketch-unpublished-tag:hover { background: #ffe8c7; border-color: rgba(168, 98, 0, 0.7); }
/* Validated from the page: students see it now; the marker stays until the next load, quietly. */
.sketch-unpublished-frame.is-validated { border-color: rgba(88, 200, 140, 0.45); }
.sketch-unpublished-frame.is-validated .sketch-unpublished-tag { cursor: default; border-color: rgba(88, 200, 140, 0.5); background: #16261d; color: #6fdc9f; }
.theme-light .sketch-unpublished-frame.is-validated .sketch-unpublished-tag { background: #e7f7ee; color: #1d7a47; }
