  /* ---------- Home shell: responsive layout ---------- */
  #screen-home{background:var(--paper);}
  .shell{ display:flex; flex-direction:column; }
  .main-col{ display:flex; flex-direction:column; min-width:0; }
  .z-results{padding-bottom:100px;}
  .side-nav{display:none;}

  @media (min-width:860px){
    .shell{ flex-direction:row; align-items:flex-start; }
    .side-nav{
      display:flex; flex-direction:column; flex:0 0 232px; width:232px;
      background:var(--black); color:#fff; padding:28px 18px;
      position:sticky; top:0; align-self:flex-start;
      height:100vh; height:100dvh; overflow-y:auto; overscroll-behavior-y:contain; scroll-behavior:smooth;
      transition:flex-basis 220ms var(--ease-pop), width 220ms var(--ease-pop), padding 220ms var(--ease-pop);
    }
    .main-col{ flex:1 1 auto; min-width:0; }
    .z-chips{display:none;}
    .z-search{display:none;}
    .z-brand, .exam-library-header{padding:32px 36px 6px !important;}
    .z-results{padding:0 36px 60px !important;}
    .bottom-nav{display:none !important;}
  }
  /* Round 22: this used to read `.grid{...!important}`, which — because .exam-grid carries the
     shared `.grid` class too (`class="grid exam-grid"` in index.html) — clobbered
     exam.css's own .exam-grid column breakpoints with !important no matter what exam.css set,
     forcing the book grid to 3 columns from 1180px up to 1500px regardless of how much room was
     actually free. That's what produced the "3 books, then 2 books, big empty gap on the right"
     layout the redesign brief called out. Scoped to :not(.exam-grid) so the exam bookshelf grid
     is governed only by its own rules in exam.css from here on; the plain course grid keeps its
     original behavior unchanged. */
  @media (min-width:1180px){
    .side-nav{flex-basis:250px; width:250px;}
    .side-nav-collapsed{flex-basis:84px !important; width:84px !important;}
    .grid:not(.exam-grid){grid-template-columns:repeat(3, 1fr) !important;}
  }
  @media (min-width:1500px){
    .grid:not(.exam-grid){grid-template-columns:repeat(4, 1fr) !important;}
  }

  .side-brand{display:flex; align-items:center; justify-content:space-between; gap:10px; transition:gap 220ms var(--ease-pop);}
  .side-brand-info{display:flex; align-items:center; gap:10px; min-width:0; overflow:hidden;}
  .side-badge{width:32px;height:32px;border-radius:50%;background:var(--yellow); color:var(--black); display:flex; align-items:center; justify-content:center; font-weight:800; flex:none;}
  .side-title{
    font-weight:700; font-size:var(--fs-base); white-space:nowrap; display:inline-block;
    opacity:1; max-width:120px; overflow:hidden;
    transition:opacity 160ms ease, max-width 220ms var(--ease-pop);
  }
  .side-search{margin-top:24px;}
  .side-nav-list{margin-top:26px; display:flex; flex-direction:column; gap:4px;}
  /* Round 11: background/color used to snap instantly on tab change; now both fade, and the
     row also nudges 3px right on hover/active so the desktop side-nav gets the same tap-forward
     feedback the mobile bottom-nav gets from its icon lift above. Kept to `background`+`color`+
     `transform` only (not e.g. `all`) so nothing about layout/box-shadow gets an accidental
     transition it wasn't designed to have. Round 14 adds `gap`/`padding` to the same transition
     list so the collapsible rail's icon-only state animates smoothly too. */
  .side-nav-btn{
    background:none; border:none; color:#A3A199; display:flex; align-items:center; gap:10px;
    padding:12px 12px; border-radius:12px; font-size:var(--fs-base); font-weight:500; text-align:left;
    transition:background 180ms ease, color 180ms ease, transform 180ms var(--ease-pop),
      gap 220ms var(--ease-pop), padding 220ms var(--ease-pop);
  }
  .side-nav-btn.active{background:rgba(245,217,73,0.12); color:var(--yellow); font-weight:700; transform:translateX(3px);}
  @media (hover:hover){
    .side-nav-btn:not(.active):hover{background:rgba(255,255,255,0.06); color:#fff;}
  }
  .side-account{
    margin-top:auto; padding-top:20px; border-top:1px solid rgba(255,255,255,0.1);
    display:flex; align-items:center; gap:10px; font-size:var(--fs-sm); color:#C7C5BB;
    transition:gap 220ms var(--ease-pop);
  }

  /* ---------- Round 14: collapsible rail ----------
     .side-nav-toggle is the small round chevron button in the brand row (js/ui.js's
     initSidebarToggle() flips .side-nav-collapsed on <nav class="side-nav"> and persists the
     choice to localStorage — a device-local UI preference, not something that belongs in
     Backend/profiles).

     Round 15: this used to hide each row's label via `font-size:0` on the row (so no markup
     change was needed anywhere) — but a font-size transition drags the *glyphs* down to nothing
     mid-animation, which reads as text "melting" rather than a clean slide/fade, and it couldn't
     animate at all for elements with no `transition` set on them. Replaced with a real
     `.side-nav-label` <span> around every row's trailing text (index.html's 7 static buttons;
     js/ui.js's two admin-injected buttons and renderSideAccount() do the same for their
     JS-built markup) that fades via opacity+max-width — the standard "collapsing width" pattern,
     since `width`/`max-width` can't transition to/from `auto` but a fixed max-width bigger than
     any real label works the same. The icon itself was never touched by either approach; its
     inline `margin-right:4px` still needs `!important` to win over the inline `style` attribute
     icons.js's icon() sets, but transitions apply to *computed* values regardless of how
     specificity got there, so it animates smoothly too. */
  .side-nav-toggle{
    flex:none; width:26px; height:26px; border-radius:50%; border:1.5px solid rgba(255,255,255,0.18);
    background:rgba(255,255,255,0.06); color:#fff; display:flex; align-items:center; justify-content:center;
  }
  .side-nav-toggle:active{background:rgba(255,255,255,0.16);}
  .side-nav-toggle svg{transition:transform 220ms var(--ease-pop);}

  .side-nav-label{
    display:inline-block; white-space:nowrap; overflow:hidden;
    opacity:1; max-width:160px;
    transition:opacity 160ms ease, max-width 220ms var(--ease-pop);
  }
  .side-nav-btn svg, .side-account svg{ transition:margin 220ms var(--ease-pop); }

  .side-nav-collapsed{flex-basis:84px; width:84px; padding-left:14px; padding-right:14px;}
  .side-nav-collapsed .side-brand{flex-direction:column; gap:14px;}
  .side-nav-collapsed .side-title{opacity:0; max-width:0;}
  .side-nav-collapsed .side-nav-toggle svg{transform:rotate(180deg);}
  .side-nav-collapsed .side-nav-btn{gap:0; justify-content:center; padding:12px 0;}
  .side-nav-collapsed .side-nav-btn svg{margin:0 !important;}
  .side-nav-collapsed .side-account{gap:0; justify-content:center;}
  .side-nav-collapsed .side-account svg{margin:0 !important;}
  .side-nav-collapsed .side-nav-label{opacity:0; max-width:0;}

  /* ---------- Round 25: grouped nav (collapsible sub-sections) ----------
     A .side-nav-group is a header button + a .side-nav-sub list of the real .side-nav-btn rows
     it contains. The header never navigates on its own — clicking it only toggles the group's
     "open" class (js/ui.js's initSidebarGroups()); the sub-buttons underneath are unchanged
     .side-nav-btn rows, so every existing active/hover rule above still applies to them as-is.
     setActiveTab() adds "has-active" to whichever header currently contains the active tab and
     force-opens that one group — every other group's open/closed state is left exactly as the
     person last set it, so opening one section never yanks another one shut on them. */
  .side-nav-group-header{
    background:none; border:none; color:#A3A199; display:flex; align-items:center; gap:10px;
    width:100%; padding:12px 12px; border-radius:12px; font-size:var(--fs-base); font-weight:500;
    text-align:left; cursor:pointer;
    transition:background 180ms ease, color 180ms ease, gap 220ms var(--ease-pop), padding 220ms var(--ease-pop);
  }
  @media (hover:hover){
    .side-nav-group-header:hover{background:rgba(255,255,255,0.06); color:#fff;}
  }
  .side-nav-group-header.has-active{color:var(--yellow); font-weight:700;}
  .side-nav-group-chevron{margin-left:auto; flex:none; transition:transform 200ms var(--ease-pop);}
  .side-nav-group.open .side-nav-group-chevron{transform:rotate(90deg);}
  .side-nav-sub{
    display:flex; flex-direction:column; overflow:hidden; max-height:0; padding-left:14px;
    transition:max-height 260ms var(--ease-pop);
  }
  .side-nav-group.open .side-nav-sub{max-height:220px;}
  .side-nav-sub .side-nav-btn{padding:10px 12px; font-size:var(--fs-sm);}
  .side-nav-sub .side-nav-btn.active{transform:translateX(2px);}

  /* Icon-only rail: there's no room for a header row + indented sub-list, so grouping collapses
     back to a flat icon stack — every sub-button always shows regardless of open/closed state,
     and the (now-redundant) chevron/header row hides itself out of the way. */
  .side-nav-collapsed .side-nav-group-header .side-nav-group-chevron{display:none;}
  .side-nav-collapsed .side-nav-group-header{padding:12px 0; justify-content:center; gap:0;}
  .side-nav-collapsed .side-nav-sub{max-height:none !important; padding-left:0;}
  .side-nav-collapsed .side-nav-sub .side-nav-btn{gap:0; justify-content:center; padding:12px 0;}
  .side-nav-collapsed .side-nav-sub .side-nav-btn svg{margin:0 !important;}

