/* ============================================================
   planner.css — AI Lesson Planner workspace + rendered-resource
   (.pr-*) styles. Extracted verbatim from index.html's single
   inline <style> block (behaviour-preserving extraction, no rule
   changes) — see PLANNER_NOTES.md. Loaded via a <link> in the
   exact same cascade position the rules previously occupied.
   ============================================================ */
/* ============================================================
   LILY LESSON PLANNER — full-screen workspace + rendered resources
   ============================================================ */

/* ---- rendered resource page: a printable, branded page. Uses
   --pr-primary/--pr-secondary/--pr-accent/--pr-font set inline per
   resource by renderResourceHTML() from the logged-in business's own
   branding, never a hardcoded brand. Resource types get a subtle
   .pr-flavour-* accent (a corner wash + header underline) rather than a
   fully different template, so a maths sheet and a reading passage look
   like the same family of document while still feeling distinct. */
.pr-page{
  width:100%; max-width:760px; margin:0 auto; color:#232a35;
  border-radius:14px; box-shadow:0 2px 0 rgba(0,0,0,.06), 0 12px 30px rgba(20,30,50,.10);
  padding:30px 36px 40px; font-family:var(--pr-font, sans-serif); box-sizing:border-box; position:relative; overflow:hidden;
  /* Long-word handling (Planner-B2): a single genuinely long word (a
     compound term, a run-together technical word) must wrap inside its
     own box rather than forcing .pr-page — and everything downstream of
     it (a PDF page, a pushed board tile) — wider than its container.
     Inherited, so every text descendant (title, headings, paragraphs,
     table cells, callouts...) gets this for free without repeating it
     per selector; confirmed by real narrow-viewport measurement this
     was previously missing and could genuinely overflow. */
  overflow-wrap:break-word; word-break:break-word;
  /* Resource-type "flavour" — a subtle, brand-driven accent wash reusing
     the SAME --pr-primary/--pr-secondary/--pr-accent variables every
     other element already reads from the logged-in business's own
     branding (never a hardcoded per-type colour, which would break
     white-label neutrality). Was documented in this file's own header
     comment since the original checkpoint but never actually
     implemented — every resource type rendered visually identical
     regardless of this class; this closes that gap. A background image
     on the page itself (not a positioned ::after box) on purpose: a
     positioned pseudo-element poking past the page's own edge still
     counts toward scrollWidth/scrollable-overflow even though
     overflow:hidden clips it visually — confirmed by real measurement —
     which would make every resource read as "overflowing" to anything
     checking scroll width (device-width testing, this checkpoint's own
     regression tests) for a purely decorative reason. A background
     image never creates scrollable overflow. */
  background:#fff radial-gradient(circle at top right, color-mix(in srgb, var(--pr-accent) 10%, transparent), transparent 70%) no-repeat top right/220px 220px;
}
.pr-page::before{content:''; position:absolute; inset:0 0 auto 0; height:6px; background:linear-gradient(90deg, var(--pr-primary), var(--pr-accent))}
/* Poster is the one type the product brief calls out for dedicated
   attention (it's visual-first by nature, meant to be glanced at rather
   than worked through line-by-line) — a bolder, centred header treatment
   rather than a worksheet-style top-left masthead. Still the same
   template, same tokens, same block vocabulary underneath. */
