    body { font-family: 'Inter', 'Noto Sans Kannada', sans-serif; }

    /* Brief highlight when landing on a specific test paper card from search results */
    .search-result-highlight { box-shadow: 0 0 0 3px #f59e0b, 0 8px 20px -4px rgba(245,158,11,0.4) !important; }

    /* --------------------------------------------------
       Layout guarantee: the body must always allow scroll when content is tall,
       and must sticky-footer only when content is short. The default Tailwind
       min-h-screen + flex-col + flex-1 on main can, in some browsers, clip
       tall content because a flex-1 child with flex-basis:0 doesn't always
       let content push the parent past 100vh. This explicit rule makes it
       bulletproof: `main` grows with its content, `body` never clips.
    -------------------------------------------------- */
    html, body { overflow-x: hidden; }
    body { overflow-y: auto !important; }
    body > main { min-height: 0 !important; }  /* let flex-1 mains shrink AND grow with content */
    body > main[id^="view-"]:not(.hidden) { display: block; }  /* fallback if flex-1 clips */

    /* View-as-student mode: hide admin-only affordances so preview matches a real student */
    body.viewing-as-student [data-admin-only],
    body.viewing-as-student #view-admin,
    body.viewing-as-student .admin-only,
    body.viewing-as-student [data-panel-link="admin"],
    body.viewing-as-student a[onclick*="navigate('admin')"] {
      display: none !important;
    }
    .omr-bubble {
      width: 26px; height: 26px; border-radius: 9999px; border: 1.5px solid #475569;
      display: flex; align-items: center; justify-content: center;
      font-size: 11px; font-weight: 700; color: #475569; cursor: pointer;
      user-select: none; transition: all 0.15s ease-in-out; background-color: #ffffff;
    }
    .omr-bubble:hover { background-color: #e2e8f0; border-color: #1e293b; }
    .omr-bubble.filled {
      background-color: #0f172a !important; border-color: #0f172a !important;
      color: #ffffff !important; box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.6);
    }
    /* Difficult Mode: a question's non-chosen bubbles once it's answered -- see renderOMR/selectAnswer */
    .omr-bubble-locked { cursor: not-allowed; opacity: .35; pointer-events: none; }
    .omr-bubble-locked:hover { background-color: #ffffff; border-color: #475569; }
    /* renderQuestionTextHtml() output -- see js/02_pricing_master_storage.js. .qtext-line is a plain
       paragraph line of question text; .match-table is a "match the following" List I/List II grid
       built from consecutive "|"-separated lines. Scoped narrowly (not full-bleed) since it sits
       inside question cards of very different widths across the exam engine, Practice Mode, the
       review modal and Studio's compact row preview. */
    .qtext-line { margin: 0 0 0.35em; }
    .qtext-line:last-child { margin-bottom: 0; }
    .match-table { border-collapse: collapse; margin: 0.35em 0; width: 100%; max-width: 480px; }
    .match-table td { border: 1px solid #cbd5e1; padding: 4px 10px; font-size: inherit; vertical-align: top; }
    .match-table tr:first-child td { background: #f8fafc; }
    /* Studio's compact per-question row preview is much denser than a real exam card, so the table
       needs smaller padding to avoid blowing out the row height. */
    .studio-qrow .match-table { font-size: 12px; max-width: 100%; }
    .studio-qrow .match-table td { padding: 2px 6px; }
    .omr-row-unanswered { background-color: #fff1f2; border-radius: 6px; box-shadow: inset 3px 0 0 #fb7185; }
    .omr-row-answered { background-color: #ecfdf5; border-radius: 6px; box-shadow: inset 3px 0 0 #10b981; }
    .omr-row-marked { background-color: #f3e8ff; border-radius: 6px; box-shadow: inset 3px 0 0 #a855f7; }
    .omr-flag-btn { font-size: 14px; line-height: 1; cursor: pointer; opacity: .25; transition: opacity .15s ease; background: none; border: none; }
    .omr-flag-btn:hover { opacity: .6; }
    .omr-flag-btn.active { opacity: 1; }
    .question-card.active-target {
      border-color: #f59e0b; box-shadow: 0 0 0 2px #f59e0b; background-color: #fffbeb;
    }
    /* OMR drawer (mobile only, < 768px to match Tailwind's md: used throughout the exam engine).
       Confirmed via Playwright that the always-visible 320-360px sidebar this overrides left only
       ~50-90px for the actual question on a standard 390-412px phone -- unusable. Below md, the
       aside is pulled off-screen by default and slides in as a full-height overlay via
       openOmrDrawer()/closeOmrDrawer() (js/10_omr_exam_engine.js); at md and up both rules below are
       no-ops (the media query doesn't apply) and the aside is the original always-visible sidebar. */
    @media (max-width: 767px) {
      .omr-aside {
        position: fixed; top: 56px; right: 0; bottom: 0; z-index: 40;
        width: min(88vw, 360px);
        transform: translateX(100%);
        transition: transform .2s ease-out;
      }
      .omr-aside.omr-aside-open { transform: translateX(0); }
      .omr-drawer-scrim { position: fixed; top: 56px; left: 0; right: 0; bottom: 0; z-index: 39; background: rgba(15, 23, 42, .45); }
    }
    .dropdown-parent:hover .dropdown-menu, .dropdown-parent:focus-within .dropdown-menu { display: block; }

    /* ---------- KAS Study Planner (student dashboard) ---------- */
    .kp { --ink:#221F4E; --ink2:#3A3670; --cream:#FFF7E8; --yellow:#FFC531; --red:#DA3B2B; --green:#1E9E6A; --greenbg:#E7F6EF; --mute:#6E6A93; --line:#EFE6D2; --flag:linear-gradient(90deg,#FFC531,#DA3B2B);
      background:var(--cream); color:var(--ink); font-family:'Karla','Noto Sans Kannada',ui-sans-serif,sans-serif; border-radius:1rem; overflow:hidden; border:1px solid var(--line); }
    .kp-display { font-family:'Bricolage Grotesque','Karla',sans-serif; font-weight:800; }
    .kp-head { background:var(--ink); color:#fff; padding:22px 18px 24px; position:relative; }
    .kp-head::before { content:''; position:absolute; top:0; left:0; right:0; height:6px; background:var(--flag); }
    .kp-head-row { display:flex; align-items:center; gap:16px; }
    .kp-title { font-size:24px; line-height:1.05; }
    .kp-sub { font-size:13px; opacity:.85; margin-top:4px; }
    .kp-chips { margin-top:8px; display:flex; gap:8px; flex-wrap:wrap; }
    .kp-chip { font-size:11.5px; padding:3px 9px; border-radius:99px; font-weight:700; }
    .kp-saved { position:absolute; top:12px; right:14px; font-size:11px; font-weight:700; color:#9FE8C8; opacity:0; transition:opacity 200ms ease; }
    .kp-saved.show { opacity:1; }
    .kp-saved.error { color:#FFB3A9; }
    .kp-body { padding:14px; }
    .kp-tabs { display:flex; gap:8px; }
    .kp-tab { flex:1; padding:9px 4px; border-radius:14px; border:2px solid transparent; background:#fff; color:var(--ink); box-shadow:0 1px 3px rgba(34,31,78,.08); cursor:pointer; text-align:center; transition:all 150ms ease; }
    .kp-tab[aria-selected="true"] { background:var(--ink); border-color:var(--ink); color:#fff; }
    .kp-tab-label { font-size:14px; }
    .kp-tab-sub { font-size:10.5px; opacity:.7; }
    .kp-section-head { margin:16px 0 14px; }
    .kp-section-row { display:flex; justify-content:space-between; align-items:baseline; gap:8px; }
    .kp-section-title { font-size:19px; }
    .kp-section-pct { font-size:13px; font-weight:800; color:var(--red); }
    .kp-note { font-size:12px; color:var(--mute); margin:3px 0 8px; }
    .kp-bar { background:#F1EADB; border-radius:99px; height:8px; overflow:hidden; }
    .kp-bar > div { height:100%; background:var(--flag); border-radius:99px; transition:width 500ms ease; }
    .kp-card { background:#fff; border-radius:16px; padding:12px 14px 12px 12px; margin-bottom:12px; box-shadow:0 1px 4px rgba(34,31,78,.07); border-left:5px solid var(--yellow); transition:background 250ms ease; }
    .kp-card.mock { border-left-color:var(--red); }
    .kp-card.full { background:var(--greenbg); border-left-color:var(--green); }
    .kp-card-top { display:flex; align-items:center; gap:8px; margin-bottom:8px; flex-wrap:wrap; }
    .kp-week { font-size:12px; background:var(--ink); color:#fff; padding:3px 10px; border-radius:99px; }
    .kp-card.full .kp-week { background:var(--green); }
    .kp-tag { font-size:10.5px; font-weight:800; padding:3px 8px; border-radius:99px; }
    .kp-tag-kn { color:#B02A1D; background:#FCE3DF; }
    .kp-tag-mock { color:#7A4A00; background:#FFEDC2; }
    .kp-tag-opt { color:#1F4E8C; background:#E1ECFA; }
    .kp-tag-ca { color:#0F6B47; background:#DDF3E8; }
    .kp-tag-custom { color:#3A3670; background:#ECEAF8; }
    .kp-version { display:inline-block; font-size:10.5px; font-weight:800; padding:2px 8px; border-radius:99px; margin-left:6px; vertical-align:middle; }
    .kp-version.basic { background:rgba(255,255,255,0.14); color:#EAE8FF; }
    .kp-version.advanced { background:var(--yellow); color:var(--ink); }
    .kp-start { display:flex; flex-wrap:wrap; align-items:center; gap:8px; background:#fff; border:1px solid var(--line); border-radius:12px; padding:8px 12px; margin:-4px 0 14px; font-size:12.5px; color:var(--ink2); }
    .kp-start input { border:1px solid #D8CFBA; border-radius:8px; padding:3px 6px; font:inherit; color:var(--ink); }
    .kp-start b { color:var(--ink); }
    .kp-tag-now { color:#221F4E; background:var(--yellow); }
    .kp-tag-behind { color:#B02A1D; background:#fff; border:1.5px solid #DA3B2B; }
    .kp-dates { font-size:11.5px; font-weight:700; color:var(--mute); }
    .kp-card.now { box-shadow:0 0 0 2px var(--yellow), 0 1px 4px rgba(34,31,78,.07); }
    .kp-num { font-size:12px; background:var(--ink); color:#fff; width:24px; height:24px; border-radius:99px; display:inline-flex; align-items:center; justify-content:center; }
    .kp-upgrade { margin-top:14px; background:var(--ink); color:#fff; border-radius:16px; padding:14px 16px; }
    .kp-upgrade button { margin:8px 8px 0 0; border:0; border-radius:99px; padding:7px 14px; font-weight:800; font-size:12.5px; cursor:pointer; background:var(--yellow); color:var(--ink); }
    .kp-switch { display:inline-flex; margin-top:14px; padding:3px; border-radius:99px; background:rgba(255,255,255,0.10); gap:2px; }
    .kp-switch button { border:0; background:transparent; color:#EAE8FF; font-weight:800; font-size:12.5px; padding:6px 14px; border-radius:99px; cursor:pointer; transition:all 150ms ease; font-family:'Bricolage Grotesque','Karla',sans-serif; }
    .kp-switch button:hover { color:#fff; }
    .kp-switch button[aria-checked="true"] { background:var(--yellow); color:var(--ink); }
    .kp-switch button:focus-visible { outline:3px solid var(--yellow); outline-offset:2px; }
    .kp-switch-pct { font-family:'Karla',sans-serif; font-weight:700; opacity:.75; margin-left:4px; font-size:11px; }
    .kp-done-label { font-size:11.5px; font-weight:800; color:var(--green); margin-left:auto; }
    .kp-row { display:flex; gap:11px; align-items:flex-start; padding:6px 0; }
    .kp-row-label { font-size:10px; font-weight:800; letter-spacing:.6px; color:#A9862B; text-transform:uppercase; margin-right:6px; }
    .kp-row-text { font-size:13px; font-weight:600; line-height:1.45; color:var(--ink2); }
    .kp-row.bold .kp-row-text { font-size:14.5px; font-weight:800; color:var(--ink); }
    .kp-row.on .kp-row-text { color:#8B87A8; text-decoration:line-through; }
    .kp-check { width:20px; height:20px; min-width:20px; border-radius:7px; border:2px solid #CFC7B2; background:#fff; display:flex; align-items:center; justify-content:center; cursor:pointer; padding:0; transition:all 150ms ease; }
    .kp-check.lg { width:26px; height:26px; min-width:26px; border-radius:8px; }
    .kp-check.on { border-color:var(--green); background:var(--green); }
    .kp-check:focus-visible, .kp-tab:focus-visible, .kp-reset:focus-visible { outline:3px solid var(--yellow); outline-offset:2px; }
    .kp-book { margin-top:8px; font-size:11.5px; color:var(--mute); font-weight:700; }
    .kp-callout { background:#FFF1D6; border:1.5px solid var(--yellow); border-radius:14px; padding:12px 14px; font-size:13px; }
    .kp-panel { background:#fff; border-radius:16px; padding:6px 14px; box-shadow:0 1px 4px rgba(34,31,78,.07); }
    .kp-daily-row { display:flex; gap:12px; align-items:center; padding:13px 0; border-bottom:1px solid var(--line); }
    .kp-daily-row:last-child { border-bottom:none; }
    .kp-daily-text { font-size:14.5px; font-weight:600; }
    .kp-daily-row.on .kp-daily-text { text-decoration:line-through; color:var(--mute); }
    .kp-dark { margin-top:14px; background:var(--ink); color:#fff; border-radius:16px; padding:14px 16px; }
    .kp-books { background:#fff; border-radius:16px; padding:12px 14px; margin-bottom:12px; box-shadow:0 1px 4px rgba(34,31,78,.07); border-top:4px solid var(--yellow); }
    .kp-books.alt { border-top-color:var(--red); }
    .kp-book-row { display:flex; gap:11px; align-items:center; padding:8px 0; }
    .kp-book-text { font-size:13.5px; font-weight:600; color:var(--ink2); }
    .kp-book-row.on .kp-book-text { text-decoration:line-through; color:var(--mute); }
    .kp-foot { text-align:center; margin-top:18px; padding-bottom:6px; }
    .kp-reset { background:none; border:1.5px solid #D8CFBA; color:var(--mute); border-radius:99px; padding:7px 18px; font-size:12.5px; font-weight:700; cursor:pointer; }
    .kp-foot-note { font-size:11px; color:#A79F8C; margin-top:10px; }
    @media (prefers-reduced-motion: reduce) { .kp * { transition:none !important; } }
    
    /* Word Document Canvas Styling */
    .word-canvas {
      outline: none;
      font-size: 14px;
      line-height: 1.8;
    }
    /* The 440px floor is only wanted on the admin's editable "blank page" canvas, so it
       feels like a page to type into -- NOT on the public page-render/preview containers
       that also share the .word-canvas class, where it forced short pages to pad out with
       empty space before the "Inside this section" children grid. See #word-editor-canvas
       in admin-console.html vs #page-render-content / #page-preview-content in index.html. */
    #word-editor-canvas.word-canvas {
      min-height: 440px;
    }

    /* "Section tile style" = 'shadow' (Dev Console -> Site branding -> Section tile style, see
       pageTileHtml()'s 'shadow' case in js/08_word_page_creator.js): soft resting shadow that
       lifts a bit more on hover. The resting shadow is set inline on the card (it's specific to
       this one look, not worth a reusable utility), and inline style always outranks a plain
       stylesheet hover rule regardless of selector -- hence !important here, just to let the
       hover state actually override it. */
    .evp-tile-lift { transition: box-shadow .15s, transform .15s; }
    .evp-tile-lift:hover { box-shadow: 0 2px 4px rgba(15,23,42,.06), 0 10px 24px rgba(15,23,42,.1) !important; transform: translateY(-2px); }

    .word-canvas h1 { font-size: 1.75rem; font-weight: 800; margin-bottom: 0.75rem; color: #0f172a; }
    .word-canvas h2 { font-size: 1.35rem; font-weight: 700; margin-top: 1.25rem; margin-bottom: 0.5rem; color: #1e293b; border-bottom: 1px solid #e2e8f0; padding-bottom: 0.25rem; }
    .word-canvas h3 { font-size: 1.15rem; font-weight: 700; margin-top: 1rem; margin-bottom: 0.25rem; color: #334155; }
    .word-canvas p { margin-bottom: 0.75rem; }
    .word-canvas ul { list-style-type: disc; padding-left: 1.5rem; margin-bottom: 0.75rem; }
    .word-canvas ol { list-style-type: decimal; padding-left: 1.5rem; margin-bottom: 0.75rem; }

    /* ---------- Page content blocks (info boxes, tables, columns, figures, timeline, FAQ, page cards) ---------- */
    .word-canvas .kb-box { border: 1px solid; border-left-width: 5px; border-radius: 10px; padding: 0.85rem 1rem; margin: 1rem 0; }
    .word-canvas .kb-box > :last-child { margin-bottom: 0; }
    .word-canvas .kb-box-title { font-weight: 800; margin-bottom: 0.35rem; }
    .word-canvas .kb-info  { background: #eff6ff; border-color: #bfdbfe; border-left-color: #2563eb; color: #1e3a8a; }
    .word-canvas .kb-tip   { background: #ecfdf5; border-color: #a7f3d0; border-left-color: #059669; color: #064e3b; }
    .word-canvas .kb-warn  { background: #fffbeb; border-color: #fde68a; border-left-color: #d97706; color: #78350f; }
    .word-canvas .kb-facts { background: #f5f3ff; border-color: #ddd6fe; border-left-color: #7c3aed; color: #3b0764; }
    .word-canvas .kb-table-wrap { position: relative; overflow-x: auto; margin: 1rem 0; border: 1px solid #e2e8f0; border-radius: 10px; }
    /* Responsive 16:9 embed -- the padding-bottom % trick (classic "intrinsic ratio box") rather
       than aspect-ratio, so this still works in the handful of older in-app webviews aspect-ratio
       support can't be assumed on. The iframe itself is absolutely positioned to fill that box. */
    .word-canvas .kb-video-wrap { position: relative; width: 100%; padding-bottom: 56.25%; margin: 1rem 0; border-radius: 10px; overflow: hidden; background: #0f172a; }
    .word-canvas .kb-video-wrap iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; }
    .word-canvas table.kb-table { width: 100%; border-collapse: collapse; font-size: 13px; }
    .word-canvas .kb-table th, .word-canvas .kb-table td { border-bottom: 1px solid #e2e8f0; border-right: 1px solid #f1f5f9; padding: 0.5rem 0.7rem; text-align: left; vertical-align: top; min-width: 80px; }
    .word-canvas .kb-table th { background: #f1f5f9; font-weight: 700; color: #0f172a; }
    .word-canvas .kb-table tbody tr:nth-child(even) td { background: #fafafa; }
    .word-canvas .kb-cols { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1rem; margin: 1rem 0; position: relative; }
    /* !important so a column count set dynamically via inline style (columnsAction() in
       08_word_page_creator.js, for the "add/remove columns after the fact" feature) still
       collapses to one column on phones -- the inline style otherwise wins over this breakpoint. */
    @media (max-width: 640px) { .word-canvas .kb-cols { grid-template-columns: minmax(0, 1fr) !important; } }
    /* A column's own box already stretches to match its tallest sibling (CSS grid's default
       align-items:stretch) -- but a resource card nested one level inside that column (see
       .kb-resource-card below) doesn't inherit that stretch automatically, since it's not itself
       the grid item. Without this, two resource cards side by side end up at whatever height
       their own title/description text happens to produce, so a short card and a long card in
       the same row visibly mismatch even though their columns are the same height. Making the
       column a flex column and the card flex:1 (below) lets the card fill the already-stretched
       column, so same-row cards always end up the same height regardless of text length. */
    .word-canvas .kb-cols > div,
    .word-canvas .kb-cols-3 > div,
    .word-canvas .kb-cols-4 > div { display: flex; flex-direction: column; }
    .word-canvas .kb-cols > div > .kb-resource-card,
    .word-canvas .kb-cols-3 > div > .kb-resource-card,
    .word-canvas .kb-cols-4 > div > .kb-resource-card { flex: 1 1 auto; }
    .word-canvas .kb-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 0.75rem; margin: 1rem 0; }
    .word-canvas .kb-stats > div { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px; padding: 0.8rem; }
    .word-canvas .kb-stats b { display: block; font-size: 1.5rem; font-weight: 900; color: #0f172a; line-height: 1.2; }
    .word-canvas .kb-stats span { font-size: 12px; color: #475569; }
    .word-canvas ol.kb-timeline { list-style: none; padding-left: 1.1rem; border-left: 3px solid #f59e0b; margin: 1rem 0 1rem 0.4rem; }
    .word-canvas ol.kb-timeline li { position: relative; margin-bottom: 0.7rem; }
    .word-canvas ol.kb-timeline li::before { content: ''; position: absolute; left: -1.55rem; top: 0.45rem; width: 11px; height: 11px; border-radius: 50%; background: #fff; border: 3px solid #f59e0b; }
    .word-canvas ol.kb-timeline li b { color: #b45309; margin-right: 0.35rem; }
    .word-canvas details.kb-faq { border: 1px solid #e2e8f0; border-radius: 10px; padding: 0.6rem 0.9rem; margin: 0.5rem 0; background: #fff; }
    .word-canvas details.kb-faq summary { font-weight: 700; cursor: pointer; }
    .word-canvas details.kb-faq[open] summary { margin-bottom: 0.4rem; }
    .word-canvas hr.kb-divider { border: none; border-top: 2px solid #e2e8f0; margin: 1.5rem 0; }
    .word-canvas blockquote { margin: 1rem 0; padding: 0.4rem 1rem; border-left: 4px solid #cbd5e1; color: #475569; font-style: italic; background: #f8fafc; }
    /* editor-only guides */
    #word-editor-canvas .kb-cols > div { outline: 1px dashed #cbd5e1; outline-offset: 4px; min-height: 2rem; }
    #word-editor-canvas .kb-pagelink { position: relative; display: inline-flex; align-items: center; gap: 6px; padding: 6px 78px 6px 10px; margin: 4px 0; border: 1px dashed #f59e0b; border-radius: 8px; background: #fffbeb; font-size: 12px; font-weight: 700; color: #78350f; }
    /* Delete (and, for resource cards, edit) buttons overlaid on non-editable blocks (resource
       cards, page cards, tables). These blocks are contenteditable="false" "islands" the browser
       won't let you select-and-backspace or click into reliably, so without these there is no way
       to remove or change one once inserted (only delete-and-recreate). Editor-only -- styled only
       inside #word-editor-canvas, not .word-canvas. The <button> elements themselves are still
       part of the saved content (so the editor can find and style them), so this scoping alone
       does NOT keep them off the published page -- renderDynamicCustomPage() in
       08_word_page_creator.js strips every .kb-block-delete node from the DOM before a visitor
       ever sees it. Keep both: the strip is the real guarantee, this CSS is what makes them usable
       in the editor in the first place. */
    #word-editor-canvas .kb-block-delete {
      position: absolute; z-index: 3; width: 20px; height: 20px; padding: 0; border: none;
      border-radius: 999px; background: rgba(15,23,42,.78); color: #fff; font-size: 11px;
      font-weight: 800; line-height: 20px; text-align: center; cursor: pointer;
    }
    #word-editor-canvas .kb-block-delete:hover { background: #dc2626; }
    #word-editor-canvas .kb-block-delete-edit:hover { background: #2563eb; }
    #word-editor-canvas .kb-resource-card .kb-block-delete { top: 6px; right: 6px; }
    #word-editor-canvas .kb-resource-card .kb-block-delete-edit { top: 6px; right: 30px; }
    #word-editor-canvas .kb-pagelink .kb-block-delete { top: 50%; right: 6px; transform: translateY(-50%); }
    /* Same overlay pattern, on a table's wrapper -- tables stay normal contenteditable content
       (cells are typed into directly), only the button itself is an island, so this doesn't
       interfere with editing cell text. Previously a table's only delete path was the floating
       toolbar that appears while the cursor sits in a cell -- easy to miss, and the only block
       type here without the same obvious ✕ every other block got. */
    #word-editor-canvas .kb-table-wrap .kb-block-delete { top: 6px; right: 6px; }
    /* Same overlay, on a video embed -- the whole block is a non-editable island (no typeable
       text inside it, unlike the table/columns wrappers above), so the button sits on top of the
       iframe rather than beside editable content. */
    #word-editor-canvas .kb-video-wrap .kb-block-delete { top: 6px; right: 6px; }
    /* Same overlay, on a multi-column block -- the block itself (.kb-cols/-3/-4) stays normal
       editable content (each column's text is typed into directly), only the button is an
       island. An absolutely-positioned grid child like this is taken out of normal grid flow, so
       it doesn't consume a column slot or disturb the layout. */
    #word-editor-canvas .kb-cols .kb-block-delete,
    #word-editor-canvas .kb-cols-3 .kb-block-delete,
    #word-editor-canvas .kb-cols-4 .kb-block-delete { top: 6px; right: 6px; }
    /* The generic column-delete rule just above has the same specificity (two classes) as the
       per-block-type positioning rules above it (.kb-resource-card .kb-block-delete-edit,
       .kb-pagelink .kb-block-delete), so without these higher-specificity overrides, source
       order alone decides the winner for an island block nested inside a columns block -- and
       since the column rule comes later, it would silently clobber the block's own position. For
       a resource card specifically, that meant its edit (✎) button landed in the exact same spot
       as its delete (✕) button (both top:6px;right:6px), so the delete button -- later in the
       DOM, same z-index -- always intercepted clicks meant for edit, making "Edit" do nothing for
       any resource card placed inside columns. These rules restore the correct per-type position
       whenever that block type sits inside any columns block. */
    #word-editor-canvas .kb-cols .kb-resource-card .kb-block-delete-edit,
    #word-editor-canvas .kb-cols-3 .kb-resource-card .kb-block-delete-edit,
    #word-editor-canvas .kb-cols-4 .kb-resource-card .kb-block-delete-edit { right: 30px; }
    #word-editor-canvas .kb-cols .kb-pagelink .kb-block-delete,
    #word-editor-canvas .kb-cols-3 .kb-pagelink .kb-block-delete,
    #word-editor-canvas .kb-cols-4 .kb-pagelink .kb-block-delete { top: 50%; right: 6px; transform: translateY(-50%); }
    /* Move-up/move-down buttons, same editor-only overlay idea as .kb-block-delete just above (and
       stripped from public view the same way -- see the JS strip in renderDynamicCustomPage() and
       downloadWordEditorPagePdf(), both now also removing .kb-block-move). Added only to the
       "island" block types (table, columns, video, page/paper card, resource card) -- the ones a
       cursor can't just be placed inside and retyped around, so before this the only way to move
       one was delete-and-reinsert at the new spot. Plain editable content (info/tip/warn/facts,
       key-figures, timeline, FAQ, card grid) is left out on purpose: that text can already be
       moved the ordinary way, by selecting it and cutting/pasting, so a dedicated button would be
       redundant there. Placed at the opposite (left) corner from the delete button so the two
       never collide, except on the pagelink badge (inline, no room for a second corner) where
       they sit in the same row, left of delete. */
    #word-editor-canvas .kb-block-move {
      position: absolute; z-index: 3; width: 20px; height: 16px; padding: 0; border: none;
      border-radius: 999px; background: rgba(15,23,42,.6); color: #fff; font-size: 9px;
      font-weight: 800; line-height: 16px; text-align: center; cursor: pointer;
    }
    #word-editor-canvas .kb-block-move:hover { background: #2563eb; }
    #word-editor-canvas .kb-table-wrap .kb-block-move-up,
    #word-editor-canvas .kb-video-wrap .kb-block-move-up,
    #word-editor-canvas .kb-cols .kb-block-move-up,
    #word-editor-canvas .kb-cols-3 .kb-block-move-up,
    #word-editor-canvas .kb-cols-4 .kb-block-move-up { top: 6px; left: 6px; }
    #word-editor-canvas .kb-table-wrap .kb-block-move-down,
    #word-editor-canvas .kb-video-wrap .kb-block-move-down,
    #word-editor-canvas .kb-cols .kb-block-move-down,
    #word-editor-canvas .kb-cols-3 .kb-block-move-down,
    #word-editor-canvas .kb-cols-4 .kb-block-move-down { top: 24px; left: 6px; }
    #word-editor-canvas .kb-table-wrap .kb-block-duplicate,
    #word-editor-canvas .kb-video-wrap .kb-block-duplicate,
    #word-editor-canvas .kb-cols .kb-block-duplicate,
    #word-editor-canvas .kb-cols-3 .kb-block-duplicate,
    #word-editor-canvas .kb-cols-4 .kb-block-duplicate { top: 42px; left: 6px; }
    #word-editor-canvas .kb-table-wrap .kb-block-drag-handle,
    #word-editor-canvas .kb-video-wrap .kb-block-drag-handle,
    #word-editor-canvas .kb-cols .kb-block-drag-handle,
    #word-editor-canvas .kb-cols-3 .kb-block-drag-handle,
    #word-editor-canvas .kb-cols-4 .kb-block-drag-handle { top: 60px; left: 6px; }
    #word-editor-canvas .kb-resource-card .kb-block-move-up   { top: 6px;  left: 6px; }
    #word-editor-canvas .kb-resource-card .kb-block-move-down { top: 24px; left: 6px; }
    #word-editor-canvas .kb-resource-card .kb-block-duplicate { top: 42px; left: 6px; }
    #word-editor-canvas .kb-resource-card .kb-block-drag-handle { top: 60px; left: 6px; }
    #word-editor-canvas .kb-pagelink .kb-block-move-up   { top: 50%; right: 30px; transform: translateY(-50%); }
    #word-editor-canvas .kb-pagelink .kb-block-move-down { top: 50%; right: 54px; transform: translateY(-50%); }
    #word-editor-canvas .kb-pagelink .kb-block-duplicate { top: 50%; right: 78px; transform: translateY(-50%); }
    #word-editor-canvas .kb-pagelink .kb-block-drag-handle { top: 50%; right: 102px; transform: translateY(-50%); }
    /* Drag handle is the one move-cluster button that opts the block INTO native HTML5 drag-and-
       drop (see startBlockDrag()/wireBlockDragReorder() in 08_word_page_creator.js) -- every other
       .kb-block-move button stays click-only, so this one gets its own grab/grabbing cursor and a
       distinct icon instead of reusing .kb-block-move's default pointer cursor. The block being
       dragged gets a dashed-outline ghost treatment so it's clear which one will land where. */
    #word-editor-canvas .kb-block-drag-handle { cursor: grab; font-size: 11px; }
    #word-editor-canvas .kb-block-drag-handle:active { cursor: grabbing; }
    #word-editor-canvas .kb-block-dragging { opacity: 0.35; outline: 2px dashed #2563eb; outline-offset: 2px; }
    /* In-page section nav: a row of pill links that scroll (never navigate -- see
       scrollToPageAnchor() in 08_word_page_creator.js) to headings further down the SAME page.
       Shared under .word-canvas (not editor-only) since the pills ARE the real published content --
       only the delete/move overlay buttons below are editor-only, same convention as every other
       block type above. */
    .word-canvas .kb-section-nav { display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.75rem 1rem; margin: 1rem 0; border: 1px solid #e2e8f0; border-radius: 10px; background: #f8fafc; }
    .word-canvas .kb-section-nav-link { font-size: 12px; font-weight: 700; padding: 0.35rem 0.85rem; border-radius: 999px; background: #fff; border: 1px solid #cbd5e1; color: #334155; text-decoration: none; cursor: pointer; }
    .word-canvas .kb-section-nav-link:hover { background: #0f172a; color: #fff; border-color: #0f172a; }
    /* Same non-editable-island overlay pattern as the table/video/columns wrappers above. */
    #word-editor-canvas .kb-section-nav { position: relative; }
    #word-editor-canvas .kb-section-nav .kb-block-delete { top: 6px; right: 6px; }
    #word-editor-canvas .kb-section-nav .kb-block-move-up   { top: 6px;  left: 6px; }
    #word-editor-canvas .kb-section-nav .kb-block-move-down { top: 24px; left: 6px; }
    #word-editor-canvas .kb-section-nav .kb-block-duplicate { top: 42px; left: 6px; }
    #word-editor-canvas .kb-section-nav .kb-block-drag-handle { top: 60px; left: 6px; }
    /* Click-to-select for the video embed, same idea as img.kb-selected below -- the iframe itself
       is pointer-events:none ONLY inside the editor (never on the public page, where it must stay
       playable), so a click lands on the wrapper div underneath instead of being swallowed by the
       cross-origin YouTube document, which otherwise can't dispatch click events back out to this
       page's own listeners at all. */
    #word-editor-canvas .kb-video-wrap iframe { pointer-events: none; }
    #word-editor-canvas .kb-video-wrap.kb-selected { outline: 3px solid #f59e0b; outline-offset: 2px; }
    #word-editor-canvas details.kb-faq { display: block; }
    #word-editor-canvas details.kb-faq > *:not(summary) { display: block; }
    /* A blank page should read like a blank Word/Docs page, not an unlabeled empty box. Targets
       the exact "nothing typed yet" shape (a lone empty paragraph) rather than :empty, since the
       seeded <p><br></p> always has the <br> as a child. */
    #word-editor-canvas > p:only-child:has(> br:only-child):before {
      content: "Type or paste your page content here. You can also paste or drag pictures in.";
      color: #94a3b8; pointer-events: none;
    }
    .page-tile-img { aspect-ratio: 16 / 9; object-fit: cover; width: 100%; }
    .word-canvas figure.kb-figure { margin: 1rem 0; }
    .word-canvas figure.kb-figure img { max-width: 100%; height: auto; border-radius: 10px; border: 1px solid #e2e8f0; display: block; }
    .word-canvas figure.kb-figure.kb-small img { max-width: 320px; }
    .word-canvas figure.kb-figure.kb-medium img { max-width: 560px; }
    .word-canvas figure.kb-figure figcaption { font-size: 12px; color: #64748b; margin-top: 0.35rem; }
    /* Generic "link selected text to any web address" tool -- distinct blue from the amber/red PDF-
       link badge (openLinkPdfModal) so the two are visually distinguishable at a glance in saved
       content: amber+badge means "opens one of our PDFs", plain blue underline means "goes to an
       external web address". */
    .word-canvas .kb-ext-link { color: #2563eb; text-decoration: underline; }
    .word-canvas .kb-ext-link:hover { color: #1d4ed8; }
    #word-editor-canvas figure.kb-figure img { cursor: pointer; }
    #word-editor-canvas figure.kb-figure img.kb-selected { outline: 3px solid #f59e0b; outline-offset: 2px; }
    /* Free-form image resize handles -- created only while an image is selected (see
       addImageResizeHandles() in 08_word_page_creator.js), positioned in JS at the image's own
       corners rather than anchored by CSS, since the figure's box also includes the figcaption
       below the picture. */
    #word-editor-canvas .kb-img-handle {
      position: absolute; width: 12px; height: 12px; background: #2563eb; border: 2px solid #fff;
      border-radius: 999px; z-index: 5; box-shadow: 0 0 0 1px rgba(15, 23, 42, .25);
    }
    #word-editor-canvas .kb-img-handle-nw, #word-editor-canvas .kb-img-handle-se { cursor: nwse-resize; }
    #word-editor-canvas .kb-img-handle-ne, #word-editor-canvas .kb-img-handle-sw { cursor: nesw-resize; }

    /* ---------- New: text alignment classes (safety net alongside inline execCommand) ---------- */
    .word-canvas .kb-align-left   { text-align: left; }
    .word-canvas .kb-align-center { text-align: center; }
    .word-canvas .kb-align-right  { text-align: right; }
    .word-canvas .kb-align-justify{ text-align: justify; }

    /* ---------- New: extra column variants (existing .kb-cols is 2-col, kept) ---------- */
    .word-canvas .kb-cols-3 { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1rem; margin: 1rem 0; position: relative; }
    .word-canvas .kb-cols-4 { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; margin: 1rem 0; position: relative; }
    /* !important for the same reason as the .kb-cols breakpoint above -- a dynamic column count
       set via inline style must still collapse on tablet/phone widths. */
    @media (max-width: 900px) { .word-canvas .kb-cols-3, .word-canvas .kb-cols-4 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; } }
    @media (max-width: 640px) { .word-canvas .kb-cols-3, .word-canvas .kb-cols-4 { grid-template-columns: minmax(0, 1fr) !important; } }

    /* ---------- New: optional column border (admin-toggled per block, off by default — see
       columnsAction('toggleBorder') in js/08_word_page_creator.js) ---------- */
    .word-canvas .kb-cols.kb-bordered > div,
    .word-canvas .kb-cols-3.kb-bordered > div,
    .word-canvas .kb-cols-4.kb-bordered > div { border: 1px solid #e2e8f0; border-radius: 10px; padding: 1rem; }

    /* ---------- New: magazine / resource grid (auto-flow of cards) ---------- */
    .word-canvas .kb-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; margin: 1rem 0; }
    #word-editor-canvas .kb-cols-3 > div,
    #word-editor-canvas .kb-cols-4 > div,
    #word-editor-canvas .kb-grid   > * { outline: 1px dashed #cbd5e1; outline-offset: 4px; min-height: 2rem; }

    /* ---------- New: extra image sizes ---------- */
    .word-canvas figure.kb-figure.kb-icon  img { max-width: 80px;  }
    .word-canvas figure.kb-figure.kb-tiny  img { max-width: 140px; }
    .word-canvas figure.kb-figure.kb-large img { max-width: 780px; }

    /* ---------- New: image alignment (left/right float, center) ---------- */
    .word-canvas figure.kb-figure.kb-left  { float: left;  margin: 0.25rem 1rem 0.5rem 0; max-width: 45%; }
    .word-canvas figure.kb-figure.kb-right { float: right; margin: 0.25rem 0 0.5rem 1rem; max-width: 45%; }
    .word-canvas figure.kb-figure.kb-center { margin-left: auto; margin-right: auto; text-align: center; }
    .word-canvas figure.kb-figure.kb-center img { margin-left: auto; margin-right: auto; }
    /* self-clear so floated images don't collide with next block */
    .word-canvas .kb-clear { clear: both; display: block; height: 0; }

    /* ---------- New: Resource card (image + text + optional PDF/page link) ---------- */
    .word-canvas .kb-resource-card {
      display: flex; flex-direction: column; background: #fff; border: 1px solid #e2e8f0;
      border-radius: 12px; overflow: hidden; box-shadow: 0 1px 3px rgba(15,23,42,.06);
      transition: border-color .15s, box-shadow .15s, transform .15s;
      margin: 0.75rem 0; text-decoration: none; color: inherit;
    }
    .word-canvas .kb-resource-card:hover { border-color: #f59e0b; box-shadow: 0 4px 10px rgba(245,158,11,.15); transform: translateY(-1px); }
    /* Portrait-ish, like an A4/document cover (was 16:10 landscape, which cropped the
       top/bottom off most PDF cover art — exam-prep PDFs are almost always portrait). */
    .word-canvas .kb-resource-thumb {
      width: 100%; aspect-ratio: 3 / 4; background: #fef3c7 center/cover no-repeat;
      display: flex; align-items: center; justify-content: center; font-size: 2.25rem;
    }
    .word-canvas .kb-resource-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
    .word-canvas .kb-resource-body { padding: 0.75rem 0.9rem 0.9rem; display: flex; flex-direction: column; flex: 1; }
    .word-canvas .kb-resource-title { font-weight: 800; color: #0f172a; font-size: 0.95rem; line-height: 1.3; margin: 0 0 0.3rem; }
    .word-canvas .kb-resource-desc  { font-size: 12.5px; color: #475569; margin: 0 0 0.5rem; line-height: 1.45; }
    /* A real button — solid fill, full width, high contrast — not a small pill tag.
       Pinned to the bottom of the card (margin-top: auto) so titles of different
       lengths don't push it to different heights across a row of cards. */
    .word-canvas .kb-resource-cta {
      display: block; width: 100%; text-align: center; box-sizing: border-box;
      font-size: 13px; font-weight: 800; letter-spacing: 0.01em;
      color: #fff; background: #f59e0b; border: none; border-radius: 8px;
      padding: 9px 14px; margin-top: auto; cursor: pointer;
      transition: background-color .15s, transform .15s;
    }
    .word-canvas a:hover .kb-resource-cta,
    .word-canvas .kb-resource-card:hover .kb-resource-cta { transform: translateY(-1px); }
    .word-canvas .kb-resource-cta.pdf  { background: #dc2626; }
    .word-canvas .kb-resource-cta.pdf:hover   { background: #b91c1c; }
    .word-canvas .kb-resource-cta.page { background: #2563eb; }
    .word-canvas .kb-resource-cta.page:hover  { background: #1d4ed8; }
    /* Coming Soon: independent of "Links to" (data-target-type) -- see buildResourceCardHtml's
       comment. Muted card + a small badge instead of (or alongside, for a "page" target) the CTA. */
    .word-canvas .kb-resource-card.is-coming-soon { background: #f8fafc; }
    .word-canvas .kb-resource-card.is-coming-soon .kb-resource-thumb { filter: grayscale(60%) opacity(.75); }
    .word-canvas .kb-resource-card.is-coming-soon:hover { border-color: #e2e8f0; box-shadow: 0 1px 3px rgba(15,23,42,.06); transform: none; }
    .word-canvas .kb-resource-badge {
      display: inline-block; align-self: flex-start; font-size: 11px; font-weight: 800;
      color: #64748b; background: #f1f5f9; border: 1px solid #e2e8f0; border-radius: 99px;
      padding: 2px 10px; margin-bottom: 0.5rem;
    }

    /* editor-only guides for resource cards. The card's own public-render rule
       (.word-canvas .kb-resource-card, above) sets overflow:hidden so the thumbnail's corners
       clip to the card's rounded border -- but that same rule clips this ::before badge too,
       since it's a child of the card, straddling the top edge at top:-10px to read as a ribbon
       tag. Overriding overflow back to visible here (editor-only) lets the badge sit fully above
       the border instead of having its top half sliced off. */
    #word-editor-canvas .kb-resource-card { position: relative; overflow: visible; outline: 1px dashed #cbd5e1; outline-offset: 3px; }
    #word-editor-canvas .kb-resource-card::before {
      content: attr(data-editor-label); position: absolute; top: -10px; left: 8px;
      font-size: 10px; font-weight: 800; background: #f59e0b; color: #0f172a;
      padding: 1px 6px; border-radius: 99px; z-index: 2;
    }

    /* Test paper card: rich, always-live preview tile embedded in a custom page via the Word
       Page Editor's "📝 Test paper card" button (see testPaperCardHtml/hydratePageCards in
       js/08_word_page_creator.js). Indigo accent distinguishes it from the amber page-card/
       resource-card family since it links out to a timed exam rather than another page/PDF. */
    .word-canvas .kb-paper-card {
      display: flex; flex-direction: column; gap: 4px; background: #fff;
      border: 1px solid #e2e8f0; border-radius: 12px; padding: 14px 16px;
      box-shadow: 0 1px 3px rgba(15,23,42,.06); text-decoration: none; color: inherit;
      transition: border-color .15s, box-shadow .15s, transform .15s; margin: 0.75rem 0;
    }
    .word-canvas .kb-paper-card:hover { border-color: #6366f1; box-shadow: 0 4px 10px rgba(99,102,241,.15); transform: translateY(-1px); }
    .word-canvas .kb-paper-card-head { display: flex; align-items: center; justify-content: space-between; }
    .word-canvas .kb-paper-card-badge { font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .03em; background: #e0e7ff; color: #3730a3; padding: 2px 10px; border-radius: 99px; }
    .word-canvas .kb-paper-card-price { font-size: 12px; font-family: monospace; font-weight: 800; color: #0f172a; }
    .word-canvas .kb-paper-card-price.is-free { color: #059669; }
    .word-canvas .kb-paper-card-price.is-bundle { color: #7c3aed; }
    .word-canvas .kb-paper-card-title { font-size: 15px; font-weight: 700; color: #0f172a; margin-top: 4px; }
    .word-canvas .kb-paper-card-meta { font-size: 11px; font-family: monospace; color: #64748b; }
    .word-canvas .kb-paper-card-cta { margin-top: 10px; align-self: flex-start; font-size: 11px; font-weight: 800; color: #4338ca; }
    .word-canvas .kb-paper-card:hover .kb-paper-card-cta { text-decoration: underline; }
    /* Coming Soon: muted + non-interactive (testPaperCardHtml drops the href/onclick entirely for
       this state, so there's no hover affordance to suppress here beyond the color/cursor). */
    .word-canvas .kb-paper-card.is-coming-soon { cursor: default; background: #f8fafc; }
    .word-canvas .kb-paper-card.is-coming-soon:hover { border-color: #e2e8f0; box-shadow: 0 1px 3px rgba(15,23,42,.06); transform: none; }
    .word-canvas .kb-paper-card-price.is-coming-soon { color: #94a3b8; }
    .word-canvas .kb-paper-card.is-coming-soon .kb-paper-card-cta { color: #94a3b8; }
    .word-canvas .kb-paper-card.is-coming-soon:hover .kb-paper-card-cta { text-decoration: none; }
    /* Revision notes button: sits just below the (possibly Coming Soon) tile, same family but its
       own small pill rather than part of the card link, since the two need independent click
       targets (see testPaperCardHtml -- it's a sibling, not nested inside the <a>). */
    .word-canvas .kb-paper-card-notes-btn {
      display: block; width: 100%; margin-top: -0.5rem; margin-bottom: 0.75rem;
      font-size: 11px; font-weight: 800; text-align: center; cursor: pointer;
      color: #1d4ed8; background: #eff6ff; border: 1px solid #bfdbfe; border-radius: 8px;
      padding: 6px 10px; transition: background-color .15s;
    }
    .word-canvas .kb-paper-card-notes-btn:hover { background: #dbeafe; }

    /* ---------- Home page: ticker, stats, section styling ---------- */
    @keyframes ticker-scroll { from { transform: translateX(100%); } to { transform: translateX(-100%); } }
    .animate-ticker { display: inline-block; padding-left: 100%; animation: ticker-scroll 45s linear infinite; }
    .animate-ticker:hover { animation-play-state: paused; }
    .animate-ticker .tick-item { display: inline-block; margin-right: 3rem; }
    .animate-ticker .tick-item .dot { display: inline-block; width: 6px; height: 6px; border-radius: 999px; background: #f59e0b; margin: 0 8px 2px 0; vertical-align: middle; }

    .home-stat b { display: block; font-size: 1.8rem; font-weight: 900; color: #0f172a; line-height: 1.1; }
    .home-stat b .accent { color: #b45309; }
    .home-stat span { display: block; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: #64748b; margin-top: 2px; font-weight: 700; }

    .home-news-card { background: #fff; border: 1px solid #e2e8f0; border-radius: 14px; padding: 16px; transition: border-color .15s, box-shadow .15s, transform .15s; }
    .home-news-card:hover { border-color: #f59e0b; box-shadow: 0 6px 14px rgba(15,23,42,.08); transform: translateY(-2px); }
    .home-news-card .date { font-size: 11px; font-weight: 800; color: #64748b; letter-spacing: .04em; text-transform: uppercase; }
    .home-news-card .badge { display: inline-block; margin-left: 6px; font-size: 10px; font-weight: 800; padding: 2px 8px; border-radius: 999px; background: #fef3c7; color: #92400e; text-transform: uppercase; letter-spacing: .04em; }
    .home-news-card .badge.exam { background: #dbeafe; color: #1e3a8a; }
    .home-news-card .badge.result { background: #dcfce7; color: #065f46; }
    .home-news-card .badge.alert { background: #fee2e2; color: #991b1b; }
    .home-news-card h4 { font-weight: 800; color: #0f172a; margin: 6px 0 4px; font-size: 15px; line-height: 1.35; }
    .home-news-card p { font-size: 12.5px; color: #475569; line-height: 1.5; }
    .home-news-card a.more { display: inline-block; margin-top: 6px; font-size: 12px; font-weight: 700; color: #b45309; }

    .home-why-card { background: #fff; border: 1px solid #e2e8f0; border-radius: 14px; padding: 18px 16px; text-align: center; }
    .home-why-card .icon { font-size: 30px; margin-bottom: 6px; }
    .home-why-card h4 { font-weight: 800; color: #0f172a; margin: 6px 0 4px; font-size: 14px; }
    .home-why-card p { font-size: 12px; color: #64748b; line-height: 1.5; }

    .home-topper-card { background: #fff; border: 1px solid #e2e8f0; border-radius: 14px; padding: 16px; display: flex; gap: 12px; align-items: flex-start; }
    .home-topper-card .avatar { width: 56px; height: 56px; border-radius: 999px; object-fit: cover; background: #fef3c7 center/cover no-repeat; flex-shrink: 0; display: flex; align-items: center; justify-content: center; font-weight: 900; color: #92400e; font-size: 18px; }
    .home-topper-card h4 { font-weight: 800; color: #0f172a; font-size: 14px; }
    .home-topper-card .exam-line { font-size: 11px; color: #64748b; font-weight: 700; margin-top: 1px; }
    .home-topper-card .rank { display: inline-block; margin-top: 4px; font-size: 10px; font-weight: 800; padding: 2px 8px; border-radius: 999px; background: #fef3c7; color: #92400e; }
    .home-topper-card blockquote { font-size: 12.5px; color: #475569; margin-top: 8px; font-style: italic; line-height: 1.5; }

    .home-faq-item { background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 12px 16px; }
    .home-faq-item summary { font-weight: 700; color: #0f172a; cursor: pointer; font-size: 14px; list-style: none; display: flex; justify-content: space-between; align-items: center; gap: 8px; }
    .home-faq-item summary::-webkit-details-marker { display: none; }
    .home-faq-item summary::after { content: '+'; color: #b45309; font-size: 20px; font-weight: 700; }
    .home-faq-item[open] summary::after { content: '−'; }
    .home-faq-item[open] summary { margin-bottom: 8px; }
    .home-faq-item p { font-size: 13px; color: #475569; line-height: 1.55; }

    .home-banner-carousel { position: relative; overflow: hidden; border-radius: 16px; background: #0f172a; }
    .home-banner-track { display: flex; transition: transform .45s ease; }
    .home-banner-slide { flex: 0 0 100%; position: relative; display: block; overflow: hidden; }
    /* Blurred-backdrop fill (same trick Spotify/YouTube use for mismatched aspect ratios): a
       blurred, scaled-up copy of the SAME banner image sits behind, filling the whole box with
       no visible seams, while the sharp, uncropped original sits on top via object-fit: contain.
       aspect-ratio keeps the box a consistent, responsive "banner" shape across screen sizes.
       `inset: -5%` + `scale(1.1)` on the backdrop overshoots the box so the blur's own soft edge
       (which would otherwise show a band of the carousel's background color) never shows, and
       `overflow: hidden` on the slide itself keeps that overshoot from bleeding into neighboring
       slides. Nothing is ever cropped off the SHARP foreground image -- only its blurred backdrop
       copy is intentionally over-scaled. */
    .home-banner-slide-bg { position: absolute; inset: -5%; background-size: cover; background-position: center; background-repeat: no-repeat; filter: blur(28px) brightness(0.55); transform: scale(1.1); }
    .home-banner-slide-img { position: relative; z-index: 1; width: 100%; aspect-ratio: 2.4 / 1; object-fit: contain; display: block; }
    .home-banner-caption { position: absolute; z-index: 2; left: 0; right: 0; bottom: 0; padding: 14px 18px; font-size: 13px; font-weight: 700; color: #fff; background: linear-gradient(to top, rgba(15,23,42,.85), rgba(15,23,42,0)); }
    .home-banner-arrow { position: absolute; top: 50%; transform: translateY(-50%); width: 34px; height: 34px; border-radius: 999px; background: rgba(15,23,42,.55); color: #fff; border: none; font-size: 20px; line-height: 1; cursor: pointer; display: flex; align-items: center; justify-content: center; }
    .home-banner-arrow:hover { background: rgba(15,23,42,.8); }
    .home-banner-arrow-prev { left: 10px; }
    .home-banner-arrow-next { right: 10px; }
    .home-banner-dots { display: none; justify-content: center; gap: 6px; padding: 10px 0; background: #0f172a; }
    .home-banner-dots:not(:empty) { display: flex; }
    .home-banner-dot { width: 7px; height: 7px; border-radius: 999px; background: rgba(255,255,255,.5); border: none; padding: 0; cursor: pointer; }
    .home-banner-dot.active { background: #f59e0b; width: 18px; border-radius: 999px; }

    .hc-editor-row { display: grid; grid-template-columns: 1fr auto; gap: 8px; align-items: start; padding: 8px; border: 1px solid #e2e8f0; border-radius: 10px; background: #f8fafc; }
    .hc-editor-row + .hc-editor-row { margin-top: 6px; }
    .hc-editor-row .hc-fields { display: grid; gap: 6px; }

    /* PDF publishing wizard — step indicator */
    .wiz-step { color: #94a3b8; padding: 2px 4px; letter-spacing: .04em; }
    .wiz-step.wiz-step-active { color: #b45309; }
    .wiz-step.wiz-step-done { color: #059669; }

    /* Test paper studio */
    .studio-tab { background: #f1f5f9; color: #475569; }
    .studio-tab.active { background: #0f172a; color: #fff; }
    .studio-lang { background: #fff; color: #64748b; }
    .studio-lang.active { background: #f59e0b; color: #0f172a; }
    .studio-qrow { background: #fff; border: 1px solid #e2e8f0; border-radius: 12px; padding: 10px 12px; display: grid; grid-template-columns: auto auto 1fr auto; gap: 10px; align-items: start; cursor: pointer; transition: border-color .1s, box-shadow .1s; }
    .studio-qrow:hover { border-color: #f59e0b; box-shadow: 0 2px 6px rgba(15,23,42,.05); }
    .studio-qrow.selected { background: #fefce8; border-color: #eab308; }
    .studio-qrow.error   { background: #fef2f2; border-color: #fca5a5; }
    .studio-qrow .qnum   { font-family: ui-monospace, monospace; font-weight: 800; color: #64748b; font-size: 12px; padding-top: 2px; min-width: 26px; text-align: right; }
    .studio-qrow .qbody  { min-width: 0; }
    .studio-qrow .qtext  { font-weight: 600; color: #0f172a; font-size: 13px; line-height: 1.4; }
    .studio-qrow .qmeta  { display: flex; gap: 8px; align-items: center; margin-top: 6px; font-size: 11px; color: #64748b; flex-wrap: wrap; }
    .studio-qrow .qopts  { color: #475569; margin-top: 4px; font-size: 11.5px; line-height: 1.5; }
    .studio-qrow .qopts .correct { color: #059669; font-weight: 700; }
    .studio-qrow .qerr   { color: #991b1b; font-weight: 700; font-size: 11px; margin-top: 4px; }
    .studio-qrow .qactions { display: flex; flex-direction: column; gap: 3px; }
    .studio-qrow .qactions button { padding: 2px 6px; border: 1px solid #e2e8f0; border-radius: 6px; font-size: 11px; font-weight: 700; color: #64748b; background: #fff; cursor: pointer; }
    .studio-qrow .qactions button:hover { border-color: #64748b; color: #0f172a; }
    .studio-qrow .qthumb { max-width: 60px; max-height: 40px; border-radius: 4px; border: 1px solid #e2e8f0; margin-top: 6px; }
    .badge-both { background: #dcfce7; color: #065f46; }
    .badge-en   { background: #fef3c7; color: #92400e; }
    .badge-none { background: #fee2e2; color: #991b1b; }
    .qbadge { display: inline-block; padding: 1px 6px; border-radius: 999px; font-weight: 800; font-size: 9px; text-transform: uppercase; letter-spacing: .05em; }
    /* Both-mode two-column preview */
    .qboth { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
    .qboth .qcol { min-width: 0; padding-right: 8px; }
    .qboth .qcol + .qcol { border-left: 1px dashed #e2e8f0; padding-left: 12px; padding-right: 0; }

    /* ============================================================
       VISUAL POLISH LAYER — smooth transitions + a more premium look.
       Purely additive: every rule here targets classes/patterns that
       already exist throughout the page (buttons, rounded cards, modal
       backdrops, view containers). Nothing here changes any element's
       class attribute, id, or behavior — it only adds transitions,
       shadows, and entrance animations on top of what's already there.
       Safe to delete this whole block to fully revert the visual pass
       without touching anything else in the file.
       ============================================================ */

    /* Smooth page scrolling + a nicer scrollbar than the OS default */
    html { scroll-behavior: smooth; }
    * { scrollbar-width: thin; scrollbar-color: #cbd5e1 transparent; }
    ::-webkit-scrollbar { width: 9px; height: 9px; }
    ::-webkit-scrollbar-track { background: transparent; }
    ::-webkit-scrollbar-thumb { background: #cbd5e1; border-radius: 999px; border: 2px solid transparent; background-clip: content-box; }
    ::-webkit-scrollbar-thumb:hover { background: #94a3b8; background-clip: content-box; }
    ::selection { background: #f59e0b; color: #0f172a; }

    /* Smooth, springy transitions on every interactive element — scoped to specific tags rather
       than a universal `*` selector so it never interferes with the ticker's continuous scroll
       animation or any other keyframe-driven element. */
    button, a, input, select, textarea, [role="button"], .category-tab-btn, .admin-nav-btn {
      transition: background-color .18s ease, border-color .18s ease, color .18s ease,
                  box-shadow .22s cubic-bezier(.16,1,.3,1), transform .14s ease, opacity .18s ease;
    }
    button:not(:disabled):active, [role="button"]:active { transform: scale(0.97); }
    a:not(.no-lift):active { transform: scale(0.98); }

    /* A gentle lift + deeper shadow on the site's main call-to-action buttons, so the primary
       actions (Publish, Save, Buy Now, Approve…) feel a level more premium than a flat color swap. */
    button.bg-amber-500:hover, button.bg-slate-900:hover, button.bg-emerald-600:hover,
    button.bg-emerald-500:hover, button.bg-rose-600:hover, .category-tab-btn:hover {
      transform: translateY(-1.5px);
      box-shadow: 0 8px 20px -6px rgba(15,23,42,.25);
    }
    button.bg-amber-500:active, button.bg-slate-900:active, button.bg-emerald-600:active {
      transform: translateY(0) scale(0.97);
      box-shadow: 0 2px 6px -2px rgba(15,23,42,.2);
    }

    /* Refined focus states — visible, on-brand, and consistent everywhere instead of each browser's
       default outline (still fully keyboard-accessible, just better looking). */
    button:focus-visible, a:focus-visible, input:focus-visible, select:focus-visible,
    textarea:focus-visible, [role="button"]:focus-visible {
      outline: 2px solid #f59e0b; outline-offset: 2px; border-radius: 4px;
    }

    /* Cards and panels: every rounded-2xl/rounded-xl bordered box across the whole site (bundle
       cards, test paper cards, admin panels, resource cards) gets a smooth shadow/border transition
       and a subtle hover lift where it makes sense (clickable cards specifically). */
    .rounded-2xl.border, .rounded-xl.border {
      transition: box-shadow .25s cubic-bezier(.16,1,.3,1), border-color .25s ease, transform .25s cubic-bezier(.16,1,.3,1);
    }
    a.rounded-2xl.border:hover, a.rounded-xl.border:hover,
    .home-news-card:hover, .studio-qrow:hover {
      box-shadow: 0 12px 28px -10px rgba(15,23,42,.18);
    }
    [data-panel].rounded-2xl { box-shadow: 0 1px 3px rgba(15,23,42,.04), 0 1px 2px rgba(15,23,42,.03); }
    [data-panel].rounded-2xl:hover { box-shadow: 0 4px 16px -4px rgba(15,23,42,.08); }

    /* Modal entrance: a soft pop-in instead of an instant snap-to-visible. CSS animations restart
       automatically whenever an element re-enters the render tree (goes from display:none via the
       existing .hidden toggle to displayed), so this needs no JavaScript changes at all — it rides
       on the exact same show/hide mechanism every modal already uses. */
    @keyframes modalBackdropIn { from { opacity: 0; } to { opacity: 1; } }
    @keyframes modalPopIn {
      from { opacity: 0; transform: scale(.95) translateY(10px); }
      to   { opacity: 1; transform: scale(1)   translateY(0); }
    }
    .fixed.inset-0.backdrop-blur-sm:not(.hidden) { animation: modalBackdropIn .2s ease-out; }
    .fixed.inset-0.backdrop-blur-sm:not(.hidden) > .bg-white.rounded-2xl,
    .fixed.inset-0.backdrop-blur-sm:not(.hidden) > .bg-white.rounded-xl {
      animation: modalPopIn .28s cubic-bezier(.16,1,.3,1);
    }

    /* View transitions: a gentle fade+rise whenever the app switches between Home / Tests /
       Dashboard / Admin / the exam engine / the paper studio — same "animate on becoming visible"
       technique as the modals above, applied to every top-level view container. */
    @keyframes viewFadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
    [id^="view-"]:not(.hidden) { animation: viewFadeIn .3s cubic-bezier(.16,1,.3,1); }

    /* Toasts and floating banners (error toasts, exam warnings, PWA prompt, student-preview banner)
       ease in rather than popping in abruptly. */
    @keyframes floatUpIn { from { opacity: 0; transform: translateY(10px) scale(.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
    #pwa-install-chip, #student-preview-banner, #pdf-wizard-modal { animation: floatUpIn .25s ease-out; }

    /* Bundle price cards and the home pass strip get a slightly richer resting shadow so pricing
       tiles read as tappable products rather than flat information boxes. */
    #home-bundles > div, .home-news-card, #tests-catalog-grid > div {
      transition: box-shadow .25s cubic-bezier(.16,1,.3,1), transform .25s cubic-bezier(.16,1,.3,1), border-color .25s ease;
    }
    #home-bundles > div:hover, #tests-catalog-grid > div:hover {
      transform: translateY(-2px);
      box-shadow: 0 14px 30px -12px rgba(15,23,42,.18);
    }

    /* Admin sidebar: smoother active-state feel on nav switches (colors already swap via JS class
       toggles — this just makes the swap glide instead of snap, using the transition rule above). */
    .admin-nav-btn { border-radius: 10px; }

    /* Table rows and list rows get a soft hover tint instead of nothing, so dense admin lists
       (payments, students, templates) feel more responsive to the cursor. */
    tbody tr { transition: background-color .15s ease; }

    /* ============================================================
       PREMIUM DESIGN PASS — deepens the existing Evolve+ navy/gold
       identity rather than replacing it. Purely additive: new CSS
       classes on the hero (which needed real markup for the glow/
       texture layers — every existing id, onclick, and text node is
       untouched) plus rules targeting existing IDs/classes elsewhere.
       Nothing here changes behavior, data flow, or removes anything.
       ============================================================ */

    :root {
      --ink-950: #0B1220;   /* richer than flat slate-900 */
      --ink-900: #131B2E;
      --navy-800: #1B2A4A;
      --gold-500: #C89B3C;  /* metallic gold, replaces flat amber as the "premium" accent */
      --gold-400: #D4AF52;
      --gold-300: #E8C874;
      --paper: #FAF8F4;     /* warm off-white for section alternation */
    }

    /* ---- Hero: the one bold moment (see design rationale in chat) ---- */
    .hero-premium {
      background:
        radial-gradient(ellipse 80% 60% at 50% -10%, rgba(200,155,60,.18), transparent 60%),
        linear-gradient(180deg, var(--ink-950) 0%, var(--ink-900) 55%, var(--navy-800) 100%);
    }
    .hero-bg-img {
      position: absolute; inset: 0; width: 100%; height: 100%;
      object-fit: cover; object-position: center;
      opacity: 0; transition: opacity .5s ease;
    }
    #home-hero.has-photo .hero-bg-img { opacity: 1; }
    /* Dark scrim in the same ink/navy tones as .hero-premium's own gradient, so an admin-uploaded
       photo (any brightness/content) still reads as "the same premium dark hero" underneath it,
       and the existing text/buttons stay legible without needing per-photo tuning. */
    .hero-photo-overlay {
      position: absolute; inset: 0; pointer-events: none;
      background: linear-gradient(180deg, rgba(11,18,32,.90) 0%, rgba(11,18,32,.74) 45%, rgba(27,42,74,.92) 100%);
      opacity: 0; transition: opacity .5s ease;
    }
    #home-hero.has-photo .hero-photo-overlay { opacity: 1; }
    .hero-glow {
      position: absolute; inset: 0; pointer-events: none;
      background: radial-gradient(circle 420px at 50% 0%, rgba(232,200,116,.14), transparent 70%);
    }
    .hero-grain {
      position: absolute; inset: 0; pointer-events: none; opacity: .5;
      background-image:
        linear-gradient(rgba(255,255,255,.035) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,.035) 1px, transparent 1px);
      background-size: 42px 42px;
      mask-image: radial-gradient(ellipse 70% 70% at 50% 30%, black 0%, transparent 75%);
    }
    .hero-badge {
      background: linear-gradient(180deg, rgba(232,200,116,.14), rgba(200,155,60,.08));
      border: 1px solid rgba(212,175,82,.35);
      color: var(--gold-300);
    }
    .hero-rule {
      width: 44px; height: 2px;
      background: linear-gradient(90deg, transparent, var(--gold-400), transparent);
    }
    .hero-plus {
      background: linear-gradient(180deg, var(--gold-300), var(--gold-500));
      -webkit-background-clip: text; background-clip: text; color: transparent;
    }
    .hero-btn-primary {
      background: linear-gradient(180deg, var(--gold-300), var(--gold-500));
      color: var(--ink-950);
      box-shadow: 0 10px 24px -8px rgba(200,155,60,.5), inset 0 1px 0 rgba(255,255,255,.3);
      border: none;
    }
    .hero-btn-primary:hover { background: linear-gradient(180deg, #F0D488, var(--gold-400)); box-shadow: 0 14px 30px -6px rgba(200,155,60,.6), inset 0 1px 0 rgba(255,255,255,.35); }
    .hero-btn-secondary {
      background: transparent; color: #E2E8F0;
      border: 1.5px solid rgba(226,232,240,.28);
    }
    .hero-btn-secondary:hover { border-color: rgba(226,232,240,.55); background: rgba(255,255,255,.05); }

    /* ---- Typography: extend Playfair Display to every section heading, not just the hero/logo —
       one deliberate typographic identity instead of a single decorated moment. Targets the exact
       IDs already in the markup; the JS that fills in admin-edited titles is untouched. ---- */
    #home-whatsnew-title, #home-news-title, #home-why-title, #home-toppers-title,
    #home-faq-title, #home-cta-heading, #home-upcoming-title {
      font-family: 'Playfair Display', Georgia, serif;
      font-weight: 800;
      letter-spacing: -.01em;
    }

    /* ---- Section rhythm: intentional white/paper alternation instead of the previous
       accidental mix of slate-50/slate-100/white. Dark sections (ticker, CTA) are untouched. ---- */
    #home-passes { background: var(--paper); }
    #home-whatsnew { background: #fff; }
    #home-why { background: var(--paper); }
    #home-toppers { background: linear-gradient(180deg, #fff, var(--paper)); }
    #home-faq { background: #fff; }

    /* ---- Button hierarchy: primary actions (amber/gold fills) get a richer gradient + deeper
       shadow so they read as THE important action; plain slate-900 buttons stay a clean solid so
       the two tiers are visually distinct rather than same-weight color swaps. Scoped to `button`
       so it can never affect a non-interactive element that happens to share a color class. ---- */
    button.bg-amber-500 {
      background: linear-gradient(180deg, var(--gold-300), var(--gold-500)) !important;
      color: var(--ink-950) !important;
    }
    button.bg-amber-500:hover { background: linear-gradient(180deg, #F0D488, var(--gold-400)) !important; }
    button.bg-slate-900 { background: var(--ink-950) !important; }
    button.bg-slate-900:hover { background: var(--ink-900) !important; }

    /* ---- Featured card differentiation: avoids the generic "identical card kit" look where every
       card gets the same treatment regardless of importance. The Popular/featured bundle card gets
       a gold top accent; plain informational cards stay quiet. ---- */
    .bundle-featured { position: relative; border-color: var(--gold-400) !important; }
    .bundle-featured::before {
      content: ''; position: absolute; top: 0; left: 16px; right: 16px; height: 3px;
      background: linear-gradient(90deg, var(--gold-300), var(--gold-500));
      border-radius: 0 0 4px 4px;
    }

    /* ---- Admin console: same restrained gold/ink language on the primary "Dev Console" heading and
       the sidebar's active state, so the working tool doesn't feel like a separate, plainer product. ---- */
    #view-admin h2.text-2xl.font-black { font-family: 'Playfair Display', Georgia, serif; letter-spacing: -.01em; }
    .admin-nav-btn.bg-slate-900, .admin-nav-btn[class*="bg-slate-900"] { background: var(--ink-950) !important; }

    /* ---- Hand-rolled crop box for a pasted image (see openImageCropModal()/wireCropBoxDrag() in
       08_word_page_creator.js) -- #crop-stage holds the preview image at its natural display
       size; #crop-box is the draggable/resizable selection rectangle positioned over it in JS. ---- */
    #crop-stage { display: inline-block; max-width: 100%; }
    #crop-box { box-shadow: 0 0 0 9999px rgba(15, 23, 42, .45); cursor: move; }
    .kb-crop-handle {
      position: absolute; width: 14px; height: 14px; background: #f59e0b; border: 2px solid #fff;
      border-radius: 999px; box-shadow: 0 0 0 1px rgba(15, 23, 42, .25); margin: -7px;
    }
    .kb-crop-handle[data-corner="nw"] { top: 0; left: 0; cursor: nwse-resize; }
    .kb-crop-handle[data-corner="ne"] { top: 0; left: 100%; cursor: nesw-resize; }
    .kb-crop-handle[data-corner="sw"] { top: 100%; left: 0; cursor: nesw-resize; }
    .kb-crop-handle[data-corner="se"] { top: 100%; left: 100%; cursor: nwse-resize; }
