/* Homepage styles, extracted from index.html's inline <style> so they can be
   cached separately from the 129 KB of quiz-listing markup that changes
   whenever content is added. Loaded after theme.css, exactly where the
   inline block sat, so the cascade order is unchanged. */
:root {
      --bg: #f3f4f6;
      --card: #ffffff;
      --border: #e5e7eb;
      --ink: #1f2937;
      --muted: #6b7280;
      --accent: #2a6df2;
      --accent-soft: #eff6ff;
      --accent2: #218640;
      --accent2-soft: #f0fdf4;
      --shadow: 0 1px 3px rgba(0,0,0,0.06), 0 8px 24px rgba(0,0,0,0.06);
      --tab-border: rgba(37, 99, 235, 0.25);
    }
    @media (prefers-color-scheme: dark) {
      :root {
        --bg: #0f1115;
        --card: #191c22;
        --border: #2a2e37;
        --ink: #e5e7eb;
        --muted: #9aa1ac;
        --accent: #4981ee;
        --accent-soft: #16213a;
        --accent2: #4ade80;
        --accent2-soft: #12251a;
        --shadow: 0 1px 3px rgba(0,0,0,0.3), 0 8px 24px rgba(0,0,0,0.35);
        --tab-border: rgba(96, 165, 250, 0.3);
      }
      .edge-decor {
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 420'%3E%3Cpath d='M30,0 L30,50 L12,60 L48,80 L30,90 L30,104 Q40,112 30,120 L30,175 L12,185 L48,205 L30,215 L30,229 Q40,237 30,245 L30,320 L12,330 L48,350 L30,360 L30,374 Q40,382 30,390 L30,420' fill='none' stroke='%234981ee' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
      }
      .bottom-decor {
        background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 60'%3E%3Cpath d='M0,30 L120,30 L135,8 L150,52 L165,30 L300,30' fill='none' stroke='%2360a5fa' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
      }
    }

    @keyframes decor-pulse-intro {
      0% { opacity: 0; }
      25% { opacity: 0.4; }
      50% { opacity: 0.05; }
      75% { opacity: 0.3; }
      100% { opacity: 0.17; }
    }

    .edge-decor {
      position: fixed;
      top: 0;
      bottom: 0;
      width: 70px;
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 420'%3E%3Cpath d='M30,0 L30,50 L12,60 L48,80 L30,90 L30,104 Q40,112 30,120 L30,175 L12,185 L48,205 L30,215 L30,229 Q40,237 30,245 L30,320 L12,330 L48,350 L30,360 L30,374 Q40,382 30,390 L30,420' fill='none' stroke='%232a6df2' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
      background-repeat: repeat-y;
      background-position: center;
      background-size: 60px 420px;
      opacity: 0.17;
      pointer-events: none;
      z-index: -1;
      display: none;
      animation: decor-pulse-intro 2.2s ease-in-out 1;
      -webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 18%, black 82%, transparent 100%);
      mask-image: linear-gradient(to bottom, transparent 0%, black 18%, black 82%, transparent 100%);
    }
    .edge-decor.left { left: 0; }
    .edge-decor.right { right: 0; transform: scaleX(-1); }

    @media (min-width: 1000px) {
      .edge-decor { display: block; }
    }

    .bottom-decor {
      position: fixed;
      left: 0;
      right: 0;
      bottom: 0;
      height: 60px;
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 60'%3E%3Cpath d='M0,30 L120,30 L135,8 L150,52 L165,30 L300,30' fill='none' stroke='%232563eb' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
      background-repeat: repeat-x;
      background-position: center;
      background-size: 300px 60px;
      opacity: 0.17;
      pointer-events: none;
      z-index: -1;
      display: none;
      animation: decor-pulse-intro 2.2s ease-in-out 1;
      -webkit-mask-image: linear-gradient(to right, transparent 0%, black 15%, black 85%, transparent 100%);
      mask-image: linear-gradient(to right, transparent 0%, black 15%, black 85%, transparent 100%);
    }

    @media (max-width: 640px) {
      .bottom-decor { display: block; }
    }

    * { box-sizing: border-box; }
    html { overscroll-behavior-y: contain; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
      background: var(--bg);
      color: var(--ink);
      margin: 0;
      padding: 48px 20px;
      min-height: 100vh;
      overscroll-behavior-y: contain;
    }

    #pull-refresh {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      height: 0;
      overflow: hidden;
      display: flex;
      align-items: center;
      justify-content: center;
      background: var(--bg);
      z-index: 1000;
    }
    #pull-refresh svg { width: 100%; height: 44px; }
    #pull-refresh path {
      fill: none;
      stroke: var(--accent);
      stroke-width: 3;
      stroke-linecap: round;
      stroke-linejoin: round;
      stroke-dasharray: 420;
      stroke-dashoffset: 420;
    }
    #pull-refresh.refreshing path {
      animation: ekg-sweep 0.9s linear infinite;
    }
    @keyframes ekg-sweep {
      0% { stroke-dashoffset: 420; }
      100% { stroke-dashoffset: -420; }
    }

    .page {
      max-width: 640px;
      margin: 0 auto;
      position: relative;
    }

    .mobile-widgets-row { display: contents; }

    /* Only .page becomes a flex container (so `order` works) below the mobile
       breakpoint -- outside it, .page stays plain block layout like always,
       so every direct child (including ones added later, e.g. #request-open)
       keeps its normal document-order position with zero risk of silently
       jumping elsewhere. Every current direct child of .page is explicitly
       ordered here; a future new direct child needs an explicit order added
       too, or it defaults to 0 and floats above the header. */
    @media (max-width: 640px) {
      /* row-wrap rather than column so the two CTA rows can share a line.
         Every other direct child is flex:1 1 100% below, so it still
         occupies a full row exactly as it did under column flex -- the
         existing `order` values keep working unchanged. */
      .page {
        display: flex;
        flex-flow: row wrap;
        column-gap: 8px;
        align-items: flex-start;
      }
      .page > * { flex: 1 1 100%; min-width: 0; }
      /* Relax + Group Study pair up, matching the Arcade/Guides pair
         above them -- two full-width bars became one row. */
      .page > #relax-cta-row,
      .page > #group-cta-row { flex: 1 1 calc(50% - 4px); }
      .page > #relax-cta-row .relax-cta,
      .page > #group-cta-row .relax-cta { width: 100%; justify-content: center; }
      .page > header { order: 10; }
      .mobile-widgets-row {
        display: flex;
        order: 20;
        gap: 8px;
        margin: 14px 0;
        align-items: stretch;
      }
      .page > .search-wrap { order: 30; }
      /* Every direct child of .page needs an explicit order at this width or it
         floats above the header.
         2026-09-25 (design review item 5, approved by Jaxon): the semester card
         -- class tabs and exam accordions, the thing people open the site for --
         moved up from ~1,650px down to directly under the search row. The
         personal widgets follow it. Accordions still load closed. */
      .page > #semester-card { order: 31; }
      .page > #question-issue-row { order: 32; }
      .page > #relax-cta-row { order: 33; }
      .page > #group-cta-row { order: 34; }
      .page > #stats-widget { order: 35; }
      .page > #continue-widget { order: 45; }
      .page > .more-links { order: 50; }
      /* The issue control is "a thing you need a few times a semester, not a
         place you go": on phones it is a small centred pill again, not the most
         saturated full-width bar above the fold. */
      .page > #question-issue-row { margin: 14px 0 4px; }
      .page > #question-issue-row .question-issue-pill {
        display: inline-flex; width: auto;
        padding: 7px 14px; font-size: 13px; gap: 6px;
        /* card ground, not the amber wash: the accent is solved for 4.60:1 on
           the card, and the wash dropped it to 4.17:1 */
        background: var(--card);
      }
      .page > #question-issue-row .question-issue-pill svg { width: 14px; height: 14px; }

      .mobile-widgets-row #week-widget,
      .mobile-widgets-row #continue-widget {
        flex: 1 1 0;
        min-width: 0;
        margin: 0;
        padding: 10px;
      }
      .mobile-widgets-row .week-widget-top { flex-wrap: wrap; margin-bottom: 6px; }
      .mobile-widgets-row .week-title {
        font-size: 9.5px;
        letter-spacing: .02em;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      .mobile-widgets-row .week-range { font-size: 9.5px; }
      .mobile-widgets-row .week-all-link { font-size: 10.5px; margin-top: 7px; }
      .mobile-widgets-row .week-list { gap: 4px; }
      .mobile-widgets-row .week-item { padding: 5px 7px; gap: 5px; border-radius: 7px; }
      .mobile-widgets-row .week-day { width: 27px; font-size: 8.5px; padding: 2px 0; border-radius: 5px; }
      .mobile-widgets-row .week-item-title { font-size: 10.5px; line-height: 1.25; }
      .mobile-widgets-row .week-empty { font-size: 10.5px; }
      .mobile-widgets-row #continue-widget.hidden { display: none; }

      .mobile-widgets-row .continue-title {
        font-size: 9.5px;
        letter-spacing: .02em;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        margin-bottom: 6px;
      }
      .mobile-widgets-row .continue-list { gap: 4px; }
      .mobile-widgets-row .continue-item { padding: 6px 8px; border-radius: 7px; }
      .mobile-widgets-row .ci-title {
        font-size: 10.5px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      .mobile-widgets-row .ci-meta {
        font-size: 9px;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }
      .mobile-widgets-row .ci-bar { height: 3px; margin-top: 4px; }
    }

    .brand-mark {
      width: 380px;
      max-width: 85%;
      margin: 0 auto -34px;
    }
    /* Hover (2026-07-28). Two earlier tries were both wrong for opposite
       reasons: a rotate+scale moved the logo without doing anything WITH it,
       and re-running the full draw was too much movement for something you
       pass over constantly. The load draw is the moment worth having; hover
       just needs to acknowledge the pointer. So the mark warms very slightly
       and lifts off the page with a soft accent-tinted glow -- no geometry
       changes, nothing redraws, nothing moves. */
    .brand-mark { transition: filter .32s var(--ease); }
    .brand-mark:hover {
      filter: saturate(1.14)
              drop-shadow(0 3px 14px color-mix(in oklab, var(--accent) 26%, transparent));
    }
    @media (prefers-reduced-motion: reduce) {
      .brand-mark { transition: none; }
      .brand-mark:hover { filter: none; }
    }

    .brand-mark svg { width: 100%; height: auto; display: block; }

    /* Compact mobile header: the full-height logo + title + two-line
       subtitle pushed everything useful below the fold on narrow screens.
       .brand-mark/.brand-bars are hidden entirely on mobile -- an earlier
       version replaced them with a small inline wordmark inside header, but
       that duplicated the H1 right below it ("Class of 2028" vs "Class of
       2028 Review") and was itself still one more row of clutter at the
       very top, so it was cut rather than shrunk further. This override
       lives in the later @media(max-width:640px) block below (not here) --
       .brand-bars's own unconditional `display:flex` comes after this
       point in the cascade and would otherwise win by source order despite
       equal specificity. */

    /* Restored 2026-07-27 at Jaxon's request, now with a reveal: the centre
       dot lands first, then the two rules draw outward from it. */
    .brand-bars {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: 8px;
      margin: 0 auto 20px;
    }
    .brand-bars span {
      display: block;
      height: 3px;
      background: var(--ink);
      border-radius: 2px;
    }
    .brand-bars span:nth-child(1) { width: 100px; transform-origin: right center; }
    .brand-bars span:nth-child(2) { width: 28px; }
    .brand-bars span:nth-child(3) { width: 100px; transform-origin: left center; }

    /* The dot drops in first... */
    @keyframes mark-dot-in {
      from { transform: scaleX(.14); opacity: 0; }
      55%  { opacity: 1; }
      to   { transform: none; opacity: 1; }
    }
    /* ...then each rule draws outward from it. */
    @keyframes mark-rule-out {
      from { transform: scaleX(0); }
      to   { transform: scaleX(1); }
    }
    .brand-bars span:nth-child(2) {
      animation: mark-dot-in .46s var(--ease-spring) both;
    }
    .brand-bars span:nth-child(1),
    .brand-bars span:nth-child(3) {
      animation: mark-rule-out .58s var(--ease) .40s both;
    }
    @media (prefers-reduced-motion: reduce) {
      .brand-bars span { animation: none !important; transform: none !important; opacity: 1 !important; }
    }

    @media (max-width: 640px) {
      body { padding: 32px 16px; }
      .tabs { flex-wrap: wrap; }
      .tab-btn { flex: 1 1 auto; min-width: 45%; }
      .card { padding: 16px; }
    }

    @media (min-width: 900px) {
      .brand-mark { width: 560px; }
    }

    header { margin-bottom: 20px; text-align: center; }
    h1 {
      margin: 0 0 6px;
      font-size: 28px;
      font-weight: 700;
      letter-spacing: -0.02em;
    }
    .subtitle { color: var(--muted); font-size: 14px; margin: 0; }
    .subtitle + .subtitle { margin-top: 4px; }
    /* The tagline sits straight on the drifting ground gradient, whose
       darkest stop (#eef1f6) put --muted #6b7280 at 4.23-4.25:1 in light mode.
       #4b5563 is the site's existing stronger muted grey (the guide back
       link's): 6.6:1 measured there. Dark mode keeps --muted. */
    :root[data-theme="light"] body.homepage .subtitle { color: #4b5563; }
    /* Same grey for the other small muted text that sits on a tint rather
       than on white: inactive class tabs on the warm ground (4.48:1) and the
       semester card's progress label and disclaimer on its blue wash
       (4.41:1). */
    :root[data-theme="light"] body.homepage .tab-btn:not(.active):not(:hover),
    :root[data-theme="light"] body.homepage .semester-progress-label,
    :root[data-theme="light"] body.homepage .ai-disclaimer { color: #4b5563; }

    .search-wrap { position: relative; margin: 18px 0; display: flex; gap: 10px; }
    .search-input-box { position: relative; flex: 1; min-width: 0; }
    .search-input-box svg {
      position: absolute; left: 14px; top: 50%; transform: translateY(-50%);
      width: 16px; height: 16px; color: var(--muted); pointer-events: none;
    }
    .site-search-input {
      width: 100%;
      font: inherit;
      font-size: 15px;
      color: var(--ink);
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 12px;
      padding: 12px 16px 12px 40px;
      box-shadow: var(--shadow);
    }
    .site-search-input:focus {
      outline: none;
      border-color: var(--accent);
    }
    .guides-btn {
      flex: none;
      display: flex;
      align-items: center;
      gap: 8px;
      font: inherit;
      font-size: 14px;
      font-weight: 700;
      color: var(--ink);
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 12px;
      padding: 12px 16px;
      box-shadow: var(--shadow);
      text-decoration: none;
      white-space: nowrap;
    }
    .guides-btn-icon { width: 17px; height: 17px; flex: none; transition: transform .25s var(--ease-spring); }
    .guides-btn:hover { border-color: var(--accent); color: var(--accent); }
    .guides-btn:hover .guides-btn-icon { transform: rotate(-10deg) scale(1.15); }

    /* Homepage nav-button hover flair (added 2026-07-18). Guides' book icon
       opens on hover (two stacked SVGs crossfaded, see .guides-icon-wrap
       below -- replaced an earlier falling-letters version that read as an
       illegible smear rather than a clean effect). Arcade's burst and
       Group's converging dots are short-lived position:fixed divs spawned
       by the script near the end of this file on mouseenter --
       .btn-burst-piece and .btn-avatar-piece below just define their shared
       look/animation. */
    .guides-icon-wrap { position: relative; width: 17px; height: 17px; flex: none; }
    .guides-icon-wrap .guides-btn-icon {
      position: absolute; inset: 0; width: 17px; height: 17px;
      transition: opacity .3s, transform .3s var(--ease-spring);
    }
    .guides-icon-wrap .icon-open { opacity: 0; transform: scale(.7); }
    .guides-btn:hover .guides-icon-wrap .icon-closed { opacity: 0; transform: scale(.7) rotate(8deg); }
    .guides-btn:hover .guides-icon-wrap .icon-open { opacity: 1; transform: scale(1.15); }
    .btn-burst-piece {
      position: fixed; z-index: 9999; pointer-events: none; border-radius: 50%;
      animation: btn-burst-fly .7s cubic-bezier(.2,.8,.3,1) forwards;
    }
    @keyframes btn-burst-fly {
      to { transform: translate(var(--bx), var(--by)) scale(.3); opacity: 0; }
    }
    .btn-avatar-piece {
      position: fixed; z-index: 9999; pointer-events: none; border-radius: 50%;
      animation: btn-avatar-join .6s var(--ease) forwards;
    }
    @keyframes btn-avatar-join {
      0% { transform: translate(var(--sx), var(--sy)) scale(.4); opacity: 0; }
      35% { opacity: 1; }
      100% { transform: translate(0, 0) scale(1); opacity: 0; }
    }
    @media (prefers-reduced-motion: reduce) {
      .guides-icon-wrap .guides-btn-icon { transition: none; }
    }
    @media (max-width: 640px) {
      .search-wrap { flex-wrap: wrap; gap: 8px; }
      .search-input-box { flex: 1 1 100%; }
      /* 2-up icon cards instead of two identical full-width stacked bars --
         each button gets its own colored icon chip so "pick one of these
         two things" reads at a glance instead of parsing two matching rows.
         Icon sits beside the label (not stacked above it) so the card only
         needs to be as tall as one line of text, not icon+text stacked. */
      .guides-btn {
        flex: 1 1 calc(50% - 4px);
        align-items: center;
        gap: 8px;
        padding: 10px 12px;
        white-space: normal;
      }
      .guides-btn-icon {
        width: 26px; height: 26px; padding: 6px; border-radius: 8px;
        background: var(--accent-soft); color: var(--accent); box-sizing: border-box;
        flex-shrink: 0;
      }
      .guides-btn.group-btn .guides-btn-icon { background: var(--accent2-soft); color: var(--accent2); }
      .guides-btn.arcade-btn .guides-btn-icon { background: var(--accent3-soft); color: var(--accent3); }
      /* .guides-icon-wrap .guides-btn-icon (2 classes) outranks the plain
         .guides-btn-icon rule above by specificity regardless of media
         query, so the 26px mobile chip sizing needs restating here at equal
         specificity to actually win via source order -- same trap already
         documented elsewhere in this file for the tab-overflow fix. */
      .guides-icon-wrap { width: 26px; height: 26px; flex-shrink: 0; }
      .guides-icon-wrap .guides-btn-icon {
        width: 26px; height: 26px; padding: 6px; border-radius: 8px;
        background: var(--accent-soft); color: var(--accent); box-sizing: border-box;
      }
      /* Arcade and Guides pair up on one row. `.wide-row` used to give Guides
         a full-width row of its own while Arcade + Group Study shared the row
         above -- but Group Study moved to the left rail, leaving Arcade alone
         on a row it grew to fill, so both buttons rendered as identical
         stacked bars (the exact thing the 2-up cards above exist to avoid).
         Both are flex: 1 1 calc(50% - 4px) now; .search-wrap stretches them to
         equal height when the longer label wraps to two lines. */
      .guides-btn span { font-size: 13px; line-height: 1.25; }
    }
    .search-results {
      position: absolute;
      top: calc(100% + 8px);
      left: 0;
      right: 0;
      z-index: 20;
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 12px;
      box-shadow: var(--shadow);
      max-height: 420px;
      overflow-y: auto;
      padding: 6px;
    }
    .search-results.hidden { display: none; }
    .search-result {
      display: block;
      padding: 10px 12px;
      border-radius: 8px;
      text-decoration: none;
      color: var(--ink);
    }
    .search-result:hover, .search-result.active-result {
      background: var(--accent-soft);
    }
    .search-result .sr-title { font-size: 14.5px; font-weight: 600; }
    .search-result .sr-path { font-size: 12px; color: var(--muted); margin-top: 2px; }
    .search-no-results { padding: 14px 12px; font-size: 14px; color: var(--muted); text-align: center; }


    /* Today's study ring (2026-09-28). A rail card above the week widget on desktop,
       a full-width row under the header on phones, and ordinary flow in between. */
    .planner-widget { display: block; margin: 18px 0; text-decoration: none; color: var(--ink); transition: border-color .15s, transform .15s; }
    .planner-widget:hover { border-color: var(--a-teal); }
    .pw-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 0 0 10px; }
    .pw-title { font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0; }
    .pw-go { font-size: 12.5px; font-weight: 700; color: var(--a-teal); }
    .planner-widget:hover .pw-go { text-decoration: underline; }
    .pw-body { display: flex; align-items: center; gap: 14px; }
    .pw-ring { position: relative; width: 68px; height: 68px; flex: none; }
    .pw-ring svg { width: 100%; height: 100%; transform: rotate(-90deg); }
    .pw-ring circle { fill: none; stroke-width: 9; stroke-linecap: round; }
    .pw-track { stroke: rgba(127,127,127,.2); }
    .pw-fill { stroke: var(--a-teal); stroke-dasharray: 263.9; stroke-dashoffset: 263.9; transition: stroke-dashoffset .8s cubic-bezier(.2,.8,.2,1), stroke .3s; }
    .planner-widget.is-done .pw-fill { stroke: var(--a-green); }
    .pw-pct { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; font-size: 17px; font-weight: 800; color: var(--ink); }
    .pw-pct small { font-size: 10px; font-weight: 700; opacity: .65; margin-left: 1px; }
    .pw-text { min-width: 0; }
    .pw-main { font-size: 15px; font-weight: 700; color: var(--ink); line-height: 1.25; }
    .pw-sub { font-size: 12.5px; color: var(--muted); margin-top: 2px; line-height: 1.35; }
    .pw-streak { display: inline-flex; align-items: center; gap: 4px; margin-top: 5px; font-size: 12px; font-weight: 700; color: var(--a-amber-hover, var(--a-amber)); }
    .pw-streak svg { width: 13px; height: 13px; }
    @media (prefers-reduced-motion: reduce) { .pw-fill, .planner-widget { transition: none; } }
    @media (min-width: 1220px) {
      .planner-widget { position: absolute; left: calc(100% + 24px); width: 260px; margin: 0; box-sizing: border-box; }
    }
    @media (max-width: 640px) {
      .page > #planner-widget { order: 19; flex: 1 1 100%; margin: 14px 0 0; padding: 12px 14px; box-sizing: border-box; }
      .page > #planner-widget .pw-ring { width: 56px; height: 56px; }
      .page > #planner-widget .pw-top { margin-bottom: 6px; }
    }

    .week-widget { margin: 18px 0; }
    .week-widget-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 0 0 12px; flex-wrap: wrap; }
    .week-title {
      font-size: 13px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: .04em;
      color: var(--muted);
      margin: 0;
    }
    .week-range { font-size: 12.5px; color: var(--muted); }
    /* The widget only ever shows five days; this is the way out to the term. */
    .week-all-link {
      display: inline-block; margin-top: 10px;
      font-size: 12.5px; font-weight: 700; color: var(--accent); text-decoration: none;
    }
    .week-all-link:hover { text-decoration: underline; }
    .week-list { display: flex; flex-direction: column; gap: 8px; }
    .week-item {
      display: flex;
      align-items: center;
      gap: 12px;
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 12px;
      padding: 11px 14px;
    }
    .week-day {
      flex: none;
      width: 40px;
      text-align: center;
      font-size: 11.5px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: .03em;
      color: var(--accent);
      background: var(--accent-soft);
      border-radius: 8px;
      padding: 5px 0;
    }
    .week-item-title { font-size: 14px; color: var(--ink); }
    .week-item.done { opacity: .55; }
    .week-item.done .week-item-title { text-decoration: line-through; }
    .week-empty { font-size: 14px; color: var(--muted); padding: 4px 2px; }

    .continue-widget { margin: 18px 0; }
    /* Cumulative counter + My Progress share their own card (#stats-widget),
       sitting under Group Study -- left sidebar on desktop, under the buttons
       on tablet/phone. Placed BEFORE the >=1180px block below so that block's
       `margin: 0` override wins on desktop by source order, same as the
       week/continue widgets (media queries don't add specificity). */
    .stats-widget {
      margin: 18px 0;
      display: flex; flex-direction: column; align-items: center; gap: 11px;
      text-align: center;
    }
    /* Relax is its own standalone element under the stats card (base margin here,
       before the >=1180px block, so that block's `margin: 0` wins on desktop). */
    .relax-cta-row, .group-cta-row { text-align: center; margin: 14px 0 0; }
    .guides-btn-floating { display: none; }

    /* Report an exam question. A pill, not one of the white rail cards -- it is
       something you need a few times a semester, so it should be findable
       without competing with Relax/Group Study/Arcade for attention. Amber
       because it is a flag being raised, and amber is already the site's
       "attention, not alarm" hue; crimson would read as an error on the page
       itself. */
    .question-issue-row { text-align: center; margin: 14px 0 0; }
    /* Sized to the search input and the Guides button it shares a line with --
       full width of its slot, same height. It was a small centred pill before,
       which read as a stray tag floating next to a full-width row. Still a
       pill in shape; only the footprint changed. */
    .question-issue-pill {
      display: flex; align-items: center; justify-content: center; gap: 8px;
      width: 100%; box-sizing: border-box;
      font-family: inherit; font-size: 14px; font-weight: 700; cursor: pointer;
      padding: 12px 16px; border-radius: var(--r-pill, 999px);
      color: var(--a-amber); background: var(--a-amber-soft);
      border: 1px solid color-mix(in oklab, var(--a-amber) 26%, transparent);
      transition: background .15s ease, border-color .15s ease, transform .15s ease;
    }
    .question-issue-pill svg { width: 16px; height: 16px; flex: 0 0 auto; }
    .question-issue-pill:hover {
      background: color-mix(in oklab, var(--a-amber) 14%, var(--card));
      border-color: var(--a-amber);
      transform: translateY(-1px);
    }
    .question-issue-pill:active { transform: translateY(0); }
    @media (prefers-reduced-motion: reduce) {
      .question-issue-pill, .question-issue-pill:hover { transition: none; transform: none; }
    }
    /* Arcade lives inline beside the search box until the rail exists. */
    .arcade-cta-row { display: none; }

    /* Must come after both base `margin: 18px 0`/`display: none` rules above
       so it wins the cascade at this breakpoint (media queries don't add
       specificity -- plain source order decides ties, and these selectors
       are otherwise equally specific). */
    @media (min-width: 1220px) {
      .continue-widget {
        position: absolute;
        top: 0;
        right: calc(100% + 24px);
        width: 260px;
        margin: 0;
      }
      /* Left sidebar stack under the Group Study button: #stats-widget sits at
         the semester-card top line (like the other widgets), and #continue-widget
         is pushed below it -- exact tops are set by alignSidebars() since the
         stats card's height is dynamic. */
      .stats-widget {
        position: absolute;
        right: calc(100% + 24px);
        width: 260px;
        margin: 0;
      }
      /* The 260px rail is the only place the caption doesn't fit at base size,
         so shrink it here just enough to hold one line (~208px in a ~210px
         content box). */
      .stats-widget .cc-label { font-size: 10.5px; letter-spacing: -.2px; }
      /* Left rail (top -> bottom): #relax-cta-row -> #group-cta-row ->
         #arcade-cta-row -> stats card -> #continue-widget. Exact tops set by
         alignSidebars() (dynamic heights). */
      .arcade-cta-row { display: block; }
      .relax-cta-row, .group-cta-row, .arcade-cta-row, .question-issue-row {
        position: absolute;
        right: calc(100% + 24px);
        width: 260px;
        margin: 0;
      }
      .week-widget {
        position: absolute;
        top: 0;
        left: calc(100% + 24px);
        width: 260px;
        margin: 0;
      }
      .guides-btn-floating {
        display: flex;
        justify-content: center;
        position: absolute;
        top: 0;
        left: calc(100% + 24px);
        width: 260px;
      }
      .group-btn-floating {
        display: flex;
        justify-content: center;
        position: absolute;
        left: auto;
        right: calc(100% + 24px);
        width: 260px;
      }
      /* Every nav button has a rail counterpart at this width (Arcade gained
         one 2026-07-27), so the search input gets the whole row to itself --
         .search-input-box is already flex:1, so it simply stretches. */
      .search-wrap .guides-btn, .search-wrap .group-btn { display: none; }
    }
    .continue-widget.hidden { display: none; }
    .continue-title {
      font-size: 13px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: .04em;
      color: var(--muted);
      margin: 0 0 10px;
    }
    .continue-list { display: flex; flex-direction: column; gap: 8px; }
    .continue-item {
      display: block;
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 12px;
      box-shadow: var(--shadow);
      padding: 12px 16px;
      text-decoration: none;
      color: var(--ink);
    }
    .continue-item:hover { border-color: var(--accent); }
    .ci-top { display: flex; flex-direction: column; gap: 3px; }
    .ci-title { font-size: 14.5px; font-weight: 600; }
    .ci-meta { font-size: 12.5px; color: var(--muted); }
    .ci-bar { height: 5px; background: var(--bg); border-radius: 99px; overflow: hidden; margin-top: 8px; }
    .ci-bar > i { display: block; height: 100%; background: var(--accent); }

    @media (max-width: 640px) {
      .continue-widget { margin: 12px 0; }
      .continue-title { font-size: 10.5px; margin-bottom: 6px; }
      .continue-list { gap: 5px; }
      .continue-item { padding: 7px 10px; border-radius: 9px; }
      .ci-top { gap: 1px; }
      .ci-title { font-size: 12.5px; }
      .ci-meta { font-size: 10.5px; }
      .ci-bar { height: 3px; margin-top: 5px; }
    }

    .card {
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 16px;
      box-shadow: var(--shadow);
      padding: 24px;
    }

    .semester + .semester { margin-top: 32px; padding-top: 28px; border-top: 1px solid var(--border); }
    .semester-header {
      margin: 0 0 16px;
      font-family: var(--font-display);
      font-size: 21px;
      font-weight: 700;
      letter-spacing: -0.018em;
      color: var(--ink);
      text-align: center;
    }

    /* Semester progress bar -- same thin track/gradient-fill visual language
       as every quiz's own question-progress .track/#bar (see e.g.
       "Anatomy Exam 4/appendicular-skeleton-quiz.html"'s .track{height:6px}
       rule), just using the homepage's own --accent/--accent3 instead of a
       quiz's per-topic accent pair. Filled in by JS from the dates in
       semesters.js (the SEMESTER_START/SEMESTER_END constants this used to
       point at are gone) -- starts at width:0 so the transition below animates
       it filling in on page load. */
    .semester-progress { margin: 0 0 16px; }
    .semester-progress-track {
      height: 8px;
      background: var(--border);
      border-radius: 4px;
      overflow: hidden;
    }
    /* Selector is the CLASS, not the id. The bar was built when there was one
       semester and one #semester-progress-fill; the registry then generalised
       the markup to four .semester blocks, each with its own fill, but an id
       can only be on one element. Only the Summer I block kept the id, so from
       the moment Fall became the current term the visible bar had no CSS at
       all -- display:inline, no height, no background -- and rendered as
       nothing while its width was being set correctly all along. The element
       that still carries the id also carries the class, so this covers both. */
    .semester-progress-fill {
      display: block;
      height: 100%;
      width: 0%;
      background: linear-gradient(90deg, var(--accent), var(--accent3));
      border-radius: 4px;
      transition: width 0.8s var(--ease);
    }
    .semester-progress-label {
      margin-top: 6px;
      font-size: 12.5px;
      color: var(--muted);
      text-align: center;
    }
    @media (prefers-reduced-motion: reduce) {
      .semester-progress-fill { transition: none; }
    }

    /* Class-wide cumulative "questions answered" counter (class-stats.js).
       Hidden until the live value loads so it never flashes a stale 0. */
    .class-counter {
      display: flex; align-items: center; justify-content: center;
      flex-wrap: wrap; gap: 3px 7px; margin: 0;
      font-size: 13px; color: var(--muted, #6b7280); line-height: 1.35;
      opacity: 0; transition: opacity .5s var(--ease);
    }
    .class-counter.ready { opacity: 1; }
    .class-counter .cc-icon { display: inline-flex; align-self: center; }
    .class-counter .cc-icon svg { width: 18px; height: 18px; display: block; }
    .class-counter .cc-num {
      font-weight: 800; font-size: 20px; letter-spacing: -.01em;
      color: var(--accent, #2a6df2); font-variant-numeric: tabular-nums;
    }
    /* Keep the caption on a single line -- it fits at the base size on phone
       and tablet; the narrow 260px desktop sidebar shrinks it just enough
       (see the >=1180px block) rather than letting it wrap to two lines. */
    .class-counter .cc-label { white-space: nowrap; }
    /* Odometer digits (class-stats.js animateTo): each digit is a column of 0-9 that slides to its value. */
    .class-counter .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
    .class-counter .odo { display: inline-flex; align-items: flex-end; }
    .class-counter .od { display: inline-block; height: 1.18em; line-height: 1.18; overflow: hidden; }
    .class-counter .odr { display: flex; flex-direction: column; transform: translateY(calc(var(--d, 0) * -1.18em)); transition: transform 1.05s cubic-bezier(.16,.84,.26,1); }
    .class-counter .odr i { font-style: normal; height: 1.18em; line-height: 1.18; }
    .class-counter .osep { line-height: 1.18; }
    .class-counter.cc-bump .cc-num { animation: cc-bump .55s ease-out 1; }
    @keyframes cc-bump { 0% { transform: scale(1); } 30% { transform: scale(1.14); } 100% { transform: none; } }
    @media (prefers-reduced-motion: reduce) { .class-counter .odr { transition: none; } .class-counter.cc-bump .cc-num { animation: none; } }
    .my-progress-link {
      display: inline-flex; align-items: center; gap: 7px;
      background: var(--accent-soft, #eef0fd); color: var(--accent, #2a6df2);
      border: 1px solid var(--border, #e5e7ef); border-radius: 999px;
      padding: 8px 15px 8px 13px; font-size: 13.5px; font-weight: 700; text-decoration: none;
      transition: border-color .15s, transform .15s, box-shadow .15s;
    }
    .my-progress-link svg { width: 15px; height: 15px; flex: none; opacity: .95; }
    /* Two pills share the row when there is something to review; the Review
       one carries the Review page's own accent so the colours agree with
       where the link goes. */
    .stat-links { display: flex; flex-wrap: wrap; gap: 8px; justify-content: center; }
    .review-link { background: var(--a-crimson-soft, #faf2f2); color: var(--a-crimson, #df2a44); }
    .review-link:hover { border-color: var(--a-crimson, #df2a44); box-shadow: 0 4px 12px rgba(223,42,68,.16); }
    .review-link b { font-weight: 800; }
    .review-link svg { transition: transform .45s var(--ease-spring, ease); }
    .review-link:hover svg { transform: rotate(-160deg); }
    @media (prefers-reduced-motion: reduce) { .review-link svg, .review-link:hover svg { transition: none; transform: none; } }
    /* Green, matching the Cram Sheet badge on guides.html -- a cram sheet is
       green everywhere else on the site, so it should be green here too. */
    .mycram-link { background: var(--a-green-soft, #e7fbea); color: var(--a-green, #218640); }
    .mycram-link:hover { border-color: var(--a-green, #218640); box-shadow: 0 4px 12px rgba(33,134,64,.16); }
    .my-progress-link:hover { border-color: var(--accent, #2a6df2); transform: translateY(-1px); box-shadow: 0 4px 12px rgba(37,99,235,.14); }
    /* Playful icon hovers -------------------------------------------------- */
    /* Cap tips its hat when you hover the counter */
    .class-counter .cc-icon svg { transition: transform .35s var(--ease-spring); transform-origin: 58% 68%; }
    .class-counter:hover .cc-icon svg { transform: rotate(-15deg); }
    /* My Progress: the trend line draws upward on hover */
    .my-progress-link .mp-line { stroke-dasharray: 1; stroke-dashoffset: 0; }
    .my-progress-link:hover .mp-line { animation: mp-draw .75s ease forwards; }
    @keyframes mp-draw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }

    /* Relax: standalone calming button (its own element under the stats card) */
    .relax-cta {
      position: relative; overflow: hidden;
      display: flex; width: 100%; box-sizing: border-box;
      align-items: center; justify-content: center; gap: 9px;
      background: var(--card, #fff); color: var(--accent, #2a6df2);
      border: 1px solid var(--border, #e5e7ef); border-radius: 14px;
      padding: 13px 18px; font-size: 14.5px; font-weight: 700; text-decoration: none;
      box-shadow: var(--shadow);
      transition: border-color .15s, transform .15s, box-shadow .15s;
    }
    .relax-cta svg:not(.vines):not(.arc-inv):not(.arc-pm):not(.crowd) { width: 18px; height: 18px; flex: none; }
    .relax-cta svg.leaf-icon { transition: transform .4s var(--ease-spring); }
    /* Vines that grow onto the Relax widget on hover (rebuilt 2026-09-30, Jaxon: "make it look like vines").
       A sinuous stem draws in from each of two corners, curling tendrils follow it, and lance-shaped
       leaves unfurl one after another from where they join the stem; once grown the vine sways very
       slightly. Plain transitions, so moving the pointer away retracts it instead of snapping.
       Keyboard focus grows them too. The bottom-right vine is the same drawing turned half a turn. */
    .relax-cta > svg.leaf-icon, .relax-cta > span { position: relative; z-index: 1; }
    .relax-cta .vines { position: absolute; width: 84px; height: 42px; color: var(--accent, #2a6df2); opacity: .62; pointer-events: none; z-index: 0; overflow: visible; }
    .relax-cta .vines-tl { top: -2px; left: -3px; }
    .relax-cta .vines-br { bottom: -2px; right: -3px; transform: rotate(180deg); }
    .relax-cta .vsway { transform-box: view-box; transform-origin: 0 0; }
    .relax-cta .vstem, .relax-cta .vtend { stroke-dasharray: 1; stroke-dashoffset: 1; transition: stroke-dashoffset .32s ease; }
    .relax-cta .vgrow { transform: scale(0) rotate(-24deg); transform-box: fill-box; transform-origin: 0% 50%; transition: transform .28s ease; }
    .relax-cta .vl { fill: currentColor; fill-opacity: .34; stroke: currentColor; stroke-width: .8; stroke-linejoin: round; }
    .relax-cta .vrib { stroke: currentColor; stroke-width: .6; opacity: .75; stroke-linecap: round; }
    .relax-cta:hover .vstem, .relax-cta:focus-visible .vstem { stroke-dashoffset: 0; transition: stroke-dashoffset 1s cubic-bezier(.45,.05,.25,1); }
    .relax-cta:hover .vtend, .relax-cta:focus-visible .vtend { stroke-dashoffset: 0; transition: stroke-dashoffset .7s ease .55s; }
    .relax-cta:hover .vtend2, .relax-cta:focus-visible .vtend2 { transition-delay: .85s; }
    .relax-cta:hover .vgrow, .relax-cta:focus-visible .vgrow { transform: scale(1) rotate(0); transition: transform .55s cubic-bezier(.3,1.5,.5,1) var(--d, .5s); }
    .relax-cta:hover .vsway, .relax-cta:focus-visible .vsway { animation: vine-sway 3.6s ease-in-out 1.6s infinite; }
    @keyframes vine-sway { 0%, 100% { transform: rotate(0); } 50% { transform: rotate(1.8deg); } }
    @media (prefers-reduced-motion: reduce) {
      .relax-cta .vstem, .relax-cta .vtend, .relax-cta .vgrow { transition: none !important; }
      .relax-cta:hover .vsway, .relax-cta:focus-visible .vsway { animation: none; }
      .relax-cta:not(.group-cta):not(.arcade-cta):hover { animation: none; box-shadow: 0 8px 22px rgba(37,99,235,.3); }
    }
    .relax-cta .leaf-blade { fill: rgba(37,99,235,.15); transition: fill .5s var(--ease); }
    .relax-cta:hover { border-color: var(--accent, #2a6df2); transform: translateY(-1px); box-shadow: 0 6px 16px rgba(37,99,235,.16); }
    /* Relax widget only: a blue aura that gently pulses/exudes on hover */
    .relax-cta:not(.group-cta):not(.arcade-cta):hover { animation: relax-aura 2s ease-in-out infinite; }
    @keyframes relax-aura {
      0%, 100% { box-shadow: 0 6px 16px rgba(37,99,235,.18), 0 0 12px rgba(37,99,235,.16); }
      50% { box-shadow: 0 9px 26px rgba(37,99,235,.32), 0 0 34px rgba(37,99,235,.42); }
    }
    .relax-cta:hover svg.leaf-icon { transform: scale(1.1) rotate(-4deg); }
    /* Words in black like the other widgets; the icon keeps the accent color */
    .relax-cta > span { color: var(--ink, #1f2937); }
    /* Group Study widget: icon springs on hover (its own effect, since it has no leaf/branches) */
    .group-cta svg { transition: transform .3s var(--ease-spring); }
    .group-cta:hover svg { transform: scale(1.14); }
    /* Arcade widget: the two stacked cabinet screens spread apart on hover,
       like a deck of cards being fanned -- its own effect, matching the
       colour burst the JS already fires from this button. */
    .arcade-cta .cab-back, .arcade-cta .cab-front {
      transition: transform .32s var(--ease-spring);
      transform-box: fill-box; transform-origin: center;
    }
    .arcade-cta:hover .cab-back { transform: translate(-1.6px, 1.6px); }
    .arcade-cta:hover .cab-front { transform: translate(1.6px, -1.6px); }
    @media (prefers-reduced-motion: reduce) {
      .arcade-cta .cab-back, .arcade-cta .cab-front { transition: none; }
      .arcade-cta:hover .cab-back, .arcade-cta:hover .cab-front { transform: none; }
    }
    .relax-cta:hover .leaf-blade { fill: rgba(37,99,235,.20); }
    /* ---- Arcade widget (2026-09-30, reworked the same day; Jaxon removed the coin) ---------------
       On hover the cabinet screen flickers, a pixel invader marches on the left and a chomper
       eats a row of dots on the right. Everything stays in the two outer thirds of the widget,
       clear of the icon and the label, and the old full-page confetti burst now only fires for the
       small inline Arcade button (home.js). */
    .relax-cta > .arc-inv, .relax-cta > .arc-pm, .relax-cta > .crowd { position: absolute; pointer-events: none; z-index: 0; }
    .arcade-cta .arc-inv { left: 16px; top: 14px; width: 33px; height: 24px; fill: var(--accent2, #218640); opacity: 0; transition: opacity .25s; }
    .arcade-cta .arc-inv .f2 { display: none; }
    .arcade-cta .arc-pm { right: 10px; top: 0; width: 84px; height: 52px; opacity: 0; transition: opacity .25s; }
    .arcade-cta:hover .arc-inv, .arcade-cta:focus-visible .arc-inv, .arcade-cta:hover .arc-pm, .arcade-cta:focus-visible .arc-pm { opacity: 1; }
    /* invader: two-frame leg wiggle plus a stepped side-to-side march */
    .arcade-cta:hover .arc-inv, .arcade-cta:focus-visible .arc-inv { animation: arc-march 2.4s steps(6, end) .5s infinite; }
    .arcade-cta:hover .arc-inv .f1, .arcade-cta:focus-visible .arc-inv .f1 { animation: arc-frame1 .6s steps(1, end) .5s infinite; }
    .arcade-cta:hover .arc-inv .f2, .arcade-cta:focus-visible .arc-inv .f2 { display: inline; animation: arc-frame2 .6s steps(1, end) .5s infinite; }
    @keyframes arc-march { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(22px); } }
    @keyframes arc-frame1 { 0%, 100% { opacity: 1; } 50% { opacity: 0; } }
    @keyframes arc-frame2 { 0%, 100% { opacity: 0; } 50% { opacity: 1; } }
    /* chomper: mouth snaps open and shut while it slides left along the dots, then starts over */
    .arcade-cta .arc-pm .chomp { fill: #f2b01e; transform: translateX(74px); }
    .arcade-cta .arc-pm .dot { fill: var(--muted, #6b7280); }
    .arcade-cta .arc-pm .pt, .arcade-cta .arc-pm .pb { transform-box: view-box; transform-origin: 0 0; }
    .arcade-cta:hover .arc-pm .chomp, .arcade-cta:focus-visible .arc-pm .chomp { animation: pm-run 2.6s linear .55s infinite; }
    .arcade-cta:hover .arc-pm .pt, .arcade-cta:focus-visible .arc-pm .pt { animation: pm-top .26s ease-in-out infinite alternate; }
    .arcade-cta:hover .arc-pm .pb, .arcade-cta:focus-visible .arc-pm .pb { animation: pm-bot .26s ease-in-out infinite alternate; }
    .arcade-cta:hover .arc-pm .d1, .arcade-cta:focus-visible .arc-pm .d1 { animation: pm-d1 2.6s linear .55s infinite; }
    .arcade-cta:hover .arc-pm .d2, .arcade-cta:focus-visible .arc-pm .d2 { animation: pm-d2 2.6s linear .55s infinite; }
    .arcade-cta:hover .arc-pm .d3, .arcade-cta:focus-visible .arc-pm .d3 { animation: pm-d3 2.6s linear .55s infinite; }
    @keyframes pm-run { 0% { transform: translateX(78px); } 100% { transform: translateX(14px); } }
    @keyframes pm-top { from { transform: rotate(0); } to { transform: rotate(-30deg); } }
    @keyframes pm-bot { from { transform: rotate(0); } to { transform: rotate(30deg); } }
    @keyframes pm-d1 { 0%, 31% { opacity: 1; } 32%, 99% { opacity: 0; } 100% { opacity: 1; } }
    @keyframes pm-d2 { 0%, 53% { opacity: 1; } 54%, 99% { opacity: 0; } 100% { opacity: 1; } }
    @keyframes pm-d3 { 0%, 75% { opacity: 1; } 76%, 99% { opacity: 0; } 100% { opacity: 1; } }
    .arcade-cta .cab-front { fill: currentColor; fill-opacity: 0; }
    .arcade-cta:hover .cab-front, .arcade-cta:focus-visible .cab-front { animation: arc-flicker 1.6s steps(1, end) .8s infinite; }
    @keyframes arc-flicker { 0%, 100% { fill-opacity: .16; } 12% { fill-opacity: .04; } 24% { fill-opacity: .2; } 40% { fill-opacity: .08; } 58% { fill-opacity: .22; } 74% { fill-opacity: .06; } }

    /* ---- Group Study widget: friends joining (2026-09-30) ------------------------------------
       Three little people hop up from the bottom edge on each side, one after another, then bob
       while you stay there. They drop back out of sight when the pointer leaves. */
    .group-cta .crowd { bottom: 0; width: 64px; height: 34px; overflow: hidden; }
    .group-cta .crowd-l { left: 8px; } .group-cta .crowd-r { right: 8px; }
    .group-cta .av { fill: var(--c); opacity: .78; transform: translateY(26px); transition: transform .28s ease, opacity .28s; }
    .group-cta .av circle, .group-cta .av path { stroke: var(--card, #fff); stroke-width: 1.4; }
    .group-cta:hover .av, .group-cta:focus-visible .av { transform: none; animation: av-hop .7s cubic-bezier(.3,1.5,.5,1) calc(var(--i) * .11s) 1 both, av-bob 1.7s ease-in-out calc(var(--i) * .11s + .9s) infinite; }
    @keyframes av-hop { 0% { transform: translateY(26px); } 55% { transform: translateY(-7px); } 78% { transform: translateY(2px); } 100% { transform: none; } }
    @keyframes av-bob { 0%, 100% { transform: none; } 50% { transform: translateY(-3px); } }
    @media (prefers-reduced-motion: reduce) {
      .arcade-cta .arc-inv, .arcade-cta .arc-pm, .group-cta .crowd { display: none; }
      .arcade-cta:hover .cab-front, .arcade-cta:focus-visible .cab-front { animation: none; }
    }
    @media (prefers-reduced-motion: reduce) {
      .class-counter:hover .cc-icon svg, .relax-cta:hover svg.leaf-icon { transform: none; }
      .my-progress-link:hover .mp-line { animation: none; }
      .class-counter .cc-icon svg, .relax-cta svg.leaf-icon { transition: none; }
    }

    .tabs {
      display: flex;
      flex-wrap: wrap;
      gap: 6px;
      background: var(--bg);
      border: 1px solid var(--border);
      border-radius: 10px;
      padding: 4px;
      margin-bottom: 8px;
    }
    .ai-disclaimer {
      text-align: center;
      font-size: 12px;
      color: var(--muted);
      margin: 0 0 20px;
      text-wrap: pretty;
    }
    /* Trust signal, not a waiver (design review item 16): the AI disclosure
       stays, and says what is actually done to keep the material honest. */
    .ai-disclaimer strong { color: var(--ink); font-weight: 700; }
    .ai-disclaimer a.ai-report { color: var(--accent); font-size: inherit; font-weight: 600; text-decoration: underline; text-underline-offset: 2px; }
    .tab-btn {
      /* With 7+ tabs now, cramming them all onto one row (the old
         flex:1;min-width:0 approach) squished each button to an unreadably
         narrow sliver. .tabs now has flex-wrap: wrap (above), so instead
         each tab gets a real target width and just flows onto a second row
         once a row's tabs no longer fit -- e.g. 4 on top, 3 below -- rather
         than shrinking forever. flex-basis 140px is what flex-grow expands
         from to fill leftover row space evenly; flex-shrink still lets a
         tab narrow a bit before wrapping kicks in. */
      flex: 1 1 140px;
      background: none;
      border: none;
      border-radius: 8px;
      padding: 9px 14px;
      min-height: 54px;   /* two lines of 14px/1.25 + padding, so wrapping and non-wrapping tabs match */
      display: inline-flex;
      align-items: center;
      justify-content: center;
      text-align: center;
      line-height: 1.25;
      font-size: 14px;
      font-weight: 600;
      cursor: pointer;
      color: var(--muted);
      box-shadow: 0 0 7px 1px var(--tab-border);
      transition: background 0.15s var(--ease), color 0.15s var(--ease), box-shadow 0.15s var(--ease), transform .2s var(--ease-spring);
    }
    .tab-btn:hover { color: var(--ink); transform: translateY(-2px); }
    .tab-btn:active { transform: scale(0.96); }
    .tab-btn.active {
      background: var(--accent);
      color: #fff;
    }
    .tab-panel { display: none; }
    .tab-panel.active { display: block; }
    .tab-empty { font-size: 14px; color: var(--muted); padding: 20px 4px; }

    .exam-section {
      border: 1px solid var(--border);
      /* Each exam's own colour down the left edge, so a panel of six reads as
         six places rather than six rows. Generated per exam by
         tools/build_exam_spines.py; the fallback keeps sections whose folder
         has no palette (Pharmacodynamics, the Practicums) aligned with the
         rest instead of 2px narrower. */
      border-left: 3px solid var(--exam-spine, var(--border));
      border-radius: var(--r-md, 12px);
      margin-bottom: 10px;
      overflow: hidden;
      background: var(--card);
      transition: border-color var(--dur-1, 160ms) var(--ease, ease),
                  box-shadow var(--dur-1, 160ms) var(--ease, ease);
    }
    .exam-section:hover { border-color: color-mix(in oklab, var(--accent) 32%, var(--border)); }
    /* ...but not the spine, which is the whole point of it. */
    .exam-section:hover { border-left-color: var(--exam-spine, var(--border)); }
    .exam-section[open] { box-shadow: var(--e-1); }
    .exam-section summary {
      list-style: none;
      cursor: pointer;
      padding: 12px 16px;
      font-size: 14px;
      font-weight: 700;
      display: flex;
      align-items: center;
      justify-content: space-between;
      background: var(--bg);
      user-select: none;
      transition: background var(--dur-1, 160ms) var(--ease, ease);
    }
    .exam-section summary:hover { background: var(--accent-soft); }
    .exam-section summary::-webkit-details-marker { display: none; }
    .exam-section summary::after {
      content: "";
      width: 9px; height: 9px;
      flex: none;
      margin-right: 3px;
      border-right: 1.75px solid var(--muted);
      border-bottom: 1.75px solid var(--muted);
      border-radius: 1px;
      transform: rotate(45deg) translate(-2px, -2px);
      transition: transform var(--dur-1, 160ms) var(--ease, ease), border-color var(--dur-1, 160ms) var(--ease, ease);
    }
    .exam-section[open] summary::after {
      transform: rotate(225deg) translate(-2px, -2px);
    }
    .exam-section summary:hover::after { border-color: var(--accent); }
    .exam-section .exam-content { padding: 14px 16px; }

    .topic-section {
      border: none;
      border-top: 1px solid var(--border);
      margin-top: 12px;
      padding-top: 4px;
    }
    .topic-section summary {
      list-style: none;
      cursor: pointer;
      padding: 8px 4px;
      font-size: 13px;
      font-weight: 700;
      color: var(--muted);
      display: flex;
      align-items: center;
      justify-content: space-between;
      user-select: none;
    }
    .topic-section summary::-webkit-details-marker { display: none; }
    .topic-section summary::after {
      content: "▾";
      font-weight: 400;
      transition: transform 0.15s var(--ease);
    }
    .topic-section[open] summary::after { transform: rotate(180deg); }
    .topic-section .exam-content { padding: 4px 4px 6px 14px; }

    ul { list-style: none; margin: 0; padding: 0; }
    li { margin: 0; }
    li + li { margin-top: 6px; }

    a.quiz-link {
      display: flex;
      align-items: center;
      gap: 10px;
      color: var(--ink);
      text-decoration: none;
      font-size: 15px;
      font-weight: 500;
      padding: 12px 14px;
      border-radius: 10px;
      border: 1px solid transparent;
      transition: background 0.15s var(--ease), border-color 0.15s var(--ease), transform 0.15s var(--ease), box-shadow 0.15s var(--ease);
    }
    a.quiz-link::before {
      content: "";
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--accent);
      flex-shrink: 0;
      transition: transform .25s var(--ease-spring);
    }
    /* Quiz tags. "New" is self-expiring -- home.js removes it after 3 days,
       because a New badge that never clears stops carrying information and
       eventually sits on everything. Colours reuse the existing accent tokens
       rather than introducing a hue (see [[site_design_tokens]]). */
    .quiz-tag {
      margin-left: auto;
      flex-shrink: 0;
      font-size: 10.5px;
      font-weight: 700;
      letter-spacing: .04em;
      text-transform: uppercase;
      padding: 3px 7px;
      border-radius: 999px;
      border: 1px solid transparent;
      line-height: 1.4;
    }
    .quiz-tag + .quiz-tag { margin-left: 6px; }
    .quiz-tag--new     { background: var(--accent); color: #fff; }
    .quiz-tag--updated { background: var(--accent-soft); color: var(--accent); border-color: var(--tab-border); }
    /* "Drill" marks the one-fact rapid sets. Violet, because it is the only
       hue in theme.css's map not already spoken for by a tag here (New is the
       page accent, Updated its soft form) -- and it is permanent, unlike New,
       which expires after three days. */
    .quiz-tag--drill   { background: var(--a-violet-soft); color: var(--a-violet);
                         border-color: color-mix(in oklab, var(--a-violet) 26%, transparent); }

    a.quiz-link:hover {
      background: var(--accent-soft);
      border-color: var(--accent);
      transform: translate(2px, -2px);
      box-shadow: var(--shadow-lift);
    }
    a.quiz-link:hover::before { transform: scale(1.6) rotate(-30deg); }
    /* Listening quizzes (audio questions) swap the round bullet for headphones, so the
       one quiz you play by ear reads differently from the rest at a glance. A mask keeps the
       icon on the link's accent colour in light and dark without a second asset. */
    a.quiz-link--listen::before {
      width: 17px; height: 17px; border-radius: 0; background: var(--accent);
      -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 18v-6a9 9 0 0 1 18 0v6'/%3E%3Cpath d='M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z'/%3E%3C/svg%3E") center / contain no-repeat;
      mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 18v-6a9 9 0 0 1 18 0v6'/%3E%3Cpath d='M21 19a2 2 0 0 1-2 2h-1a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3zM3 19a2 2 0 0 0 2 2h1a2 2 0 0 0 2-2v-3a2 2 0 0 0-2-2H3z'/%3E%3C/svg%3E") center / contain no-repeat;
    }
    a.quiz-link--listen:hover::before { transform: scale(1.2); }
    @media (prefers-reduced-motion: reduce) {
      a.quiz-link, a.quiz-link::before { transition: none; }
      a.quiz-link:hover { transform: none; }
    }
    .score-badge {
      margin-left: auto;
      flex-shrink: 0;
      font-size: 12px;
      font-weight: 700;
      color: var(--accent);
      background: var(--accent-soft);
      border: 1px solid var(--accent);
      border-radius: 999px;
      padding: 3px 10px;
      cursor: pointer;
    }
    .score-badge:hover {
      background: var(--accent);
      color: #fff;
    }

    .review-modal-overlay {
      display: none;
      position: fixed;
      inset: 0;
      background: rgba(0,0,0,0.55);
      z-index: 2000;
      align-items: flex-start;
      justify-content: center;
      padding: 40px 16px;
      overflow-y: auto;
    }
    .review-modal-overlay.open { display: flex; }
    .review-modal {
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 16px;
      box-shadow: var(--shadow);
      max-width: 640px;
      width: 100%;
      padding: 26px 24px 28px;
      position: relative;
      color: var(--ink);
    }
    .review-modal-close {
      position: absolute;
      top: 14px;
      right: 14px;
      border: none;
      background: var(--border);
      color: var(--ink);
      width: 30px;
      height: 30px;
      border-radius: 50%;
      font-size: 16px;
      cursor: pointer;
      line-height: 1;
    }
    .review-modal-title {
      font-size: 13px;
      font-weight: 700;
      text-transform: uppercase;
      letter-spacing: .05em;
      color: var(--accent);
      margin: 0 26px 14px 0;
    }
    .review-modal-content { font-size: 14px; }
    .review-modal-content h2, .review-modal-content h3 {
      color: var(--ink); margin: 16px 0 10px; font-size: 15px;
      text-transform: uppercase; letter-spacing: .04em;
    }
    .review-modal-content .scorebig, .review-modal-content .bigscore {
      font-size: 40px; font-weight: 800; color: var(--accent); line-height: 1; margin: 0 0 2px;
    }
    .review-modal-content .pct { font-size: 15px; color: var(--muted); font-weight: 700; margin: 0 0 14px; }
    .review-modal-content .gradeline, .review-modal-content .verdict-line {
      font-size: 14px; margin: 0 0 16px; color: var(--ink);
    }
    .review-modal-content .missed,
    .review-modal-content .missed-item,
    .review-modal-content .miss {
      border: 1px solid var(--border); border-left: 4px solid #dc2626; border-radius: 10px;
      padding: 12px 14px; margin-bottom: 10px; background: var(--bg);
    }
    .review-modal-content .mq { font-weight: 700; margin-bottom: 6px; font-size: 14px; }
    .review-modal-content .row, .review-modal-content .ma { font-size: 13px; margin: 3px 0; }
    .review-modal-content .yours,
    .review-modal-content .row.yours,
    .review-modal-content .ma.yours { color: #dc2626; }
    .review-modal-content .right,
    .review-modal-content .row.right,
    .review-modal-content .ma.right { color: #218640; font-weight: 600; }
    .review-modal-content .why, .review-modal-content .mexp {
      color: var(--muted); font-size: 12.5px; margin-top: 6px;
    }
    .review-modal-content .mcite, .review-modal-content .msrc {
      color: var(--muted); font-size: 11.5px; font-style: italic; margin-top: 6px;
    }
    .review-modal-content .allright, .review-modal-content .perfect {
      background: var(--accent2-soft); border: 1px solid var(--accent2);
      border-radius: 12px; padding: 14px 16px; color: var(--accent2); font-weight: 600;
    }
    .review-modal-content .bd-row { display: flex; align-items: center; gap: 8px; margin: 6px 0; }
    .review-modal-content .bd-name { font-size: 12.5px; color: var(--ink); flex: 0 0 40%; }
    .review-modal-content .bd-bar { flex: 1; height: 8px; background: var(--border); border-radius: 5px; overflow: hidden; }
    .review-modal-content .bd-bar > i { display: block; height: 100%; background: var(--accent); }
    .review-modal-content .bd-frac { font-size: 11.5px; color: var(--muted); flex: 0 0 50px; text-align: right; }
    .review-modal-content .bd {
      display: inline-block; background: var(--accent-soft); border: 1px solid var(--border);
      border-radius: 10px; padding: 6px 10px; font-size: 12px; color: var(--muted);
      font-weight: 600; margin: 3px 4px 3px 0;
    }
    .review-modal-content .breakdown { display: flex; flex-wrap: wrap; gap: 6px; margin: 0 0 8px; }

    .request-link {
      display: block;
      width: 100%;
      text-align: center;
      font: inherit;
      font-size: 13px;
      color: var(--muted);
      background: none;
      border: none;
      text-decoration: underline;
      cursor: pointer;
      margin: 22px 0 0;
      padding: 4px;
    }
    .request-link:hover { color: var(--accent); }

    /* These four were stacked directly on the page as separate links,
       competing for attention with the actual navigation above them --
       real but rare actions, so they're tucked behind one disclosure now
       (closed by default, matching the site's existing accordion
       convention -- see feedback_accordion_default_closed memory). */
    .more-links {
      margin: 24px 0 0;
      border: 1px solid var(--border);
      border-radius: 12px;
      padding: 0 16px;
      background: var(--card);
    }
    .more-links summary {
      list-style: none;
      cursor: pointer;
      padding: 14px 0;
      font-size: 13px;
      font-weight: 700;
      color: var(--muted);
      display: flex;
      align-items: center;
      justify-content: space-between;
      user-select: none;
    }
    .more-links summary::-webkit-details-marker { display: none; }
    .more-links summary svg { width: 14px; height: 14px; flex: none; transition: transform 0.15s var(--ease); }
    .more-links[open] summary svg { transform: rotate(180deg); }
    .more-links .request-link { margin: 4px 0; }
    .more-links .request-link:last-of-type { margin-bottom: 14px; }
    .request-btn {
      font: inherit;
      font-size: 14px;
      font-weight: 600;
      padding: 10px 18px;
      border-radius: 10px;
      border: 1px solid var(--border);
      background: var(--bg);
      color: var(--ink);
      cursor: pointer;
    }
    .request-btn.primary {
      background: var(--accent);
      border-color: var(--accent);
      color: #fff;
    }

    .empty {
      color: var(--muted);
      font-size: 14px;
      font-style: italic;
      padding: 12px 14px;
    }

    .divider {
      display: flex;
      align-items: center;
      margin: 20px 0 12px;
      color: var(--muted);
      font-size: 12px;
      font-weight: 600;
      text-transform: uppercase;
      letter-spacing: 0.06em;
    }
    .divider::before, .divider::after { content: ""; flex: 1; border-top: 1px solid var(--border); }
    .divider span { padding: 0 10px; }

    .install-banner {
      position: fixed;
      left: 16px;
      right: 16px;
      bottom: 16px;
      max-width: 480px;
      margin: 0 auto;
      z-index: 3000;
      background: var(--card);
      border: 1px solid var(--border);
      border-radius: 14px;
      box-shadow: var(--shadow);
      padding: 14px 16px;
      display: flex;
      align-items: flex-start;
      gap: 12px;
      transform: translateY(140%);
      transition: transform .25s var(--ease);
    }
    .install-banner.show { transform: translateY(0); }
    .install-banner-icon {
      flex: 0 0 auto;
      width: 34px;
      height: 34px;
      border-radius: 9px;
      background: var(--accent-soft);
      color: var(--accent);
      display: flex;
      align-items: center;
      justify-content: center;
    }
    .install-banner-body { flex: 1; min-width: 0; }
    .install-banner-title { font-size: 13.5px; font-weight: 700; color: var(--ink); margin: 0 0 3px; }
    .install-banner-text { font-size: 12.5px; color: var(--muted); margin: 0; line-height: 1.4; }
    .install-banner-actions { display: flex; gap: 8px; margin-top: 10px; }
    .install-banner-btn {
      font: inherit;
      font-size: 12.5px;
      font-weight: 700;
      padding: 6px 12px;
      border-radius: 8px;
      border: 1px solid var(--border);
      background: var(--bg);
      color: var(--ink);
      cursor: pointer;
    }
    .install-banner-btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
    .install-banner-close {
      flex: 0 0 auto;
      border: none;
      background: none;
      color: var(--muted);
      cursor: pointer;
      font-size: 15px;
      line-height: 1;
      padding: 2px;
    }

    .new-badge {
      display: inline-block;
      margin-left: 6px;
      padding: 1px 6px;
      border-radius: 999px;
      font-size: 9.5px;
      font-weight: 800;
      letter-spacing: .04em;
      background: var(--accent2-soft);
      color: var(--accent2);
      border: 1px solid var(--accent2);
      vertical-align: middle;
      animation: new-badge-pulse 2.6s ease-in-out infinite;
    }
    @keyframes new-badge-pulse {
      0%, 100% { transform: scale(1); }
      50% { transform: scale(1.06); }
    }
    @media (prefers-reduced-motion: reduce) {
      .new-badge { animation: none; }
    }

    /* Marks a quiz as one of a specific curated set (currently: quizzes from
       "Ricky") -- unlike .new-badge this never expires, since it's an
       identity tag for the quiz's source, not a freshness indicator. Uses
       --accent3/--accent3-soft (theme.css, alongside --accent2) so it's
       correctly themed both by OS preference and by the manual toggle. */
    .r-badge {
      display: inline-block;
      margin-left: 6px;
      padding: 1px 6px;
      border-radius: 999px;
      font-size: 9.5px;
      font-weight: 800;
      letter-spacing: .04em;
      background: var(--accent3-soft);
      color: var(--accent3);
      border: 1px solid var(--accent3);
      vertical-align: middle;
    }

    /* Mobile homepage hierarchy pass -- kept as one block at the very end of
       the stylesheet, after every unconditional rule it needs to win over
       (.brand-bars, header, h1, .subtitle, .card), since a media-query rule
       does NOT out-rank an unconditional same-specificity rule that simply
       comes later in source order -- it only ever applies conditionally,
       the cascade still resolves ties by position. Being last guarantees
       these win at <=640px regardless of where the rest of the file's
       styles get edited later. */
    .h1-logo { display: none; transition: transform .25s var(--ease-spring); }
    .h1-logo:hover { transform: rotate(-6deg) scale(1.08); }
    .h1-divider { display: none; }
    /* The plain-text H1 ("Class of 2028 Review") duplicated the logo right
       above it -- .brand-mark's own SVG already has "Class of 2028" baked
       in (desktop), and the mobile .h1-logo below reuses that exact same
       graphic (paths + the "Class of 2028" <text> element), just smaller.
       Unconditional (not mobile-only): the same duplication exists on
       desktop, where .brand-mark stays fully visible/untouched and only
       the redundant text underneath it goes away. .h1-text stays in the
       DOM (visually hidden, not display:none) at every width so the
       heading keeps a real accessible name for screen readers. */
    .h1-text {
      position: absolute;
      width: 1px; height: 1px;
      padding: 0; margin: -1px;
      overflow: hidden;
      clip: rect(0, 0, 0, 0);
      white-space: nowrap;
      border: 0;
    }
    @media (max-width: 640px) {
      .brand-mark, .brand-bars { display: none; }
      /* header's own top margin does the corner-buttons clearance job the
         old wordmark row used to (indirectly) provide. */
      header { margin: 26px 0 18px; }
      h1 { font-size: 19px; margin-bottom: 0; }
      .subtitle { font-size: 11.5px; }

      /* Mirrors desktop's .brand-mark -> .brand-bars -> header rhythm --
         can't reuse .brand-bars itself since that element sits before
         .page opens (so it can't land between .h1-logo and the subtitles,
         which live inside header), and reusing its class name here would
         also catch it in the `.brand-mark, .brand-bars { display:none }`
         rule two lines up. Same visual recipe, scaled down to match the
         smaller mobile logo. */
      .h1-divider {
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 6px;
        margin: 2px auto 14px;
      }
      .h1-divider span {
        display: block;
        height: 2px;
        background: var(--ink);
        border-radius: 2px;
      }
      .h1-divider span:nth-child(1) { width: 60px; }
      .h1-divider span:nth-child(2) { width: 16px; }
      .h1-divider span:nth-child(3) { width: 60px; }

      /* viewBox is cropped tight to the mark+text's own bounding box
         (getBBox() measured content at y:72-195.86 out of the original
         0-240 box -- nearly half the box was empty padding above/below,
         which is what made the gap to the divider look too big regardless
         of margin tweaks) so height now scales with the visible content,
         not a mostly-empty box. */
      .h1-logo {
        display: block;
        width: 200px;
        height: auto;
        margin: 0 auto;
      }

      /* The subject/exam picker is the page's actual content, not another
         navigation action -- give it a tinted surface distinct from the
         plain --card background every button/widget above it uses, so it
         doesn't read as "one more item in the stack." */
      #semester-card {
        background: var(--accent-soft);
        border-color: var(--tab-border);
      }
      #semester-card .tabs,
      #semester-card .exam-section summary {
        background: var(--card);
      }

      /* This Week: show only the next upcoming exam, not the whole week --
         .week-item.done items are always the earliest in the (date-sorted)
         list, so "the first :not(.done) item" is exactly "what's next."
         Pure CSS (general sibling combinator hides every :not(.done) item
         that has another :not(.done) item before it, i.e. all but the
         first), so it stays correct as days pass with zero JS changes.
         Fallback for the rare moment everything this week is already done
         (normally covered by the whole widget flipping to next week Friday
         5pm first): reveal the last .done item instead of showing nothing. */
      .week-item.done { display: none; }
      .week-item:not(.done) ~ .week-item:not(.done) { display: none; }
      #week-list:not(:has(.week-item:not(.done))) .week-item.done:last-child { display: flex; }

      /* Continue where you left off: show only the single most recent quiz
         instead of up to 5 -- the list itself is now sorted by real last-
         touched recency (see the continue-widget JS), so "first" is
         correct, not arbitrary DOM order. */
      .continue-item ~ .continue-item { display: none; }

      /* .more-links (order: 50, see above) is the last thing in the mobile
         column, which put it right up against the fixed 60px-tall
         .bottom-decor EKG line pinned to the viewport bottom -- especially
         once its <details> is expanded and its own content runs even
         closer to the bottom edge. Clearance here, not on body/.page
         generally, so desktop (which doesn't show .bottom-decor at all)
         is untouched. */
      .page > .more-links { margin-bottom: 56px; }

      /* "Pharmacodynamics" has no space to wrap at like the other three tab
         labels do, so at this width it was overflowing straight past its
         own button's right edge instead of wrapping. A first attempt used
         plain overflow-wrap: break-word, which works but breaks wherever
         the character happens to run out of room ("Pharmacodyna" /
         "mics") -- ugly, and not a real word boundary. The actual fix is
         the &shy; (soft hyphen) placed in the label markup itself
         ("Pharmaco&shy;dynamics"), which forces the break to land at
         "Pharmaco-" / "dynamics" and shows a real hyphen only when a
         break is actually needed there. hyphens: auto (needs
         <html lang="en">, added above) is kept as a belt-and-suspenders
         addition for browsers that hyphenate other long strings on this
         page, and overflow-wrap as a last-resort fallback. This block
         must come after the base .tab-btn rule (which sets padding: 9px
         14px) to actually win -- an earlier attempt placed this override
         before that base rule and it was silently discarded, same trap
         documented elsewhere in this file: a media query doesn't out-rank
         an unconditional rule of equal specificity that simply comes
         later in source order.

         min-width: 45% is re-asserted here for the same reason: the
         earlier @media(max-width:640px) block (near the top of the file)
         also sets it, but the base .tab-btn rule's flex: 1 1 140px comes
         after that first media block in source order and would otherwise
         win at mobile widths too, loosening the intended exact 2-per-row
         mobile grid into a wrap point that varies with label length. */
      .tab-btn {
        -webkit-hyphens: auto;
        hyphens: auto;
        overflow-wrap: break-word;
        min-width: 45%;
      }
    }

    /* On very wide monitors, .page's fixed 640px max-width (sized for
       comfortable reading, not for filling the screen) leaves the floating
       sidebar widgets clustered in the middle with hundreds of pixels of
       empty margin on both sides that neither the content nor the sidebars
       use. Widening .page modestly here also pushes the sidebars (positioned
       via calc(100% + 24px) off .page's own edges) further out with them,
       so the whole cluster gets more presence without needing to touch the
       sidebar positioning math directly. */
    @media (min-width: 1600px) {
      .page { max-width: 760px; }
    }

/* ============================================================
   ACTIVE-TAB CLASS COLOUR (2026-07-28, take 2)
   ------------------------------------------------------------
   Take 1 gave all seven tabs a coloured wash at once, which read as a
   rainbow rather than as identity. Here the inactive tabs stay exactly as
   they were, and only the ACTIVE tab takes its class colour -- so the page
   picks up the colour of whatever class you are actually in, and the row
   itself stays calm. Every hue is solved on the same 4.60:1 recipe as the
   site accent ring; no off-system colour is introduced.
   ============================================================ */
    /* --- BEGIN generated exam spines (tools/build_exam_spines.py) --- */
    /* Each exam's own --indigo, lifted by 0.057 lightness for dark mode
       (floor 0.42) -- the same treatment the tab hues above get. */
    .exam-section[data-examid="anatomy-exam1"] { --exam-spine: #5b3a8e; }
    .exam-section[data-examid="anatomy-exam2"] { --exam-spine: #1e3a5f; }
    .exam-section[data-examid="anatomy-exam3"] { --exam-spine: #8a3d5b; }
    .exam-section[data-examid="anatomy-exam4"] { --exam-spine: #0f766e; }
    .exam-section[data-examid="cam-nutrition-exam1"] { --exam-spine: #3f7a5c; }
    .exam-section[data-examid="cam-nutrition-exam2"] { --exam-spine: #3f7a5c; }
    .exam-section[data-examid="cms1-exam1"] { --exam-spine: #3f7d7a; }
    .exam-section[data-examid="cms1-exam2"] { --exam-spine: #5566b5; }
    .exam-section[data-examid="cms1-exam3"] { --exam-spine: #6a4fa3; }
    .exam-section[data-examid="cms1-exam4"] { --exam-spine: #a753b4; }
    .exam-section[data-examid="cms1-exam5"] { --exam-spine: #b4538c; }
    .exam-section[data-examid="clinpath1-exam1"] { --exam-spine: #6a4fa3; }
    .exam-section[data-examid="clinpath1-exam2"] { --exam-spine: #8a3d5b; }
    .exam-section[data-examid="medlit-book"] { --exam-spine: #4f5666; }
    .exam-section[data-examid="medlit-exam1"] { --exam-spine: #b55575; }
    .exam-section[data-examid="intro-pa-comprehensive"] { --exam-spine: #4f5666; }
    .exam-section[data-examid="micro-exam1"] { --exam-spine: #3f8a55; }
    .exam-section[data-examid="micro-exam2"] { --exam-spine: #2a5f8f; }
    .exam-section[data-examid="pharm1-exam1"] { --exam-spine: #9c5230; }
    .exam-section[data-examid="pharm1-exam2"] { --exam-spine: #4a7fa5; }
    .exam-section[data-examid="pharm1-exam3"] { --exam-spine: #3f7a5c; }
    .exam-section[data-examid="pd1-exam2"] { --exam-spine: #5c8a72; }
    .exam-section[data-examid="pd1-exam3"] { --exam-spine: #5b46a3; }
    .exam-section[data-examid="pd2-exam1"] { --exam-spine: #6b7f35; }
    .exam-section[data-examid="pd2-exam2"] { --exam-spine: #15707f; }
    .exam-section[data-examid="physio-exam1"] { --exam-spine: #1f6b4a; }
    .exam-section[data-examid="physio-exam2"] { --exam-spine: #4a2f6b; }
    .exam-section[data-examid="physio-exam3"] { --exam-spine: #2a5f8f; }
    .exam-section[data-examid="physio-exam4"] { --exam-spine: #15707f; }
    .exam-section[data-examid="pdm1-exam1"] { --exam-spine: #8a5f8d; }
    .exam-section[data-examid="pdm1-exam2"] { --exam-spine: #4e8a76; }
:root[data-theme="dark"] .exam-section[data-examid="anatomy-exam1"] { --exam-spine: #6842a3; }
:root[data-theme="dark"] .exam-section[data-examid="anatomy-exam2"] { --exam-spine: #3363a3; }
:root[data-theme="dark"] .exam-section[data-examid="anatomy-exam3"] { --exam-spine: #9e4668; }
:root[data-theme="dark"] .exam-section[data-examid="anatomy-exam4"] { --exam-spine: #18beb1; }
:root[data-theme="dark"] .exam-section[data-examid="cam-nutrition-exam1"] { --exam-spine: #498d6b; }
:root[data-theme="dark"] .exam-section[data-examid="cam-nutrition-exam2"] { --exam-spine: #498d6b; }
:root[data-theme="dark"] .exam-section[data-examid="cms1-exam1"] { --exam-spine: #49908d; }
:root[data-theme="dark"] .exam-section[data-examid="cms1-exam2"] { --exam-spine: #6978be; }
:root[data-theme="dark"] .exam-section[data-examid="cms1-exam3"] { --exam-spine: #795eb1; }
:root[data-theme="dark"] .exam-section[data-examid="cms1-exam4"] { --exam-spine: #b167bd; }
:root[data-theme="dark"] .exam-section[data-examid="cms1-exam5"] { --exam-spine: #bd679a; }
:root[data-theme="dark"] .exam-section[data-examid="clinpath1-exam1"] { --exam-spine: #795eb1; }
:root[data-theme="dark"] .exam-section[data-examid="clinpath1-exam2"] { --exam-spine: #9e4668; }
:root[data-theme="dark"] .exam-section[data-examid="medlit-book"] { --exam-spine: #5d6679; }
:root[data-theme="dark"] .exam-section[data-examid="medlit-exam1"] { --exam-spine: #be6985; }
:root[data-theme="dark"] .exam-section[data-examid="intro-pa-comprehensive"] { --exam-spine: #5d6679; }
:root[data-theme="dark"] .exam-section[data-examid="micro-exam1"] { --exam-spine: #489e61; }
:root[data-theme="dark"] .exam-section[data-examid="micro-exam2"] { --exam-spine: #316ea6; }
:root[data-theme="dark"] .exam-section[data-examid="pharm1-exam1"] { --exam-spine: #b25e37; }
:root[data-theme="dark"] .exam-section[data-examid="pharm1-exam2"] { --exam-spine: #588eb4; }
:root[data-theme="dark"] .exam-section[data-examid="pharm1-exam3"] { --exam-spine: #498d6b; }
:root[data-theme="dark"] .exam-section[data-examid="pd1-exam2"] { --exam-spine: #689b80; }
:root[data-theme="dark"] .exam-section[data-examid="pd1-exam3"] { --exam-spine: #6852b5; }
:root[data-theme="dark"] .exam-section[data-examid="pd2-exam1"] { --exam-spine: #7f973f; }
:root[data-theme="dark"] .exam-section[data-examid="pd2-exam2"] { --exam-spine: #1ea2b8; }
:root[data-theme="dark"] .exam-section[data-examid="physio-exam1"] { --exam-spine: #30a673; }
:root[data-theme="dark"] .exam-section[data-examid="physio-exam2"] { --exam-spine: #674195; }
:root[data-theme="dark"] .exam-section[data-examid="physio-exam3"] { --exam-spine: #316ea6; }
:root[data-theme="dark"] .exam-section[data-examid="physio-exam4"] { --exam-spine: #1ea2b8; }
:root[data-theme="dark"] .exam-section[data-examid="pdm1-exam1"] { --exam-spine: #996d9c; }
:root[data-theme="dark"] .exam-section[data-examid="pdm1-exam2"] { --exam-spine: #589d86; }
    /* --- END generated exam spines --- */

.tab-btn[data-tab="physio"]              { --tab-hue: #2a6df2; }
.tab-btn[data-tab="pharmacodynamics"]    { --tab-hue: #9c46f3; }
.tab-btn[data-tab="anatomy"]             { --tab-hue: #df2a44; }
.tab-btn[data-tab="anatomy-practicum"]   { --tab-hue: #d3299c; }
.tab-btn[data-tab="intro-pa"]            { --tab-hue: #228283; }
.tab-btn[data-tab="cam-nutrition"]       { --tab-hue: #218640; }
.tab-btn[data-tab="physical-diagnosis"]  { --tab-hue: #ad631e; }
:root[data-theme="dark"] .tab-btn[data-tab="physio"]             { --tab-hue: #4580f3; }
:root[data-theme="dark"] .tab-btn[data-tab="pharmacodynamics"]   { --tab-hue: #a562f4; }
:root[data-theme="dark"] .tab-btn[data-tab="anatomy"]            { --tab-hue: #f53c52; }
:root[data-theme="dark"] .tab-btn[data-tab="anatomy-practicum"]  { --tab-hue: #ed30b0; }
:root[data-theme="dark"] .tab-btn[data-tab="intro-pa"]           { --tab-hue: #279394; }
:root[data-theme="dark"] .tab-btn[data-tab="cam-nutrition"]      { --tab-hue: #279849; }
:root[data-theme="dark"] .tab-btn[data-tab="physical-diagnosis"] { --tab-hue: #c37024; }

/* Inactive tabs are untouched. Only the active one changes. */
.tab-btn.active {
  background: var(--tab-hue, var(--accent));
  box-shadow: 0 4px 14px color-mix(in oklab, var(--tab-hue, var(--accent)) 30%, transparent);
}


/* Practicum Atlas entry point inside each practicum section. Same shell as the
   other feature buttons (.guides-btn) so it reads as part of the set rather
   than a bare link, with a second line for what it actually does. */
.atlas-btn{ margin: 2px 0 14px; align-items: center; }
.atlas-btn .atlas-btn-note{
  font-weight: 600; font-size: 12.5px; color: var(--muted);
  padding-left: 10px; margin-left: 2px; border-left: 1px solid var(--border);
}
@media (max-width: 560px){
  .atlas-btn{ white-space: normal; align-items: flex-start; }
  .atlas-btn .atlas-btn-note{ display: block; border-left: 0; padding-left: 0; margin: 2px 0 0; }
}


/* "3 more in Semester 1 →" under scoped search results */
.search-other-sem{
  display: block; width: 100%; text-align: left; font: inherit; font-size: 13px;
  font-weight: 700; cursor: pointer; border: 0; border-top: 1px solid var(--border);
  background: transparent; color: var(--a-teal, #329293); padding: 10px 14px;
}
.search-other-sem:hover{ background: var(--paper, rgba(0,0,0,.03)); }

/* Everyday entrance: the homepage's cards settle into place on load. Much
   smaller than the first-week drop below -- this one runs every visit, so it
   has to read as the page arriving rather than as an effect. A touch of
   overshoot and one correction; no wobble you could count.

   Only transform and opacity, so it never moves anything in layout. That
   matters: alignSidebars() positions the left and right rails by measuring the
   semester card, and a transform DOES shift what getBoundingClientRect
   reports -- see the note there about when this is allowed to start. */
@keyframes card-settle{
  0%   { transform:translateY(-17px) rotate(-.35deg); opacity:0; }
  62%  { transform:translateY(3px)   rotate(.14deg);  opacity:1; }
  84%  { transform:translateY(-1.5px) rotate(-.06deg); }
  100% { transform:translateY(0)     rotate(0);       opacity:1; }
}
.card-settle{ animation:card-settle .6s cubic-bezier(.2,.8,.3,1) both; }

@media (prefers-reduced-motion:reduce){
  .card-settle{ animation:none; }
}


/* Semester countdown, directly under the progress bar. The bar answers "where
   am I", this answers "how much longer" -- so the number carries the weight and
   everything else stays quiet. Colours are the existing accent pair; no new
   hues enter the palette for one component. */
/* `display:flex` below beats the UA stylesheet's `[hidden]{display:none}`, so
   the attribute alone will NOT hide this element -- it would paint as an empty
   26px strip of background and border. This matters now that an in-progress
   term deliberately leaves the countdown hidden. */
.semester-countdown[hidden]{ display:none; }
.semester-countdown{
  display:flex; align-items:center; gap:14px;
  margin:14px 0 4px; padding:12px 16px;
  background:var(--accent-soft);
  border:1px solid color-mix(in oklab, var(--accent) 18%, transparent);
  border-radius:12px;
}
.semester-countdown .cd-num{
  /* never let a long detail line squeeze or wrap the number -- an estimated
     term's "~275" is four characters wide where a live one is often one */
  flex:0 0 auto; white-space:nowrap;
  font-size:34px; font-weight:800; line-height:1;
  color:var(--accent);
  /* tabular figures so the number does not jitter as it ticks down */
  font-variant-numeric:tabular-nums;
}
.semester-countdown .cd-text{ min-width:0; }
.semester-countdown .cd-main{
  font-size:14.5px; font-weight:700; color:var(--ink); line-height:1.3;
}
.semester-countdown .cd-sub{
  font-size:12.5px; color:var(--muted); line-height:1.45; margin-top:2px;
}
@media (max-width:560px){
  .semester-countdown{ gap:11px; padding:10px 12px; }
  .semester-countdown .cd-num{ font-size:27px; }
  .semester-countdown .cd-main{ font-size:13px; }
  .semester-countdown .cd-sub{ font-size:11.5px; }
}

/* Exam roadmap shown in a class tab that has no quizzes in it yet. Reads as
   reference material rather than something to click, because none of it is
   clickable -- no hover, no link colour. */
.class-schedule{ padding:6px 2px 14px; }
.cs-lead{
  margin:0 0 14px; font-size:13.5px; line-height:1.6; color:var(--muted);
  max-width:62ch;
}
.cs-lead b{ color:var(--ink); font-weight:700; }
.cs-list{ list-style:none; margin:0; padding:0;
  display:flex; flex-direction:column; gap:6px; }
.cs-row{
  display:flex; align-items:baseline; gap:12px;
  padding:9px 12px; border:1px solid var(--line, rgba(127,127,127,.18));
  border-radius:9px; background:var(--card);
}
.cs-date{
  flex:0 0 auto; min-width:8.5em;
  font-size:12.5px; font-weight:700; color:var(--accent);
  font-variant-numeric:tabular-nums;
}
.cs-title{ font-size:13.5px; color:var(--ink); line-height:1.4; }
/* A date already gone by stays listed -- the roadmap is the whole term -- but
   stops competing with the ones still ahead. */
.cs-row.past{ opacity:.5; }
.cs-row.past .cs-date{ color:var(--muted); }
@media (max-width:560px){
  .cs-row{ flex-direction:column; gap:3px; padding:8px 10px; }
  .cs-date{ min-width:0; }
}

/* Empty state for a semester that has no content yet. Kept quiet -- it is a
   placeholder, not a call to action. */
.semester-empty{ text-align:center; padding:38px 20px 44px; color:var(--muted); }
.semester-empty-title{ font-size:17px; font-weight:800; color:var(--ink);
  margin:0 0 6px; }
.semester-empty p{ margin:0 auto; max-width:44ch; font-size:14px; line-height:1.6; }

/* The site has no global .hidden rule -- every one is scoped to its own
   component -- so the semester blocks need their own. Without this all four
   terms render stacked down the page, each with its own heading, progress bar
   and picker, which reads as duplicated content rather than as a bug. */
.semester.hidden { display: none; }

/* Light-mode contrast (design-review leftovers item 1, 2026-09-27). Every
   site accent is solved to 4.60:1 on WHITE; set on its own pale -soft wash
   the same accent drops to 4.17-4.26:1. Chips and pills that put an accent
   on its wash take that accent's existing darker -hover shade (5.3:1 on the
   wash), light theme only. */
:root[data-theme="light"] body.homepage .my-progress-link { color: var(--a-blue-hover); }
:root[data-theme="light"] body.homepage .review-link { color: var(--a-crimson-hover); }
:root[data-theme="light"] body.homepage .mycram-link { color: var(--a-green-hover); }
:root[data-theme="light"] body.homepage .week-day,
:root[data-theme="light"] body.homepage .quiz-tag--updated,
:root[data-theme="light"] body.homepage .ai-disclaimer a.ai-report { color: var(--a-blue-hover); }
:root[data-theme="light"] body.homepage .question-issue-pill { color: var(--a-amber-hover); }
