  .solution-render .qcard{border:1px solid var(--line); border-radius:14px; padding:16px 18px; margin-bottom:16px;}
  .solution-render .qhead{font-weight:800; font-size:var(--fs-base); color:var(--warning); margin-bottom:8px; cursor:pointer; display:flex; align-items:center; gap:6px; user-select:none;}
  .solution-render .qhead-toggle{display:inline-block; font-size:var(--fs-2xs); transition:transform 150ms ease;}
  .solution-render .qcard.collapsed .qhead{margin-bottom:0;}
  .solution-render .qcard.collapsed .qhead-toggle{transform:rotate(-90deg);}
  /* Round 9: smooth collapse instead of an instant display:none jump-cut. The 1fr/0fr
     grid-template-rows trick animates to/from "auto" height without JS ever having to measure
     pixel heights — the actual content lives in .qbody-inner (min-height:0 is required for the
     row to actually shrink toward 0, otherwise a grid item's default min-height:auto pins it to
     its content size and the transition has nothing to animate). See renderSolutionFromJSON()
     in answer-viewer.js for the .qbody-inner wrapper this depends on. */
  .solution-render .qbody{display:grid; grid-template-rows:1fr; transition:grid-template-rows 260ms var(--ease-pop);}
  .solution-render .qbody-inner{overflow:hidden; min-height:0;}
  .solution-render .qcard.collapsed .qbody{grid-template-rows:0fr;}
  .solution-render .line{font-size:var(--fs-base); line-height:1.9; margin-bottom:2px;}
  .solution-render .label{font-weight:700; color:var(--black);}
  .solution-render .gap{height:4px;}
  /* Shared conversation/passage block shown above the per-item ข้อ N cards, so the
     original context isn't lost when a reading passage/conversation is split into
     several fill-in-the-blank items. See renderSolutionFromJSON() / buildSolutionHtml(). */
  .solution-render .passage-box{border:1.5px solid var(--yellow-deep); background:#FFFBEA; border-radius:14px; padding:16px 18px; margin-bottom:18px;}
  .solution-render .passage-title{font-weight:800; font-size:var(--fs-sm); letter-spacing:0.04em; color:var(--warning); margin-bottom:10px; text-transform:uppercase;}
  .solution-render .passage-text{font-size:var(--fs-base); line-height:2; color:var(--ink); white-space:pre-wrap;}
  .solution-render .blank-hl{background:var(--yellow); color:var(--black); font-weight:700; padding:1px 5px; border-radius:5px;}

  /* ---------- Full-page solution view + print (in-page modal, no popup window —
     window.open()/new-tab approaches are unreliable on iPad/mobile Safari and can
     leave the app looking like it "kicked you back" to the home screen). Uses the shared
     .fullscreen-overlay class (styles/main.css) for its show/hide + open/close animation —
     see answer-viewer.js's ensureSolutionModal(), which sets that class on this element. ---------- */
  #solution-print-modal{background:#fff; display:flex; flex-direction:column; z-index:999;}

  /* ---------- Round 2: exam player (interactive question/choice/timer flow) ----------
     Also uses the shared .fullscreen-overlay class (see index.html) — kept in the default
     z-index band from that shared class (below the loading overlay/notifications so a network
     hiccup during submit still surfaces, above the drawer/lightbox since taking an exam should
     be the only thing on screen). */
  #exam-player-overlay{background:var(--paper); display:flex; flex-direction:column;}
  #exam-player-panel{flex:1; display:flex; flex-direction:column; min-height:0;}
  .ep-bar{display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; padding:14px 18px; border-bottom:1px solid var(--line); background:var(--card);}
  .ep-title{font-weight:800; font-size:var(--fs-base); color:var(--ink);}
  .ep-timer{font-weight:700; font-size:var(--fs-base); padding:4px 12px; border-radius:20px; background:rgba(18,18,18,0.06);}
  .ep-timer.ep-timer-low{background:#FBE3DD; color:var(--danger);}
  .ep-body{flex:1; overflow-y:auto; -webkit-overflow-scrolling:touch; scroll-behavior:smooth; padding:20px 20px 100px; max-width:640px; margin:0 auto; width:100%; box-sizing:border-box;}
  .ep-nav-pills{display:flex; flex-wrap:wrap; gap:6px; margin-bottom:20px;}
  .ep-pill{width:34px; height:34px; border-radius:50%; border:1.5px solid var(--line); background:var(--card); font-size:var(--fs-sm); font-weight:700; cursor:pointer; display:flex; align-items:center; justify-content:center;}
  .ep-pill.answered{background:var(--yellow); border-color:var(--yellow-deep);}
  .ep-pill.current{border-color:var(--black); border-width:2px;}
  .ep-question-meta{font-size:var(--fs-xs); color:var(--muted); margin-bottom:8px;}
  /* Roadmap Phase 2 section 3.4 — passage box sits between the meta line and the question
     text/image, styled as a distinct "reference material" panel (muted background, left accent
     bar) so it doesn't get confused with the question itself. */
  .ep-passage-box{background:rgba(18,18,18,0.035); border-left:3px solid var(--line); border-radius:8px; padding:14px 16px; margin-bottom:16px; font-size:var(--fs-sm); line-height:1.7;}
  .ep-passage-title{font-weight:700; margin-bottom:6px; font-size:var(--fs-sm);}
  .ep-question-text{font-size:var(--fs-md); font-weight:700; line-height:1.6; margin-bottom:18px;}
  .ep-choice{display:flex; align-items:flex-start; gap:10px; border:1.5px solid var(--line); border-radius:14px; padding:14px 16px; margin-bottom:12px; cursor:pointer; font-size:var(--fs-base); line-height:1.6;}
  .ep-choice.selected{border-color:var(--black); background:rgba(18,18,18,0.04); font-weight:600;}
  .ep-choice.correct{border-color:var(--success); background:var(--success-bg);}
  .ep-choice.incorrect{border-color:var(--danger); background:#FBE3DD;}
  .ep-choice input{margin-top:2px; flex-shrink:0;}
  .ep-bottom-bar{position:fixed; left:0; right:0; bottom:0; display:flex; gap:10px; padding:14px 18px; background:var(--paper); border-top:1px solid var(--line); max-width:640px; margin:0 auto;}
  .ep-result-score{font-size:var(--fs-3xl); font-weight:800; text-align:center; margin:10px 0 4px;}
  .ep-result-sub{font-size:var(--fs-sm); color:var(--muted); text-align:center; margin-bottom:24px;}
  .ep-result-item{border:1px solid var(--line); border-radius:14px; padding:14px 16px; margin-bottom:12px;}
  .ep-result-item.wrong{border-color:#F2C4B6;}
  .ep-explanation{font-size:var(--fs-sm); color:var(--muted); margin-top:8px; padding-top:8px; border-top:1px dashed var(--line);}

  /* Comment author avatar (photo synced from the account's profile photo — see
     018-comment-name-photo-sync.sql) shown just before the display name. */
  .comment-avatar{width:20px; height:20px; border-radius:50%; object-fit:cover; flex-shrink:0;}
  .comment-avatar-fallback{display:inline-flex; align-items:center; justify-content:center; background:rgba(18,18,18,0.08); color:var(--muted);}
  .sol-modal-bar{display:flex; align-items:center; justify-content:space-between; gap:12px; flex-wrap:wrap; padding:14px 18px; border-bottom:1px solid var(--line); background:var(--paper);}
  .sol-modal-title{font-weight:800; font-size:var(--fs-md); color:var(--ink);}
  /* Per-question collapse/expand + jump controls — only shown when the current
     solution has collapsible .qcard items (structured JSON solutions). */
  .sol-modal-controls{display:flex; align-items:center; gap:10px; flex-wrap:wrap; padding:10px 18px; border-bottom:1px solid var(--line); background:var(--card); font-size:var(--fs-sm);}
  .sol-modal-controls button{width:auto; padding:6px 14px; font-size:var(--fs-sm);}
  .sol-modal-controls select{width:auto; padding:6px 10px; font-size:var(--fs-sm);}
  .sol-modal-body{flex:1; overflow-y:auto; -webkit-overflow-scrolling:touch; scroll-behavior:smooth; padding:20px 20px 60px; max-width:720px; margin:0 auto; width:100%;}
  .sol-modal-meta{font-size:var(--fs-sm); color:var(--muted); margin-bottom:16px;}
  body.sol-modal-open{overflow:hidden;}
  @media print{
    body *{visibility:hidden;}
    #solution-print-modal, #solution-print-modal *{visibility:visible;}
    /* The base rule (#solution-print-modal{position:fixed; inset:0; ...} above) pins the modal
       to exactly one viewport-height box, and .sol-modal-body's overflow-y:auto turns its
       content into an internally-scrolling region — printed/PDF output can only ever capture
       what's on-screen in a fixed, scrolling box like that, which is exactly why "Save as PDF"
       was coming out as one (often blank, depending on scroll position) page instead of the
       full solution. Overriding position/height/overflow back to normal in-flow values here
       lets the browser's print engine measure the *real* full height of the content and
       paginate it across as many pages as it actually needs. */
    #solution-print-modal{position:static; inset:auto; display:block !important; background:#fff; height:auto;}
    .sol-modal-body{flex:none; overflow:visible; height:auto; max-width:100%; padding:0;}
    .no-print{display:none !important;}
    /* Printed/PDF output should always show everything, regardless of on-screen collapse state. */
    .solution-render .qcard.collapsed .qbody{grid-template-rows:1fr !important;}
  }

  /* ---------- Round 4: anti-cheat UI — pre-flight rules box, watermark, warning banner ---------- */
  .ep-preflight-box{border:1.5px solid var(--line); border-radius:14px; padding:18px 20px; background:var(--card);}
  .ep-preflight-box li{margin-bottom:6px;}

  /* .ep-body needs position:relative so the watermark (position:absolute) tiles over
     just the question area, not the whole fixed overlay (nav bar/timer stay clear). */
  .ep-body{position:relative;}
  .ep-watermark{
    position:absolute; inset:0; z-index:5; pointer-events:none; overflow:hidden;
    display:flex; flex-wrap:wrap; align-content:space-around; justify-content:space-around;
    opacity:0.07; transform:rotate(-22deg) scale(1.3); user-select:none;
  }
  .ep-watermark span{font-size:var(--fs-xs); font-weight:700; white-space:nowrap; color:var(--black); margin:18px 14px;}

  .ep-warn-banner{
    position:fixed; left:50%; bottom:90px; transform:translate(-50%, 12px);
    max-width:520px; width:calc(100% - 32px); box-sizing:border-box;
    background:var(--danger); color:#fff; font-size:var(--fs-sm); font-weight:600; line-height:1.5;
    border-radius:12px; padding:12px 16px; box-shadow:0 8px 24px rgba(0,0,0,0.18);
    opacity:0; pointer-events:none; transition:opacity 180ms ease, transform 180ms ease; z-index:20;
  }
  .ep-warn-banner.show{opacity:1; transform:translate(-50%, 0);}

  /* ---------------- Round 22: exam library page header (spec item 5) ----------------
     Own title/tagline/stats block instead of reusing the course-search hero — toggled opposite
     .z-brand in setActiveTab() (js/ui.js). Padding intentionally mirrors .z-brand/.home-header's
     own padding (courses.css + the min-width:860px override in sidebar.css) so switching tabs
     doesn't visibly jump the content's top edge. */
  .exam-library-header{padding:24px 20px 8px;}
  .exam-library-title{font-size:var(--fs-2xl); font-weight:800; line-height:1.3;}
  .exam-library-sub{font-size:var(--fs-sm); color:var(--muted); font-weight:300; margin-top:6px; line-height:1.6; max-width:480px;}
  .exam-library-stats{display:flex; align-items:center; flex-wrap:wrap; gap:8px; margin-top:14px; font-size:var(--fs-sm); color:var(--muted);}
  .exam-library-stats strong{color:var(--ink); font-weight:800;}
  .exam-library-stats-dot{width:3px; height:3px; border-radius:50%; background:var(--muted); opacity:0.5;}
  .exam-chips-wrap{padding:14px 20px 0;}
  @media (min-width:860px){ .exam-chips-wrap{padding:14px 36px 0;} }

  /* ---------------- Round 13/14: "คลังข้อสอบ" bookshelf redesign ----------------
     examCard() in exam-manager.js renders a book-cover-style card instead of the plain
     .course-card: a colored cover (subject-specific artwork) with the exam title printed
     directly on the cover, a bookmark toggle on the cover's top-right corner, then
     subject/level/status below on the plain page background — matching the reference
     bookstore UI's flat book-cover-card look instead of a flat badge-in-a-box card. Applies
     to both grids that render examCard(): the คลังข้อสอบ tab (#tab-exam-list) and the
     saved-exams half of the บันทึกไว้ tab (#tab-bookmark-exams-list), via the shared
     .exam-grid class on both containers in index.html — does not touch .course-card, so the
     course catalog and the "ทำข้อสอบได้ทันที" practice cards are unaffected.

     Round 21: switched from the 3-D spine+cream-mat+hanging-ribbon look (Round 19) to a flat
     cover-fills-the-card style per design review — cover now fills the whole card edge to
     edge, no spine sliver or page-block mat, and the bookmark toggle moved from a hanging
     ribbon at the bottom to a small icon button in the cover's top-right corner. Only the
     visual shell changed; examCard()'s data, click-to-open, and bookmark toggling logic are
     untouched.

     Round 22: layout/density pass per the redesign brief. Two things drove most of the "too
     big / too much dead space" feel:
     (1) sidebar.css's `.grid{...!important}` rules (now scoped :not(.exam-grid), see that
         file) were silently forcing this grid to 3 columns from 1180–1500px regardless of what
         was set here, so at common desktop widths it never actually reached 4 — that's what
         produced a ragged "3 then 2, empty gap on the right" last row.
     (2) the breakpoints below only had one step (3 cols) for the entire 520–1179px range, so
         tablet widths were carrying the same card size as narrow phones-in-landscape.
     Breakpoints now line up with the app's existing sidebar breakpoints (mobile bottom-nav
     below 860px, sidebar from 860px, desktop from 1180px) so "how many books per row" always
     matches the layout mode the person is actually in: 2 on mobile, 3 once the sidebar appears
     (tablet/iPad), 4 at desktop widths — and covers are ~20% smaller at every step than the
     old sizes so a row reads as a shelf, not four oversized tiles. */
  .exam-grid{grid-template-columns:repeat(2, 1fr); gap:14px 10px; align-content:start;}
  @media (min-width:480px){ .exam-grid{grid-template-columns:repeat(3, 1fr); gap:16px 12px;} }
  @media (min-width:860px){ .exam-grid{grid-template-columns:repeat(3, 1fr); gap:20px 16px;} }
  @media (min-width:1180px){ .exam-grid{grid-template-columns:repeat(4, 1fr); gap:20px 16px;} }
  @media (min-width:1500px){ .exam-grid{gap:22px 18px;} }

  .exam-book-card{background:none; border:none; padding:0; display:flex; flex-direction:column; cursor:pointer; transition:transform 220ms var(--ease-pop);}
  .exam-book-card:active{transform:scale(0.98);}

  /* Round 22, spec item 11 ("book shelf feeling"): a soft, slightly-wide-and-flat shadow sitting
     just under the cover reads as the book resting on a shelf rather than floating — kept subtle
     (no visible shelf plank/line) so it stays a modern flat UI, not a literal 3-D bookcase. */
  .exam-book-cover-wrap{position:relative; aspect-ratio:3/4.3; border-radius:12px;
    box-shadow:0 1px 2px rgba(18,18,18,0.07), 0 8px 14px rgba(18,18,18,0.05);
    transition:transform 220ms var(--ease-pop), box-shadow 220ms var(--ease-pop);}
  @media (hover:hover){ .exam-book-card:hover .exam-book-cover-wrap{
      transform:translateY(-5px);
      box-shadow:0 4px 8px rgba(18,18,18,0.09), 0 18px 24px rgba(18,18,18,0.13);
  } }

  .exam-book-cover{position:absolute; inset:0; border-radius:12px; padding:9px 9px 15px; display:flex; flex-direction:column; overflow:hidden; color:#fff;}

  .exam-book-cover::before{ /* quiet top-left sheen + gentle bottom darkening, no hard edges */
    content:""; position:absolute; inset:0; z-index:1; pointer-events:none; border-radius:inherit;
    background:
      radial-gradient(130% 65% at 14% 8%, rgba(255,255,255,0.26), rgba(255,255,255,0) 55%),
      linear-gradient(180deg, rgba(0,0,0,0) 58%, rgba(0,0,0,0.22) 100%);
  }
  /* "Spine" hint: a slightly darker vertical strip along the left edge of the cover, the way a
     book's left edge catches less light than its face — a light touch, not a literal 3-D spine. */
  .exam-book-cover::after{
    content:""; position:absolute; inset:0 auto 0 0; width:9%; z-index:1; pointer-events:none; border-radius:12px 0 0 12px;
    background:linear-gradient(90deg, rgba(0,0,0,0.16), rgba(0,0,0,0));
  }
  /* Round 20: full-bleed per-subject artwork (see EXAM_COVER_ART in exam-manager.js) replacing
     the old single centered watermark icon — sits under the sheen (::before, z-index 1) and the
     format badge/title (z-index 2) below, same stacking spot the old watermark occupied. */
  .exam-book-artwork{position:absolute; inset:0; pointer-events:none;}
  .exam-book-artwork svg{width:100%; height:100%; display:block;}
  /* Round 22, spec item 9: badge shrunk ~20% (font/padding) so it reads as a small tag on the
     cover, not a second headline competing with the title. */
  .exam-book-format{position:relative; z-index:2; align-self:flex-start; font-size:var(--fs-2xs); font-weight:700; letter-spacing:0.02em; background:rgba(255,255,255,0.24); padding:2px 6px; border-radius:5px; display:flex; align-items:center; color:#fff;}
  .exam-book-cover-title{position:relative; z-index:2; margin-top:auto; font-size:var(--fs-xs); font-weight:800; line-height:1.32; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical; overflow:hidden; text-shadow:0 1px 3px rgba(0,0,0,0.3);}

  /* Round 21: bookmark toggle moved off the hanging ribbon and onto the cover itself, top-right
     corner — same active/inactive color logic (yellow = not saved, coral = saved) as before.
     Round 22: sized down ~20% to stay a quiet overlay, not a second primary button next to the
     open-exam CTA that now appears on hover. */
  .exam-book-bookmark{position:absolute; top:7px; right:7px; z-index:3; width:22px; height:22px; border:none; padding:0; margin:0; cursor:pointer; display:flex; align-items:center; justify-content:center; background:rgba(255,255,255,0.18); border-radius:7px; transition:background 150ms ease;}
  .exam-book-bookmark svg{width:11px; height:13px;}
  .exam-book-bookmark.active{background:var(--coral);}

  /* Round 22, spec item 10: "เปิดข้อสอบ" pill fades in over the cover on hover (desktop only —
     touch devices have no hover state, so mobile/tablet keep the whole-card tap as the only
     affordance, unchanged). Purely a visual hint; the click handler is still on the whole card
     (examCard() in exam-manager.js), so tapping anywhere on the cover — not just this pill —
     already opened the exam before and still does. */
  .exam-book-open-cta{
    position:absolute; z-index:2; left:9px; right:9px; bottom:15px;
    text-align:center; font-size:var(--fs-2xs); font-weight:700; color:#fff;
    background:rgba(18,18,18,0.42); backdrop-filter:blur(2px);
    border-radius:8px; padding:6px 8px;
    opacity:0; transform:translateY(4px); pointer-events:none;
    transition:opacity 200ms ease, transform 200ms ease;
  }
  @media (hover:hover){ .exam-book-card:hover .exam-book-open-cta{ opacity:1; transform:translateY(0); } }

  .exam-book-info{padding:8px 1px 0; display:flex; flex-direction:column; gap:3px;}
  /* Round 22, spec item 8: subject already appears large on the cover itself via the title, so
     the line here is now a smaller supporting label (like a spine label) rather than a second
     headline — status line right under it is the thing that actually changes per exam and
     matters most when scanning the shelf. */
  .exam-book-subject{font-size:var(--fs-2xs); font-weight:700; opacity:0.85;}
  .exam-book-info .tag-row{margin:0;}
  .exam-book-info .tag{font-size:var(--fs-2xs); padding:2px 8px;}
  .exam-book-info .card-teacher{font-size:var(--fs-xs);}
  .exam-book-info .card-cta{font-size:var(--fs-xs);}


  /* ==================================================================================
     Round 23: "คลังข้อสอบ" folder-carousel home (renders into #exam-folder-home) +
     breadcrumb (#exam-breadcrumb) — see exam-manager.js. Independent of everything above
     this line, which still styles the reference-exam grid/cards shown once a folder here
     is opened (unchanged, just now reached via a folder click instead of the chips).

     Round 24: folders now use a real file-folder silhouette (clip-path tab+body in one
     shape, not two stacked rectangles — see .exam-folder-art below) with two extra layers
     behind it for the "pile of folders" depth the redesign brief asked for, plus an actual
     open/close sequence (.exam-folder.opening / .exam-folder-home-transitioning) instead of
     an instant switch to the exam list — see openExamFolder()/closeExamFolder() in
     exam-manager.js. ================================================================== */

  .exam-level-section{margin-bottom:34px; animation:examFadeUp 380ms cubic-bezier(.2,.8,.3,1) both;}
  .exam-level-section:nth-child(2){animation-delay:40ms;}
  .exam-level-section:nth-child(3){animation-delay:80ms;}
  @keyframes examFadeUp{ from{ opacity:0; transform:translateY(10px); } to{ opacity:1; transform:none; } }
  @media (prefers-reduced-motion:reduce){ .exam-level-section{animation:none;} }

  .exam-level-head{margin-bottom:10px;}
  .exam-level-title{font-size:var(--fs-md); font-weight:800; margin:0;}
  .exam-level-sub{font-size:var(--fs-xs); color:var(--muted); margin:2px 0 0;}

  .exam-folder-row-wrap{position:relative;}
  .exam-folder-row{
    display:flex; gap:22px; overflow-x:auto; padding:10px 4px 16px;
    scroll-snap-type:x proximity; -webkit-overflow-scrolling:touch;
    scrollbar-width:none; cursor:grab;
    /* Round 24 bugfix: this was `pan-y`, which tells the browser "only vertical touch panning is
       native here, hand horizontal off to JS" — but no touch handler ever claimed it (the pointer
       drag logic below explicitly skips touch input), so on iPad/mobile the row genuinely could
       not be swiped at all, in either direction. `pan-x` is correct: native horizontal swipe
       scrolls the row (with real OS touch momentum, for free), vertical touch still passes
       through to scroll the page. */
    touch-action:pan-x;
  }
  .exam-folder-row::-webkit-scrollbar{display:none;}
  .exam-folder-row.dragging{cursor:grabbing; scroll-snap-type:none;}

  /* Small side arrows — desktop pointer devices only; touch/mobile relies on swipe (item 6/14
     of the brief: arrows are a helper, never the primary or most prominent interaction). */
  .exam-folder-arrow{
    display:none; position:absolute; top:38%; width:28px; height:28px; border-radius:50%;
    background:#fff; border:1px solid var(--line); align-items:center; justify-content:center;
    color:var(--ink); box-shadow:0 2px 6px rgba(18,18,18,0.14); z-index:6; opacity:0.85;
    transition:opacity 150ms ease, transform 150ms ease;
  }
  .exam-folder-arrow:hover{opacity:1; transform:scale(1.06);}
  .exam-folder-arrow-l{left:-4px;} .exam-folder-arrow-r{right:-4px;}
  @media (hover:hover) and (pointer:fine) and (min-width:640px){ .exam-folder-arrow{display:flex;} }

  .exam-folder{
    flex:0 0 auto; width:154px; scroll-snap-align:start; position:relative;
    border:none; padding:16px 0 0; background:none; cursor:pointer; text-align:left;
    -webkit-user-select:none; user-select:none;
    transition:transform 220ms cubic-bezier(.2,.8,.3,1);
  }
  @media (hover:hover){ .exam-folder:hover{ transform:translateY(-18px) scale(1.045); z-index:5; } }
  @media (hover:hover){ .exam-folder:hover .exam-folder-art{ box-shadow:0 10px 16px rgba(18,18,18,0.14), 0 26px 34px rgba(18,18,18,0.18); } }
  @media (hover:hover){ .exam-folder:hover .exam-folder-open{ opacity:1; transform:translateY(0); } }
  .exam-folder:active{transform:translateY(-6px) scale(1.015);}
  @media (max-width:639px){ .exam-folder{width:clamp(128px, 40vw, 152px);} }

  /* Round 25: persistent "this folder is the one currently expanded" state (toggleExamFolder()/
     openFolderPanel() in exam-manager.js) — a lasting lift+highlight, not a one-off transition
     phase, since the folder stays open (and this class stays on) until the user closes it again.
     Reuses the same lift/shadow language as :hover above, just held rather than momentary. */
  .exam-folder.exam-folder-active{
    transform:translateY(-14px) scale(1.03); z-index:5;
  }
  .exam-folder.exam-folder-active .exam-folder-art{
    box-shadow:0 10px 16px rgba(18,18,18,0.16), 0 26px 34px rgba(18,18,18,0.2);
    outline:2.5px solid var(--yellow-deep); outline-offset:2px;
  }
  .exam-folder.exam-folder-active .exam-folder-open{ opacity:1; transform:translateY(0); }

  /* Depth layers — two plain folder-shaped silhouettes sitting behind the real one, offset and
     slightly rotated like other folders in the same pile (reference-image-3 "pull one item from
     a stack" feeling, item 3/11 of the brief). Purely decorative: same background as the front
     folder but washed out, no content, aria-hidden. */
  .exam-folder-stack{
    position:absolute; inset:16px 0 auto 0; aspect-ratio:5/4;
    clip-path:polygon(0 14%, 32% 14%, 39% 0%, 63% 0%, 70% 14%, 100% 14%, 100% 100%, 0% 100%);
    filter:grayscale(0.55) brightness(1.14); opacity:0.55; pointer-events:none;
  }
  .exam-folder-stack-a{transform:translate(5px, 7px) rotate(-2.2deg);}
  .exam-folder-stack-b{transform:translate(-5px, 11px) rotate(2.4deg); opacity:0.38;}

  /* The folder itself: tab + body drawn as ONE shape via clip-path (a small trapezoid bump on
     the top edge, like a manila folder tab) instead of two separately-rounded rectangles stacked
     with a negative margin — that seam was exactly why this used to read as "a card with a
     stripe on top" rather than an actual folder. */
  .exam-folder-art{
    position:relative; aspect-ratio:5/4; overflow:hidden;
    clip-path:polygon(0 14%, 32% 14%, 39% 0%, 63% 0%, 70% 14%, 100% 14%, 100% 100%, 0% 100%);
    box-shadow:0 3px 5px rgba(18,18,18,0.10), 0 10px 18px rgba(18,18,18,0.10);
    transition:box-shadow 200ms ease;
  }
  .exam-folder-art svg{position:absolute; inset:0; width:100%; height:100%; opacity:0.82;}
  /* Fold-line + sheen: a soft light band right under the tab (like the crease where a folder's
     front panel meets its tab) plus the usual top-left sheen / bottom darkening for depth. */
  .exam-folder-art::before{
    content:""; position:absolute; inset:0; z-index:1; pointer-events:none;
    background:
      linear-gradient(180deg, rgba(255,255,255,0.32) 0%, rgba(255,255,255,0) 20%),
      radial-gradient(120% 60% at 12% 10%, rgba(255,255,255,0.20), rgba(255,255,255,0) 55%),
      linear-gradient(180deg, rgba(0,0,0,0) 55%, rgba(0,0,0,0.26) 100%);
  }
  .exam-folder-brand{
    position:absolute; z-index:2; right:9px; bottom:8px; font-size:var(--fs-2xs); font-weight:800;
    letter-spacing:0.05em; color:rgba(255,255,255,0.65); pointer-events:none;
  }
  .exam-folder-open{
    position:absolute; z-index:2; left:9px; bottom:8px; font-size:var(--fs-2xs); font-weight:700; color:#fff;
    opacity:0; transform:translateY(5px); transition:opacity 180ms ease, transform 180ms ease;
    pointer-events:none;
  }

  .exam-folder-body{display:block; padding:11px 4px 0;}
  .exam-folder-name{display:block; font-size:var(--fs-sm); font-weight:800; line-height:1.28;}
  .exam-folder-count{display:block; font-size:var(--fs-2xs); color:var(--muted); margin-top:2px;}

  /* ---------------- Round 25: inline folder panel — the files "inside" an opened folder,
     expanded directly under that level's own row (openFolderPanel()/collapseOpenFolder() in
     exam-manager.js). Sits inside .exam-level-section, so it only ever affects that one level's
     layout — every other level section, and every other folder in this same row, stay put. ---- */
  .exam-folder-panel{
    margin-top:6px; padding:18px 16px 6px; border-radius:16px;
    background:linear-gradient(180deg, #F1EEE1 0%, var(--paper) 100%);
    border:1px solid var(--line);
    /* Collapsed by default via max-height, not display:none, so the open/close is a real
       height+fade transition ("เอกสารเลื่อนขึ้นจากด้านใน") instead of an instant cut — item 8. */
    max-height:0; opacity:0; overflow:hidden;
    transition:max-height 420ms cubic-bezier(.2,.8,.3,1), opacity 320ms ease, margin-top 320ms ease, padding 320ms ease;
  }
  .exam-folder-panel.hidden{ display:none; }
  .exam-folder-panel.exam-folder-panel-open{ max-height:2400px; opacity:1; }
  @media (prefers-reduced-motion:reduce){ .exam-folder-panel{ transition:opacity 160ms ease; } }
  .exam-folder-panel-head{
    display:flex; align-items:center; justify-content:space-between; gap:10px;
    margin-bottom:14px; padding-bottom:12px; border-bottom:1px dashed var(--line);
  }
  .exam-folder-panel-title{display:flex; align-items:center; gap:6px; font-size:var(--fs-base); font-weight:800; color:var(--ink);}
  .exam-folder-panel-count{font-size:var(--fs-xs); font-weight:600; color:var(--muted); margin-left:2px;}
  .exam-folder-panel-close{
    flex-shrink:0; width:30px; height:30px; border-radius:9px; border:1px solid var(--line);
    background:#fff; color:var(--ink); display:flex; align-items:center; justify-content:center; cursor:pointer;
  }
  .exam-folder-panel-close:hover{background:var(--paper);}
  .exam-folder-panel-grid{padding-bottom:18px;}

  /* Phase 3 of the open sequence: the exam-list panel that appears once the folder has "opened"
     — a soft scale/rise-in rather than an instant cut, so content feels like it's emerging from
     the folder that was just lifted. Unconditional (not gated behind a toggled class) because
     #exam-results goes through display:none -> flow every time a folder opens (see
     renderExamLibraryView()), and a CSS animation on an element naturally restarts each time it
     re-enters the render tree that way — same trick .exam-level-section above already relies on. */
  #exam-results:not(.hidden){ animation:examFolderContentIn 260ms cubic-bezier(.2,.8,.3,1) both; }
  @keyframes examFolderContentIn{ from{ opacity:0; transform:translateY(16px) scale(0.965); } to{ opacity:1; transform:none; } }
  @media (prefers-reduced-motion:reduce){ #exam-results:not(.hidden){ animation:none; } }
  /* Close sequence (closeExamFolder() in exam-manager.js): play this fade-shrink-out first, then
     swap back to the folder home — so closing a folder is the same three-beat motion as opening
     it, just reversed, instead of an instant cut back to the carousel. */
  #exam-results.exam-results-leaving{ animation:examFolderContentOut 180ms ease both; }
  @keyframes examFolderContentOut{ from{ opacity:1; transform:none; } to{ opacity:0; transform:translateY(10px) scale(0.975); } }
  @media (prefers-reduced-motion:reduce){ #exam-results.exam-results-leaving{ animation:none; } }

  /* ---------------- Breadcrumb (#exam-breadcrumb) ---------------- */
  .exam-breadcrumb{display:flex; align-items:center; gap:6px; flex-wrap:wrap; font-size:var(--fs-sm); color:var(--muted); margin:2px 0 16px;}
  .exam-breadcrumb-root{background:none; border:none; padding:0; margin:0; font:inherit; color:var(--muted); font-weight:600; cursor:pointer; text-decoration:underline; text-underline-offset:2px; text-decoration-color:transparent; transition:text-decoration-color 150ms ease, color 150ms ease;}
  .exam-breadcrumb-root:hover{color:var(--ink); text-decoration-color:currentColor;}
  .exam-breadcrumb-sep{opacity:0.5;}
  .exam-breadcrumb-level{color:var(--muted);}
  .exam-breadcrumb-subject{color:var(--ink); font-weight:700;}