.pr-flavour-poster .pr-header{flex-direction:column; text-align:center; border-bottom:0; padding-bottom:4px}
.pr-flavour-poster .pr-mascot{margin:0}
.pr-flavour-poster .pr-title{text-align:center; font-size:32px}
.pr-flavour-poster .pr-instructions{margin-left:auto; margin-right:auto; text-align:center}
.pr-header{display:flex; align-items:center; gap:12px; margin:8px 0 16px; padding-bottom:12px; border-bottom:2px solid color-mix(in srgb, var(--pr-primary) 14%, white)}
.pr-logo{width:44px; height:44px; object-fit:contain; border-radius:8px}
.pr-mascot{width:56px; height:56px; object-fit:contain; margin-left:auto}
.pr-headertext{flex:1; min-width:0}
.pr-brandname{font-weight:800; font-size:14px; color:var(--pr-primary)}
.pr-type{font-size:11.5px; letter-spacing:.06em; text-transform:uppercase; font-weight:800; color:#8892a3; display:flex; align-items:center; gap:5px}
.pr-title{font-family:var(--pr-font, sans-serif); font-size:27px; margin:0 0 8px; color:#1c2433; line-height:1.15; font-weight:800}
/* Deterministic, length-based title sizing (plannerTitleSizeClass in
   planner.js) — a long AI-generated title reads better a little smaller
   than wrapping to 3-4 cramped lines at the full worksheet-title size.
   Modest reductions only; overflow-wrap above is still what guarantees
   no overflow, this is purely a readability/balance improvement. */
.pr-title.pr-title-long{font-size:22px}
.pr-title.pr-title-verylong{font-size:19px}
.pr-instructions{margin:0 0 18px; padding:11px 15px; background:color-mix(in srgb, var(--pr-primary) 8%, white); border-left:4px solid var(--pr-primary); border-radius:6px; font-weight:600; color:#3a4356}
/* ---- block spacing rhythm ----
   Was a single flat flex `gap:16px` between every block regardless of
   type — a heading sat exactly as far from the paragraph beneath it as
   from the unrelated block above it, which is the concrete mechanism
   behind the "AI document" feel this checkpoint's brief names
   specifically (everything reads as a stack of same-weight boxes, not a
   designed document with sections). Each block is now wrapped in
   `.pr-block.pr-block-{type}` (see plannerRenderBlock in planner.js) so
   margin — not a flat gap — can express "this heading belongs to what's
   under it" (tight) vs. "a new section is starting here" (roomier),
   which is what a human-laid-out worksheet actually does. */
.pr-body{display:flex; flex-direction:column}
.pr-block{margin-top:15px}
.pr-block:first-child{margin-top:0}
.pr-block.pr-block-heading{margin-top:28px}
.pr-block.pr-block-heading:first-child{margin-top:0}
/* a heading hugs the block directly under it */
.pr-block-heading + .pr-block{margin-top:9px}
.pr-heading{font-family:var(--pr-font, sans-serif); font-size:18.5px; margin:0; color:var(--pr-secondary); font-weight:800; display:flex; align-items:center; gap:8px}
.pr-heading::before{content:''; width:6px; height:6px; border-radius:50%; background:var(--pr-accent); flex:none}
.pr-para{margin:0; line-height:1.65; color:#2c3444}
.pr-wordlist{display:flex; flex-wrap:wrap; gap:8px; list-style:none; margin:0; padding:0}
.pr-wordlist li{background:color-mix(in srgb, var(--pr-primary) 10%, white); border:2px solid color-mix(in srgb, var(--pr-primary) 30%, white); border-radius:999px; padding:5px 14px; font-weight:700; font-size:14px}
/* ::marker styling (not a switch to the same circle-badge markup
   .pr-questions uses — that would mean a JS/HTML change for a purely
   cosmetic difference) gives a numbered list of steps/problems the same
   "this numbering is deliberate, not default-browser" weight as
   .pr-questions' own numbering, so the two read as siblings in the same
   design language instead of one looking custom-built and the other
   looking unstyled. */
.pr-list{margin:0; padding-left:24px; display:flex; flex-direction:column; gap:8px; line-height:1.5}
.pr-list li::marker{font-weight:800; color:var(--pr-primary)}
.pr-questions{margin:0; padding:0; list-style:none; display:flex; flex-direction:column; gap:12px; font-weight:600}
.pr-questions li{display:flex; align-items:flex-start; gap:10px; line-height:1.5}
.pr-qnum{flex:none; width:24px; height:24px; border-radius:50%; background:var(--pr-primary); color:#fff; font-size:12.5px; font-weight:800; display:flex; align-items:center; justify-content:center; margin-top:1px}
.pr-qtext{flex:1; padding-top:2px}
/* ---- Planner-B2b: per-question response space ----
   A "questions" item now carries its own write-on space directly beneath
   it, matched to what its answer actually needs, instead of one shared
   answer box at the bottom of the page (see plannerRenderQuestionItem in
   planner.js). Only a NEW, object-shaped item ({text, responseType}) gets
   any of this — a bare-string item (every resource saved before this
   checkpoint) keeps the exact original <li> markup above and is completely
   untouched by every rule below, which is what keeps old saved resources
   rendering pixel-identical with no migration. */
/* Higher specificity than .pr-questions li above on purpose — .pr-qitem
   must win that row-vs-column fight, not lose it to source order. */
.pr-questions li.pr-qitem{flex-direction:column; align-items:stretch; gap:8px}
.pr-qrow{display:flex; align-items:flex-start; gap:10px}
/* 24px badge + 10px gap = the same 34px .pr-qtext's own text starts at, so
   the response area lines up under the QUESTION, not the page margin —
   the "this is where you answer THIS one" relationship stays visually
   obvious even once several questions are stacked down the page. */
.pr-qresponse{margin-left:34px}
.pr-qresponse-short{display:inline-block; width:160px; max-width:60%; height:0; border-bottom:1.5px solid #c3c9d4}
/* Same bordered-box visual family as .pr-worked/.pr-answerspace right below
   — a maths working area reads as a deliberately designed part of the page,
   not a one-off style invented just for this. */
.pr-qresponse-working{min-height:56px; border:1.5px solid color-mix(in srgb, var(--pr-primary) 20%, white); border-radius:8px; background:color-mix(in srgb, var(--pr-primary) 3%, white)}
/* Planner-B5a-1 handwriting-spacing fix (PLANNER_MULTIMODAL_RESOURCE_ARCHITECTURE.md
   §14) — was gap:10px, confirmed too tight for real child handwriting at A4
   print scale (visually verified against a real .pr-sheet render — see
   PLANNER_NOTES.md's B5a-1 section for the fixture/screenshots). The
   architecture doc's own starting hypothesis was ~24px; 20px is what
   actually reads best in practice: enough for a comfortable pencil line at
   real print size without the 2-line "short_written" case starting to look
   sparse/gappy on its own (24px measured slightly too airy for exactly 2
   lines, the most common case, in the real side-by-side comparison).
   Deliberately NOT a blanket line-height increase — "short" (inline blank)
   and "working" (bordered box) are untouched, only genuinely ruled
   multi-line response types change. */
.pr-qresponse-lines{display:flex; flex-direction:column; gap:20px}
.pr-qresponse-lines i{display:block; height:0; border-bottom:1.5px solid #c3c9d4; font-style:normal}
/* A question and its own response space splitting across a page break reads
   as a mistake, so keep them together wherever the browser can honour it.
   This only ever applies to the NATIVE browser print path (right-click →
   Print, or a future print button) — the PDF export button's own path
   (plannerSliceResourceImageToA4Pages in planner.js) rasterises the whole
   sheet to a flat image first and slices it at a fixed pixel height with no
   block awareness at all, so it cannot honour this rule; a question/response
   pair that lands exactly on that pixel boundary in a genuinely multi-page
   PDF can still be visually split there — a real, disclosed limitation of
   the existing slicing architecture, not something this rule can reach. */
.pr-qitem{break-inside:avoid; page-break-inside:avoid}
/* Table containment (Planner-B2): confirmed by real measurement that an
   auto-layout table's own intrinsic min-width can exceed a narrow
   viewport's available width even when every individual cell's text
   wraps fine on its own — without this wrapper the table either silently
   clips against .pr-page's own overflow:hidden (losing a whole column
   with no visual hint anything is missing) or forces the entire page
   wider than its container. A dedicated horizontally-scrollable box
   contains that overflow to just the table, never the page — see the
   @media print override further down, which turns this back into plain
   overflow:visible since a printed/exported page has no scrollbar to
   scroll. */
.pr-table-wrap{overflow-x:auto; -webkit-overflow-scrolling:touch; border-radius:8px}
.pr-table{width:100%; border-collapse:collapse; font-size:14px; border-radius:8px}
.pr-table th, .pr-table td{border:1px solid #d8dde6; padding:9px 11px; text-align:left}
.pr-table th{background:color-mix(in srgb, var(--pr-primary) 14%, white); font-weight:800; color:#28313f}
.pr-passage{background:color-mix(in srgb, var(--pr-secondary) 5%, white); border:1px solid color-mix(in srgb, var(--pr-secondary) 16%, white); border-radius:12px; padding:16px 18px}
.pr-passagetitle{font-family:var(--pr-font, sans-serif); font-weight:800; font-size:16px; color:var(--pr-secondary); margin-bottom:8px}
.pr-passagetext p{margin:0 0 10px; line-height:1.8; font-size:15px; color:#28313f}
.pr-passagetext p:last-child{margin-bottom:0}
.pr-vocab{background:color-mix(in srgb, var(--pr-accent) 8%, white); border-radius:10px; padding:12px 16px}
.pr-vocabhead{font-weight:800; font-size:12px; text-transform:uppercase; letter-spacing:.05em; color:var(--pr-accent); margin-bottom:8px}
.pr-vocab dl{margin:0; display:flex; flex-direction:column; gap:6px}
.pr-vocabitem{display:flex; gap:8px; flex-wrap:wrap; line-height:1.4}
.pr-vocabitem dt{font-weight:800; color:#28313f; margin:0}
.pr-vocabitem dd{margin:0; color:#4a5568}
.pr-worked{border:2px solid color-mix(in srgb, var(--pr-primary) 22%, white); border-radius:10px; padding:14px 16px}
.pr-workedhead{font-weight:800; color:var(--pr-primary); margin-bottom:8px; font-size:15px}
.pr-worked ol{margin:0; padding-left:20px; display:flex; flex-direction:column; gap:6px; line-height:1.5}
.pr-success{background:color-mix(in srgb, var(--pr-primary) 6%, white); border-radius:10px; padding:12px 16px}
.pr-successhead{font-weight:800; font-size:12px; text-transform:uppercase; letter-spacing:.05em; color:var(--pr-primary); margin-bottom:8px}
.pr-success ul{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px}
.pr-success li{display:flex; align-items:center; gap:10px; font-weight:600}
.pr-box{width:16px; height:16px; border:2px solid var(--pr-primary); border-radius:4px; flex:none}
/* A writing/working area reads as a deliberately designed part of the
   page (something a student recognises as "write here") rather than a
   few dashes floating with no boundary — a light bordered box, same
   visual family (radius/border-weight) as .pr-worked/.pr-success right
   above it, so the page's own "boxed content area" language stays
   consistent rather than introducing a one-off treatment. */
/* Planner-B5a-1: audited alongside .pr-qresponse-lines above (the
   architecture doc flagged this exact value as not yet captured) — was
   gap:16px, already more generous than .pr-qresponse-lines' old 10px but
   still tightened up to the same 20px for one consistent ruled-line
   family across the whole page, rather than two different "how far apart
   are handwriting lines" answers depending on which block produced them. */
.pr-answerspace{display:flex; flex-direction:column; gap:20px; margin:0; padding:14px 16px; border:1.5px solid color-mix(in srgb, var(--pr-primary) 18%, white); border-radius:10px; background:color-mix(in srgb, var(--pr-primary) 3%, white)}
.pr-answerprompt{font-weight:700; color:#3a4356; margin-bottom:-4px}
.pr-answerspace i{display:block; height:0; border-bottom:1.5px solid #c3c9d4; font-style:normal}
.pr-callout{background:color-mix(in srgb, var(--pr-accent) 14%, white); border:2px dashed color-mix(in srgb, var(--pr-accent) 45%, white); border-radius:10px; padding:11px 15px; font-weight:600; color:#3a4356}

/* ============================================================
   Planner-B5a-1 — design-system layout/interaction blocks:
   panel, columns, choice_group, rating, badge. See
   PLANNER_MULTIMODAL_RESOURCE_ARCHITECTURE.md §5/§14. Deliberately
   restrained (per the checkpoint's own "avoid every block becoming a
   rounded card / dashboard-like UI" requirement) — reuses the SAME
   brand-token/radius/border-weight visual family every existing bordered
   block (.pr-worked/.pr-success/.pr-answerspace) already established,
   rather than inventing a new look.
   ============================================================ */
/* "panel" — three tones, all brand-token-driven (white-label safe, no
   hardcoded per-tenant colour): "accent" for something that should stand
   out a little, "quiet" for supporting/optional material, "default"
   otherwise. Child blocks render inside .pr-panelbody through the exact
   same .pr-block rhythm rules as the top-level page (nested, not a second
   layout system) — a slightly tighter base gap (see .pr-panelbody
   .pr-block below) since a panel is already a smaller, denser context
   than the full page. */
.pr-panel{border-radius:12px; padding:14px 16px; border:1.5px solid transparent}
.pr-panel-default{background:color-mix(in srgb, var(--pr-primary) 5%, white); border-color:color-mix(in srgb, var(--pr-primary) 16%, white)}
.pr-panel-accent{background:color-mix(in srgb, var(--pr-accent) 9%, white); border-color:color-mix(in srgb, var(--pr-accent) 30%, white)}
.pr-panel-quiet{background:color-mix(in srgb, #8892a3 6%, white); border:1.5px dashed color-mix(in srgb, #8892a3 30%, white)}
.pr-panelhead{display:flex; align-items:center; gap:7px; font-weight:800; font-size:14px; color:var(--pr-primary); margin-bottom:9px}
.pr-panel-accent .pr-panelhead{color:var(--pr-accent)}
.pr-panelicon{font-size:15px; line-height:1}
.pr-panelbody{display:flex; flex-direction:column}
.pr-panelbody .pr-block{margin-top:11px}
.pr-panelbody .pr-block:first-child{margin-top:0}
/* "columns" — Beta cap of exactly 2, side by side on screen/print; stacks
   to a single column on narrow viewports (a preview card, not the printed
   sheet — see the @media rule below) rather than shrinking to
   illegibility, matching how every other responsive element in this
   codebase already behaves per the B2 narrow-viewport fixes. */
.pr-columns{display:flex; gap:16px; align-items:flex-start}
.pr-columns-lead-left .pr-col:first-child{flex:1.4}
.pr-columns-lead-left .pr-col:last-child{flex:1}
.pr-columns-lead-right .pr-col:first-child{flex:1}
.pr-columns-lead-right .pr-col:last-child{flex:1.4}
.pr-columns-even .pr-col{flex:1}
.pr-col{min-width:0; display:flex; flex-direction:column}
.pr-col .pr-block{margin-top:11px}
.pr-col .pr-block:first-child{margin-top:0}
@media(max-width:520px){
  .pr-columns{flex-direction:column}
  .pr-columns .pr-col{flex:none}
}
/* "choice_group" — the renderer, not the model, decides the exact visual:
   a round mark for true/false's fixed two-option shape, a square tick-box
   otherwise (matching .pr-box's own existing success_criteria checkbox
   language, kept as a visually distinct shape so a choice control doesn't
   read as "another checklist"). */
.pr-choicegroup{display:flex; flex-direction:column; gap:9px}
.pr-choiceprompt{font-weight:700; color:#3a4356}
.pr-choiceopts{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:8px}
.pr-choiceopt{display:flex; align-items:center; gap:10px; font-weight:600}
.pr-choicemark{width:16px; height:16px; border:2px solid var(--pr-primary); flex:none}
.pr-choicemark.pr-choicemark-square{border-radius:4px}
.pr-choicemark.pr-choicemark-round{border-radius:50%}
/* "rating" — renderer-owned star glyphs (never model-supplied SVG/HTML);
   the explanation area reuses the SAME ruled-line family as a
   short_written question response, so it reads as the same "write here"
   element as everywhere else on the page. */
.pr-rating{display:flex; flex-direction:column; gap:8px}
.pr-ratingprompt{font-weight:700; color:#3a4356}
.pr-stars{display:flex; gap:6px; font-size:22px; line-height:1; color:var(--pr-accent)}
.pr-ratingexplain{display:flex; flex-direction:column; gap:20px; margin-top:2px}
.pr-ratingexplain i{display:block; height:0; border-bottom:1.5px solid #c3c9d4; font-style:normal}
/* "badge" — the smallest of the five, deliberately: aids hierarchy without
   dominating the page. Inline, not block-level, so it can sit naturally
   next to/above the content it labels rather than always occupying its
   own full-width row. */
.pr-badge{display:inline-flex; align-items:center; font-size:11px; font-weight:800; letter-spacing:.03em; text-transform:uppercase; padding:4px 10px; border-radius:999px}
.pr-badge-default{background:color-mix(in srgb, var(--pr-primary) 14%, white); color:var(--pr-primary)}
.pr-badge-accent{background:color-mix(in srgb, var(--pr-accent) 18%, white); color:color-mix(in srgb, var(--pr-accent) 70%, black)}

/* Planner-B5a-X — word_search / maze. Deterministic HTML/CSS grids (never
   an image, never SVG — plain box-model markup, the same rendering
   primitive as every other block, chosen specifically because this
   codebase already has confirmed real html2canvas-pro compatibility for
   bordered/tabular HTML and a documented past failure mode for
   foreignObject/SVG-based capture (see CLAUDE.md's "known fragile areas" —
   resourceHTMLToImage() was rewritten away from exactly that approach).
   Self-contained card (own border/background, same visual language as
   .pr-panel) rather than requiring a wrapping "panel" — see
   plannerRenderPuzzleHead's own comment in planner.js. */
.pr-puzzle{border-radius:12px; padding:16px 18px; border:1.5px solid color-mix(in srgb, var(--pr-primary) 20%, white); background:color-mix(in srgb, var(--pr-primary) 4%, white)}
.pr-puzzlehead{margin-bottom:12px}
.pr-puzzletitle{font-weight:800; font-size:15px; color:var(--pr-primary); margin-bottom:4px}
.pr-puzzleinstructions{font-size:12.5px; color:#4a5468; font-weight:600}

/* word_search grid — a real CSS grid of individually-escaped letter cells
   (never raster text), so it stays crisp at any print/export scale. Each
   cell is forced square via aspect-ratio, so the grid is always a true
   N×N square regardless of how many columns the deterministic generator
   chose. max-width keeps even the largest (16×16, "hard") grid safely
   inside the printable A4 content column with legible cell size (see
   supabase/functions/_shared/puzzleGenerators.ts's own WORD_SEARCH_GRID_BOUNDS). */
.pr-wordsearch-gridwrap{display:flex; justify-content:center; margin-bottom:14px}
.pr-wordsearch-grid{display:grid; gap:2px; width:100%; max-width:440px; background:color-mix(in srgb, var(--pr-primary) 14%, white); border:2px solid color-mix(in srgb, var(--pr-primary) 30%, white); border-radius:8px; padding:4px; box-sizing:border-box}
.pr-wscell{aspect-ratio:1; display:flex; align-items:center; justify-content:center; background:#fff; font-family:ui-monospace, "SF Mono", Consolas, monospace; font-weight:800; font-size:clamp(9px, 2.6vw, 17px); color:#28313f; border-radius:2px}
/* Answer-key-only highlight (see plannerRenderWordSearchBlock's own
   showAnswerKey handling in planner.js) — never present unless the tutor
   explicitly revealed it; the class itself only exists on a cell when that
   flag was true, so a plain student render never carries it at all. */
.pr-wscell-solution{background:color-mix(in srgb, var(--pr-accent) 35%, white); color:color-mix(in srgb, var(--pr-accent) 75%, black)}
.pr-wordsearch-words{border-top:1.5px dashed color-mix(in srgb, var(--pr-primary) 22%, white); padding-top:10px}
.pr-wordsearch-wordshead{font-weight:800; font-size:12px; text-transform:uppercase; letter-spacing:.05em; color:var(--pr-primary); margin-bottom:8px}
.pr-wordsearch-wordlist{list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:8px}
.pr-wordsearch-wordlist li{background:color-mix(in srgb, var(--pr-primary) 10%, white); border:2px solid color-mix(in srgb, var(--pr-primary) 30%, white); border-radius:999px; padding:4px 12px; font-weight:700; font-size:12.5px}

/* maze — a plain bordered-div grid (never SVG — see this block's own
   header comment). Each cell draws only its OWN top/left wall, plus its
   own right/bottom wall on the grid's outer edge, so every real wall
   segment is drawn exactly once (see plannerRenderMazeBlock's own comment
   in planner.js for why that avoids double-thickness borders). aspect-ratio
   keeps every cell square regardless of the maze's width/height, so the
   whole grid always renders as a true rectangle at any print scale. */
.pr-maze-gridwrap{display:flex; justify-content:center; margin-bottom:12px}
.pr-maze-grid{display:grid; width:100%; max-width:440px}
.pr-mazecell{aspect-ratio:1; box-sizing:border-box; position:relative}
.pr-mzw-t{border-top:2.5px solid #28313f}
.pr-mzw-l{border-left:2.5px solid #28313f}
.pr-mzw-r{border-right:2.5px solid #28313f}
.pr-mzw-b{border-bottom:2.5px solid #28313f}
.pr-mazecell-start::after, .pr-mazecell-end::after{content:''; position:absolute; inset:18%; border-radius:50%}
.pr-mazecell-start::after{background:var(--pr-primary)}
.pr-mazecell-end::after{background:var(--pr-accent); border-radius:0; clip-path:polygon(50% 0%, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)}
/* Answer-key-only solution-path highlight — same "class only exists when
   the tutor explicitly revealed it" posture as .pr-wscell-solution above. */
.pr-mazecell-solution::before{content:''; position:absolute; inset:28%; border-radius:50%; background:color-mix(in srgb, var(--pr-accent) 55%, white)}
.pr-maze-legend{display:flex; gap:16px; flex-wrap:wrap; font-size:12.5px; font-weight:700; color:#3a4356}
.pr-maze-legenditem{display:flex; align-items:center; gap:6px}
.pr-maze-legenddot{font-size:15px; line-height:1}
.pr-maze-legenditem-start .pr-maze-legenddot{color:var(--pr-primary)}
.pr-maze-legenditem-end .pr-maze-legenddot{color:var(--pr-accent)}

/* Resource image block — foundation checkpoint, no generation yet (see
   IMAGE_GENERATION_NOTES.md). pr-image-unavailable covers every non-ready
   state (pending/generating/failed/blocked/resolution-failed) AND a live
   <img> that fails to load after being shown (see its inline onerror,
   which just adds this same class) — one fallback treatment either way. */
.pr-image-wrap{position:relative; border-radius:12px; overflow:hidden; margin:0 auto; background:color-mix(in srgb, var(--pr-primary) 6%, white); border:1px solid color-mix(in srgb, var(--pr-primary) 16%, white)}
.pr-image-wrap.pr-image-size-small{max-width:220px}
.pr-image-wrap.pr-image-size-medium{max-width:380px}
.pr-image-wrap.pr-image-size-large{max-width:100%}
.pr-image{display:block; width:100%; height:auto}
.pr-image-wrap.pr-image-unavailable{display:flex; align-items:center; justify-content:center; min-height:110px}
.pr-image-wrap.pr-image-unavailable .pr-image{display:none}
.pr-image-fallback{display:none; padding:16px; text-align:center; font-size:13px; font-weight:700; color:#6b7280}
.pr-image-wrap.pr-image-unavailable .pr-image-fallback{display:block}
.pr-image-caption{margin-top:6px; font-size:12.5px; color:#5a6478; text-align:center; font-style:italic}
@media print{
  .pr-page{box-shadow:none; border-radius:0; max-width:100%; page-break-after:always}
  /* A scroll container has no scrollbar on a printed/exported page — left
     as overflow-x:auto, an over-wide table would just silently clip at
     the container's edge with no way for the reader to ever see the rest
     of it. Letting it flow at full width instead is the honest behaviour:
     a table that's wider than intended being visibly wide beats one that
     quietly loses a column. */
  .pr-table-wrap{overflow-x:visible}
}

/* ---- A4 printable sheet ----
   Wraps a rendered .pr-page (unchanged, same renderResourceHTML() output
   used everywhere else — a lesson section's own inline resource card
   never gets this wrapper, so nothing here touches that context) in a
   real, physical-mm A4 container for the standalone-resource preview AND
   for PDF rasterisation (see plannerBuildResourcePDF in planner.js) — the
   same DOM node both paths read means the visible preview and the
   exported PDF can never quietly drift apart into two different layouts.
   Every rule below is scoped under .pr-sheet specifically so it can never
   apply to a bare, unwrapped .pr-page. */
.pr-sheetscale{ display:flex; justify-content:center; padding:6px 0 18px }
.pr-sheet{
  width:210mm; min-height:297mm; background:#fff; color:#232a35;
  box-shadow:0 2px 0 rgba(0,0,0,.06), 0 12px 30px rgba(20,30,50,.10);
  box-sizing:border-box; flex:none; position:relative;
  /* JS (plannerFitSheetScale) sets --pr-scale on narrow viewports so the
     whole sheet shrinks as one rigid unit — the A4 proportions/mm
     dimensions above are never altered, only how large it's drawn on
     screen. transform-origin stays the top so the wrapper's own
     JS-computed height (scale × 297mm) lines up with what's actually
     drawn, instead of leaving a blank gap or clipping the bottom. */
  transform:scale(var(--pr-scale, 1)); transform-origin:top center;
}
.pr-sheet .pr-page{
  width:100%; max-width:none; min-height:297mm; height:auto; margin:0; box-sizing:border-box;
  border-radius:0; box-shadow:none; padding:16mm 16mm 18mm;
  font-size:12.5px; overflow:visible; /* real overflow must stay visible here, never clipped — see plannerResourceOverflows for the honest "won't fit one page" signal instead */
}
.pr-sheet .pr-title{font-size:21px}
.pr-sheet .pr-title.pr-title-long{font-size:18px}
.pr-sheet .pr-title.pr-title-verylong{font-size:16px}
.pr-sheet .pr-instructions{font-size:12px}
/* Same tight-heading/roomier-section rhythm as the card view (see the
   base .pr-block rules above), just scaled down to match this context's
   smaller base font size — a real A4 page has room for real paragraph
   spacing, but at 12.5px body text the card view's 15/28/9px margins
   read as slightly too loose relative to the text itself. */
.pr-sheet .pr-block{margin-top:11px}
.pr-sheet .pr-block.pr-block-heading{margin-top:20px}
.pr-sheet .pr-block-heading + .pr-block{margin-top:7px}
.pr-sheet .pr-para, .pr-sheet .pr-passagetext p{font-size:12.5px; line-height:1.55}
.pr-sheet .pr-questions{gap:9px; font-size:12.5px}
.pr-sheet .pr-table{font-size:11.5px}
/* Planner-B5a-1: .pr-sheet is a FIXED-width 210mm physical page that only
   ever shrinks visually via JS-set --pr-scale (a transform, which doesn't
   change the underlying layout width a media query measures) — the real
   viewport can still be narrower than 520px (a phone previewing the A4
   sheet) even though the page itself has plenty of real width for two
   columns side by side. Without this override, the plain narrow-viewport
   .pr-columns{flex-direction:column} rule below would make the on-screen
   preview stack while the exported PDF (rasterised from this exact same
   markup at its real, unscaled width) still renders side by side — exactly
   the "preview and PDF quietly drift apart" failure this file's own header
   comment says .pr-sheet exists to prevent. Higher specificity (two
   classes) than the plain .pr-columns media-query rule, so it always wins
   here regardless of source order. */
.pr-sheet .pr-columns{flex-direction:row}
.pr-sheet .pr-columns .pr-col{flex:1}
.pr-sheet .pr-columns-lead-left .pr-col:first-child{flex:1.4}
.pr-sheet .pr-columns-lead-left .pr-col:last-child{flex:1}
.pr-sheet .pr-columns-lead-right .pr-col:first-child{flex:1}
.pr-sheet .pr-columns-lead-right .pr-col:last-child{flex:1.4}
@media print{
  .pr-sheetscale{ padding:0 }
  .pr-sheet{ box-shadow:none; transform:none; width:auto; min-height:0 }
  @page{ size:A4 portrait; margin:0 }
}

/* ---- planner workspace ---- */
.plannerwrap{flex:1; min-height:0; display:flex; flex-direction:column; overflow:hidden}
.plannertop{display:flex; align-items:center; gap:10px; padding:10px 16px; border-bottom:3px solid var(--mint-deep); flex-wrap:wrap}
/* Persistent bottom assistant bar — last child of .plannerwrap (a flex
   column), so it always sits below the scrollable body without covering
   it or needing position:fixed (which fights iPad's on-screen keyboard
   less predictably than normal flow). */
.plannerlily{flex:none; border-top:3px solid var(--mint-deep); background:linear-gradient(135deg,var(--mint) 0%,#fff 100%);
  /* Sits in normal flow at the bottom of the flex column (never
     position:fixed), so it naturally moves above an iPad/iPhone on-screen
     keyboard instead of being covered by or covering it, and padding-bottom
     respects the home-indicator safe area on notched devices. */
  padding-bottom:env(safe-area-inset-bottom, 0px)}
.plannerlilybar{display:flex; align-items:center; gap:12px; padding:10px 18px; flex-wrap:wrap}
.plannerlilyavatar{width:40px; height:40px; object-fit:contain; flex:none; border-radius:50%; background:#fff; box-shadow:0 2px 0 var(--mint-deep); cursor:pointer}
.plannerlilyavatar.busy{animation:plannerlilybob 1.1s ease-in-out infinite}
@keyframes plannerlilybob{0%,100%{transform:translateY(0)} 50%{transform:translateY(-4px)}}
@media (prefers-reduced-motion: reduce){ .plannerlilyavatar.busy{animation:none} }
.plannerlilysay{flex:1; min-width:180px; display:flex; flex-direction:column; gap:1px; cursor:pointer}
.plannerlilysay b{font-family:var(--display); font-size:11px; color:var(--sprout-dark)}
.plannerlilymsg{font-size:13.5px; font-weight:700; color:var(--ink); display:flex; align-items:center; gap:8px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.plannerlilymsg .spin{width:12px; height:12px; border-radius:50%; border:2.5px solid var(--mint-deep); border-top-color:var(--sprout); animation:plannerspin .7s linear infinite; flex:none}
/* flex-basis:280px (not a bare flex:2) so the input+Send group claims a
   sensible line of its own once it can't sit beside the avatar/message
   without squeezing — this is what keeps Send from ever being crushed to
   near-zero width instead of just wrapping cleanly. */
.plannerlilyform{display:flex; gap:8px; flex:1 1 280px; min-width:220px; flex-wrap:wrap}
.plannerlilyform input{flex:2 1 160px; min-width:0; font:inherit; padding:8px 14px; border-radius:999px; border:2px solid var(--mint-deep)}
.plannerlilyform button{border-radius:999px; padding:8px 16px; flex:none; white-space:nowrap}
/* ---- creation-mode dropdown: custom accessible listbox ----
   Replaces a native <select> entirely (see PLANNER_NOTES.md — real user
   feedback on the deployed preview was specifically about the EXPANDED
   popup still being the plain OS/browser menu; styling only the closed
   control, as the previous checkpoint did, wasn't enough, since no CSS
   can theme a native select's open popup in any browser). The trigger
   keeps the same compact, chunky-pill closed-state language as before;
   .plannercreationlistbox is the new part — a real, owned, themeable
   panel using the same shadow/border language as .toast and .pagecol
   (var(--shadow)) rather than inventing a new visual style. */
.plannercreationfield{position:relative; flex:1 1 150px; min-width:118px; max-width:210px}
.plannercreationtrigger{
  width:100%; display:flex; align-items:center; justify-content:space-between; gap:6px;
  font-family:var(--display); font-size:12.5px; font-weight:700;
  padding:8px 12px; border-radius:999px; border:2px solid var(--mint-deep);
  background:#fff; color:var(--ink); cursor:pointer; box-shadow:0 3px 0 var(--mint-deep); transition:transform .1s, filter .15s;
}
.plannercreationtriggerlabel{overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.plannercreationchevron{
  flex:none; width:11px; height:8px; background:no-repeat center/11px 8px
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%2316294C' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
}
.plannercreationtrigger:hover{filter:brightness(1.04)}
.plannercreationtrigger:active{transform:translateY(2px); box-shadow:0 1px 0 var(--mint-deep)}
.plannercreationtrigger:focus{border-color:var(--sprout); outline:none}
.plannercreationtrigger:focus-visible{outline:3px solid color-mix(in srgb, var(--brand-primary, var(--sprout)) 45%, white); outline-offset:2px}
.plannercreationtrigger[disabled]{opacity:.6; cursor:default}
@media (pointer:coarse){ .plannercreationtrigger{ padding-top:11px; padding-bottom:11px; } }
/* The panel itself — positioned by plannerWireCreationDropdown() (adds
   .opensup + inline right when there isn't room to open down/right,
   which in practice is almost always true here since the trigger sits
   in the bottom bar). list-style/margin reset since this is a real <ul>,
   not a scattering of divs. */
.plannercreationlistbox{
  position:absolute; z-index:40; top:calc(100% + 6px); left:0; min-width:220px; max-width:min(300px, 90vw);
  margin:0; padding:6px; list-style:none; max-height:260px; overflow-y:auto;
  background:#fff; border:2px solid var(--mint-deep); border-radius:var(--r-md); box-shadow:var(--shadow);
}
.plannercreationlistbox.opensup{top:auto; bottom:calc(100% + 6px)}
.plannercreationlistbox[hidden]{display:none}
.plannercreationoption{
  font-family:var(--display); font-size:13.5px; font-weight:700; color:var(--ink);
  padding:11px 14px; border-radius:10px; cursor:pointer; min-height:22px;
  display:flex; align-items:center; gap:8px;
}
.plannercreationoption::before{content:''; width:14px; height:14px; flex:none; border-radius:50%}
.plannercreationoption[aria-selected="true"]{background:color-mix(in srgb, var(--sprout) 12%, white); color:var(--sprout-dark)}
.plannercreationoption[aria-selected="true"]::before{background:var(--sprout) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 10'%3E%3Cpath d='M1 5l3.2 3.2L11 1' fill='none' stroke='white' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center/9px 8px}
.plannercreationoption.active, .plannercreationoption:hover{background:var(--mint)}
.plannercreationoption[aria-selected="true"].active, .plannercreationoption[aria-selected="true"]:hover{background:color-mix(in srgb, var(--sprout) 20%, white)}
.plannerlilyexpandbtn{border:0; background:transparent; font-size:15px; cursor:pointer; color:var(--ink-soft); flex:none; padding:6px}
.plannerlilyhistory{max-height:150px; overflow-y:auto; padding:0 18px 10px; display:flex; flex-direction:column; gap:6px; border-top:1px solid var(--mint-deep)}
.plannerlilyhistrow{font-size:12.5px; font-weight:600; color:var(--ink-soft); display:flex; gap:6px}
.plannerlilyhistrow.fromtutor{color:var(--ink)}
.plannerlilyhistrow b{flex:none; color:var(--sprout-dark)}
/* Tutor-facing only — never rendered into a saved resource or pushed
   worksheet (renderResourceHTML()/resourcePlainText() never read this
   string; it lives purely in the planner chrome). Deliberately quiet:
   small, muted, no icon/colour that reads as an error or urgent alert. */
.planneraiwarning{font-size:10.5px; font-weight:600; color:var(--ink-soft); opacity:.75; padding:2px 18px 8px; text-align:center}
/* Tablet/narrow: the say-line and the input+Send line each take a full row
   of their own (never sharing a row with the avatar), so nothing — not
   even the global floating Lily bubble, now hidden on this screen — has a
   seam to overlap Send at. */
@media(max-width:760px){ .plannerlilybar{flex-wrap:wrap} .plannerlilysay{order:1; flex-basis:100%} .plannerlilyform{order:2; flex-basis:100%} .plannerlilyexpandbtn{order:0} .plannercreationfield{flex-basis:100%; max-width:none} }
.plannertabs{display:flex; gap:6px; margin-left:6px}
.plannertab{border:0; background:var(--mint); color:var(--ink-soft); font-weight:800; font-size:13px; padding:8px 16px; border-radius:999px; cursor:pointer}
.plannertab.on{background:var(--sprout); color:#fff}
.plannersave{margin-left:auto; font-size:12px; color:var(--ink-soft); font-weight:700; display:flex; align-items:center; gap:6px}
.plannersave .dot{width:8px; height:8px; border-radius:50%; background:#C7CEDA}
.plannersave.saving .dot{background:var(--sun)}
.plannersave.saved .dot{background:var(--sprout)}
.plannersave.failed{color:var(--berry)}
.plannersave.failed .dot{background:var(--berry)}
.plannerbody{flex:1; min-height:0; overflow:hidden; display:flex}
.plannerbody.libraryview{overflow:auto; display:block; padding:18px}
.plannercol{overflow-y:auto; padding:16px}
.plannercol.left{width:260px; flex:none; border-right:3px solid var(--mint-deep); background:var(--mint)}
.plannercol.centre{flex:1; min-width:0; background:#F5F8F6}
.plannercol.right{width:250px; flex:none; border-left:3px solid var(--mint-deep)}
/* ---- previous lesson plans (left) ---- */
.plannernewlesson{width:100%; margin-bottom:12px}
.plannerhistorylist{display:flex; flex-direction:column; gap:8px}
.plannerhistcard{display:flex; flex-direction:column; gap:4px; text-align:left; width:100%; background:#fff; border:2px solid var(--mint-deep); border-radius:12px; padding:10px 12px; cursor:pointer; font:inherit}
.plannerhistcard.current{border-color:var(--brand-primary, var(--sprout)); background:color-mix(in srgb, var(--brand-primary, var(--sprout)) 8%, white)}
.plannerhistcard:focus-visible{outline:3px solid color-mix(in srgb, var(--brand-primary, var(--sprout)) 45%, white); outline-offset:2px}
.phtitle{font-family:var(--display); font-weight:800; font-size:14px; color:var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.phmeta{display:flex; gap:6px; flex-wrap:wrap; font-size:11.5px; font-weight:700; color:var(--ink-soft)}
.phfoot{display:flex; align-items:center; justify-content:space-between; margin-top:2px}
.phstatus{font-size:10px; font-weight:800; text-transform:uppercase; letter-spacing:.04em; padding:2px 7px; border-radius:999px; background:var(--mint-deep); color:var(--ink-soft)}
.phstatus-ready{background:#D6ECDD; color:var(--sprout-dark)}
.phstatus-assigned, .phstatus-completed{background:color-mix(in srgb, var(--sky) 20%, white); color:#1c5f8a}
/* Background generation job badges — see PLANNER_NOTES.md job-ownership
   section. .phstatus-ready is reused as-is above for a completed lesson;
   these two cover the two states that don't already exist. */
.phstatus-generating{background:color-mix(in srgb, var(--sky) 22%, white); color:#1c5f8a; display:inline-flex; align-items:center; gap:5px}
.phstatus-generating .spin{width:9px; height:9px; border-radius:50%; border:2px solid color-mix(in srgb, #1c5f8a 30%, white); border-top-color:#1c5f8a; animation:plannerspin .7s linear infinite; flex:none}
.phstatus-failed{background:color-mix(in srgb, var(--berry) 16%, white); color:var(--berry)}
.phwhen{font-size:10.5px; color:var(--ink-soft); opacity:.8}
/* Always-visible job notice strip, rendered inside #plannerLily (a
   page-level sibling of both Plan/Library tab bodies, so this survives
   switching tabs) — deliberately NOT the toast: a toast disappears on its
   own timer, this stays until the tutor views or retries the job. */
.plannerjobnotices{display:flex; flex-direction:column; gap:6px; padding:10px 18px 0}
.plannerjobnotice{display:flex; align-items:center; gap:10px; font-size:12.5px; font-weight:700; border-radius:10px; padding:8px 12px}
.plannerjobnotice .pjntext{flex:1 1 auto; min-width:0}
.plannerjobnotice .spin{width:12px; height:12px; border-radius:50%; border:2.5px solid rgba(255,255,255,.5); border-top-color:#fff; animation:plannerspin .7s linear infinite; flex:none}
.plannerjobnotice.generating{background:#1c5f8a; color:#fff}
.plannerjobnotice.ready{background:var(--sprout-dark); color:#fff}
.plannerjobnotice.ready .btn, .plannerjobnotice.failed .btn{background:#fff; color:var(--ink); box-shadow:none; padding:5px 12px; font-size:12px}
.plannerjobnotice.failed{background:var(--berry); color:#fff}
/* Per-notice dismiss control — deliberately small/quiet (this isn't the
   primary action, View/Retry are) but still a real, focusable, labelled
   button, never a bare div. Sits flush against the notice's own edge so it
   never crowds the View/Retry button beside it or pushes into Lily's bar
   below (.plannerjobnotices already sits entirely above .plannerlilybar in
   normal flow, so this can only ever add height there, never overlap it). */
.plannerjobnotice .pjndismiss{flex:none; background:transparent; border:0; color:inherit; opacity:.7; font-size:13px; line-height:1; width:22px; height:22px; border-radius:7px; cursor:pointer; padding:0}
.plannerjobnotice .pjndismiss:hover{opacity:1; background:rgba(255,255,255,.2)}
.plannerjobnotice .pjndismiss:focus-visible{opacity:1; outline:2px solid #fff; outline-offset:1px}
/* Compact bulk "Clear completed notices" — a plain text-style link-button
   right-aligned under the notice stack, quiet enough not to compete with
   the notices themselves or the Lily bar just below it. Only rendered at
   all when there are 2+ completed notices (see plannerRenderJobNoticesHTML). */
.plannerjobnoticesclear{display:flex; justify-content:flex-end}
.plannerjobnoticesclear button{background:none; border:0; padding:2px 4px; color:var(--ink-soft); font-size:11.5px; font-weight:700; text-decoration:underline; text-underline-offset:2px; cursor:pointer}
.plannerjobnoticesclear button:hover{color:var(--ink)}
.plannerjobnoticesclear button:focus-visible{outline:2px solid var(--ink-soft); outline-offset:1px; border-radius:4px}
@media (prefers-reduced-motion: reduce){ .phstatus-generating .spin, .plannerjobnotice .spin{animation:none} }
@media(max-width:900px){
  .plannercol.left{position:fixed; inset:0 20% 0 0; z-index:20; width:auto; transform:translateX(-100%); transition:transform .18s ease; box-shadow:4px 0 20px rgba(0,0,0,.15)}
  .plannercol.left.open{transform:translateX(0)}
  .plannerhistorytoggle{display:inline-flex !important}
}
/* .plannerstudentchip is still used by the Push-to-boards modal's student
   list (plannerOpenPushModal) — the centre-column settings strip that
   used to be its other caller was removed as a deliberate UI
   simplification (natural-language student naming + deterministic
   roster resolution replaces it; see plannerResolveStudentNames). */
.plannerstudentchip{display:flex; align-items:center; gap:8px; padding:7px 10px; border-radius:10px; background:#fff; border:2px solid var(--mint-deep); cursor:pointer; font-weight:700; font-size:13px}
.plannerstudentchip input{margin:0}
.plannerstudentchip.on{border-color:var(--sprout); background:color-mix(in srgb, var(--sprout) 10%, white)}
.plannerchatbox{display:flex; flex-direction:column; gap:8px; margin-bottom:14px}
.plannerchatbox textarea{min-height:70px}
.plannergenbtn{width:100%}
.plannersections{display:flex; flex-direction:column; gap:14px; max-width:800px; margin:0 auto}
.plannersection{background:#fff; border:2px solid var(--mint-deep); border-radius:14px; padding:14px 16px 14px 20px; position:relative; cursor:pointer; transition:border-color .12s, background .12s}
.plannersection.dragging{opacity:.5}
.plannersection::before{content:''; position:absolute; left:0; top:10px; bottom:10px; width:5px; border-radius:4px; background:transparent; transition:background .12s}
.plannersection.selected{border-color:var(--brand-primary, var(--sprout)); background:color-mix(in srgb, var(--brand-primary, var(--sprout)) 6%, white)}
.plannersection.selected::before{background:var(--brand-primary, var(--sprout))}
.plannersection:focus-within{outline:3px solid color-mix(in srgb, var(--brand-primary, var(--sprout)) 45%, white); outline-offset:2px}
.plannersectionselectedtag{display:none; align-items:center; gap:4px; font-size:11px; font-weight:800; color:var(--brand-primary, var(--sprout-dark)); text-transform:uppercase; letter-spacing:.04em}
.plannersection.selected .plannersectionselectedtag{display:inline-flex}
.plannerjobstatus{display:flex; align-items:center; gap:8px; font-size:12.5px; font-weight:700; color:var(--brand-primary, var(--sprout-dark)); background:color-mix(in srgb, var(--brand-primary, var(--sprout)) 10%, white); border-radius:8px; padding:7px 10px; margin-top:8px}
.plannerjobstatus .spin{width:13px; height:13px; border-radius:50%; border:2.5px solid color-mix(in srgb, var(--brand-primary, var(--sprout)) 30%, white); border-top-color:var(--brand-primary, var(--sprout)); animation:plannerspin .7s linear infinite; flex:none}
@keyframes plannerspin{to{transform:rotate(360deg)}}
@media (prefers-reduced-motion: reduce){ .plannerjobstatus .spin{animation:none} }
.plannersectionhead{display:flex; align-items:center; gap:8px; margin-bottom:8px}
.plannersectiontype{font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:var(--sprout-dark); background:var(--mint); padding:3px 9px; border-radius:999px}
.plannersectionmins{font-size:12px; font-weight:700; color:var(--ink-soft)}
.plannersectiontitle{font-family:var(--display); font-size:17px; border:0; background:transparent; flex:1; min-width:0; font-weight:800}
.plannerdrag{cursor:grab; color:#B7C0CE; font-size:16px}
/* Was min-height:70px — around 3-4 lines regardless of how much a section
   actually contained, forcing a scroll/resize to read a typical multi-line
   plan. 150px shows noticeably more before any scrolling is needed, and
   plannerAutoGrowTextarea() (see plannerWireSections()) grows it further to
   fit real content up to max-height, so most sections never need to
   scroll internally at all — only a genuine outlier this long still
   scrolls, rather than every card. */
.plannersectionbody{width:100%; border:0; background:transparent; font:inherit; resize:vertical; min-height:150px; max-height:480px; line-height:1.5}
.plannersectionctl{display:flex; gap:6px; flex-wrap:wrap; margin-top:8px}
.plannerresourcecard{margin-top:10px; border-top:2px dashed var(--mint-deep); padding-top:10px}
.plannerresourcecard .pr-page{box-shadow:none; border:1px solid #e4e8ee; padding:18px 20px; max-width:100%}
.plannerresourcestatus{display:flex; align-items:center; gap:6px; font-size:11.5px; font-weight:700; color:var(--ink-soft); margin-bottom:8px; text-transform:uppercase; letter-spacing:.03em}
.plannersavedbadge{display:inline-flex; align-items:center; gap:6px; text-transform:none; letter-spacing:0}
.plannersavedbadge.saved{color:var(--sprout-dark)}
.plannersavedbadge.unsaved{color:var(--berry)}
.plannersavedbadge .btn{padding:3px 9px; font-size:11px}
.plannerassignstatus{font-size:12px; font-weight:700; color:var(--ink-soft); margin-top:6px}
.plannerassignstatus.assigned{color:var(--sprout-dark)}
.plannerassignstatus.failed{color:var(--berry)}
.plannertoolbtn{width:100%; text-align:left; padding:9px 12px; font-size:13px}
/* Was just five buttons + a couple of status lines separated by nothing
   but incidental inline-block whitespace (a couple of px, purely an
   accident of how the template happened to be formatted) — every element
   read as equally weighted and equally close together, which is what
   "crowded" meant in practice. A deliberate gap between the group's own
   direct children gives real breathing room, and each status line (the
   Generate All retry note, the assign status) is wrapped with the one
   button it actually belongs to in its own tighter sub-group — see
   plannerRenderRight() — so it visually reads as "this note is about that
   button" instead of one more same-weight item in the stack. */
.plannertoolgroup{margin-bottom:16px; display:flex; flex-direction:column; gap:10px}
.plannertoolgroup h4{font-size:11px; text-transform:uppercase; letter-spacing:.05em; color:var(--ink-soft); margin:0}
.plannertoolsub{display:flex; flex-direction:column; gap:4px}
.plannerempty{max-width:520px; margin:60px auto; text-align:center; color:var(--ink-soft)}
/* ---- standalone resource workspace ----
   A wrapper AROUND the existing renderResourceHTML() output, not a
   redesign of it — same .pr-page styling as a section's resource card
   uses, just without a section to sit inside. Reuses .plannerresourcestatus/
   .plannersavedbadge/.plannerjobstatus as-is (see plannerRenderSectionHTML's
   own resource card) so a "Saved"/"Not saved yet"/working state looks and
   behaves identically wherever it appears in the planner. */
.plannerstandalonewrap{max-width:800px; margin:0 auto}
.plannerstandalonehead{display:flex; align-items:flex-start; justify-content:space-between; gap:12px; margin:0 0 10px; flex-wrap:wrap}
.plannerstandalonetype{font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.05em; color:var(--sprout-dark); background:var(--mint); padding:3px 9px; border-radius:999px; display:inline-block; margin-bottom:6px}
.plannerstandalonetitle{font-family:var(--display); margin:0; font-size:22px}
@media(max-width:900px){ .plannerstandalonehead{flex-direction:column} }
.plannerlibgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(220px,1fr)); gap:14px; margin-top:14px}
.plannerlibcard{background:#fff; border:2px solid var(--mint-deep); border-radius:12px; padding:14px; cursor:pointer; display:flex; flex-direction:column; gap:6px; min-width:0; position:relative}
.plannerlibcard:hover{border-color:var(--sprout)}
.plannerlibcard .lt{font-size:11px; font-weight:800; text-transform:uppercase; color:var(--sprout-dark)}
.plannerlibcard h4{margin:0 22px 0 0; font-family:var(--display); font-size:16px; overflow-wrap:anywhere; word-break:break-word; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden}
.plannerlibcard .ltags{display:flex; gap:5px; flex-wrap:wrap}
.plannerlibcard .ltags span{font-size:10px; background:var(--mint); border-radius:999px; padding:2px 8px; font-weight:700; color:var(--ink-soft)}
/* Resource Library deletion — reuses the existing app-wide .mini
   hover-reveal icon-button convention (see the whiteboard folder/board
   rows) rather than inventing a new control: discoverable on hover/focus,
   never visually competing with the card's own open-on-click affordance.
   Absolutely positioned so it sits over the card without disturbing its
   flex layout; stopPropagation() in the click handler (planner.js) is
   what actually keeps it from also opening the card underneath. */
/* display:grid (not the app-wide .mini default of display:none) is
   deliberate here, not a copy-paste miss: a display:none element can
   never receive keyboard focus, which would make this the one .mini
   instance in the app a keyboard-only tutor could never reach. Opacity
   does the same "hidden until hover" job without that cost, and still
   shows it on :focus-visible for real keyboard operability. */
.plannerlibcard .mini{position:absolute; top:8px; right:8px; background:rgba(255,255,255,.9); display:grid; opacity:0; transition:opacity .15s}
.plannerlibcard:hover .mini, .plannerlibcard .mini:focus-visible{opacity:1}
.plannerlibcard.libdeleting{opacity:.55; pointer-events:none}
.plannerlibcard .mini:disabled{opacity:.5}
.plannerlibfilters{display:flex; gap:8px; flex-wrap:wrap; align-items:center}
.plannerlibfilters select, .plannerlibfilters input{font:inherit; padding:7px 10px; border-radius:8px; border:2px solid var(--mint-deep)}
.modalvisual:has(.plannerpushmodal){width:min(100%,460px)}
.plannerpushmodal{text-align:left}
.plannerpushstudents{display:flex; flex-direction:column; gap:6px; margin:12px 0}
.plannerpushmode{display:flex; flex-direction:column; gap:8px; margin:12px 0}
.plannerpushmode label{display:flex; gap:8px; align-items:flex-start; font-weight:700; font-size:13px; cursor:pointer}
.plannerpushmode small{display:block; font-weight:600; color:var(--ink-soft)}

/* ============================================================
   Checkpoint Planner-B5 — tutor-facing image generation.
   The 🎨 add/change/remove-image action row + suggestion chip
   (plannerImageActionsHTML, reused by both the lesson-section resource
   card and the standalone resource view) and the confirm-and-generate
   modal (plannerOpenImageModal). Reuses the SAME tokens/patterns as
   every other Planner control on this page (--mint/--mint-deep/--sprout/
   --ink-soft, .btn's own sizing, .tip's own type scale, .plannerpushmodal's
   own modal-width convention) rather than inventing a new visual language
   for one feature — see CLAUDE.md's UI-consistency rule.
   ============================================================ */
.plannerimageactions{display:flex; gap:6px; flex-wrap:wrap; margin-top:8px}
.plannerimagechip{display:flex; align-items:center; gap:6px; margin-top:8px; padding:8px 10px; border-radius:10px; background:color-mix(in srgb, var(--sprout) 10%, white); border:2px solid var(--mint-deep)}
.plannerimagechipbtn{flex:1; text-align:left; background:none; border:0; padding:0; font:inherit; font-weight:700; font-size:12.5px; color:var(--ink); cursor:pointer}
.plannerimagechipbtn:focus-visible{outline:2px solid var(--sprout); outline-offset:2px; border-radius:4px}
.plannerimagechipdismiss{flex:none; background:none; border:0; padding:2px 6px; font-size:13px; font-weight:800; color:var(--ink-soft); cursor:pointer; border-radius:6px}
.plannerimagechipdismiss:hover{background:rgba(0,0,0,.06)}
.plannerimagechipdismiss:focus-visible{outline:2px solid var(--ink-soft); outline-offset:1px}

/* Planner-B5a-X — tutor-only 🔑 answer-key reveal row + panel (see
   plannerPuzzleActionsHTML/plannerRenderPuzzleAnswerKeyHTML in planner.js).
   App chrome, not resource content — same token family as
   .plannerimageactions right above it, not the white-label .pr-* tokens
   the resource sheet itself uses. */
.plannerpuzzleactions{display:flex; gap:6px; flex-wrap:wrap; margin-top:8px}
.plannerpuzzleanswerkey{margin-top:10px; padding:10px 12px; border-radius:10px; background:color-mix(in srgb, var(--sprout) 8%, white); border:2px dashed var(--mint-deep)}
.plannerpuzzleanswerkeylabel{font-size:11.5px; font-weight:800; color:var(--ink-soft); margin-bottom:8px; text-transform:uppercase; letter-spacing:.03em}

.modalvisual:has(.plannerimagemodal){width:min(100%,480px)}
.plannerimagemodal{text-align:left; display:flex; flex-direction:column; gap:14px}
.plannerimagefield{display:flex; flex-direction:column; gap:6px}
.plannerimagefield > span{font-size:11px; font-weight:800; text-transform:uppercase; letter-spacing:.03em; color:var(--ink-soft)}
.plannerimagefield textarea{font:inherit; font-size:13.5px; padding:8px 10px; border-radius:10px; border:2px solid var(--mint-deep); resize:vertical; min-height:64px}
.plannerimagefield textarea:focus-visible{outline:2px solid var(--sprout); outline-offset:1px; border-color:var(--sprout)}
.plannerimagestyles{display:flex; flex-wrap:wrap; gap:8px}
.plannerimagestyle{display:flex; align-items:center; gap:6px; padding:6px 10px; border-radius:999px; background:#fff; border:2px solid var(--mint-deep); cursor:pointer; font-weight:700; font-size:12.5px}
.plannerimagestyle input{margin:0}
.plannerimagestyle.on{border-color:var(--sprout); background:color-mix(in srgb, var(--sprout) 10%, white)}
.plannerimagestyle:has(input:focus-visible){outline:2px solid var(--sprout); outline-offset:2px}
.plannerimagerefbtn{display:inline-block; width:fit-content; cursor:pointer}
.plannerimagerefbtn:focus-visible{outline:2px solid var(--sprout); outline-offset:2px}
input[type="file"]#pimRefFile{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap}
.plannerimagerefpreview{display:flex; gap:10px; align-items:flex-start; padding:8px; border-radius:10px; border:2px solid var(--mint-deep); background:#fff}
.plannerimagerefpreview img{border-radius:8px; object-fit:cover; flex:none; background:var(--mint)}
.plannerimagerefmeta{display:flex; flex-direction:column; gap:6px; min-width:0}
.plannerimagerefname{font-weight:700; font-size:12.5px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.plannerimagerefpurpose{display:flex; flex-direction:column; gap:4px; font-size:12px; font-weight:600}
.plannerimagerefpurpose label{display:flex; align-items:center; gap:6px; cursor:pointer}
.plannerimageerror{color:#b3261e; font-weight:700; font-size:12.5px; min-height:0}
.plannerimageerror:empty{display:none}

@media(max-width:900px){
  .plannerbody{flex-direction:column; overflow:auto}
  .plannercol.left, .plannercol.right{width:100%}
  /* Root-caused nested-scrolling bug: .plannercol's base rule (further up
     this file) gives EVERY column its own independent overflow-y:auto,
     which makes sense on desktop where left/centre/right sit side by side
     and each genuinely needs its own scrollbar. Here .plannerbody just
     switched to a single stacked column — but nothing reset each column's
     own overflow, so centre and right stayed independently scrollable
     INSIDE the now-also-scrolling .plannerbody: three nested scroll
     containers competing for the same touch-scroll gesture instead of one
     natural region, the classic "scroll gets stuck in a tiny box" mobile
     bug, and the deeper cause behind the settings/sections/actions blocks
     looking visually cramped into short fixed-height panes instead of
     each taking its real content height. .left is excluded here — at this
     same breakpoint it becomes a fixed off-canvas drawer (see the other
     max-width:900px block above), which legitimately needs its own
     internal scroll since it no longer participates in this flow at all. */
  .plannercol.centre, .plannercol.right{overflow-y:visible; height:auto}
}
