/* =================================================================
   LAYOUT — site-wide structural chrome
   -----------------------------------------------------------------
   The topbar/header, the main page grid, the chapter sidebar (plus
   its progress tracker and search), and the footer — the structural
   frame every page shares, as opposed to reusable widgets (see
   components.css) or content specific to one kind of page (see
   pages.css).
================================================================= */

/* ---------------------------------------------------------------
   Top bar
--------------------------------------------------------------- */
.topbar {
  background: var(--surface);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
  transition: background 0.2s ease, backdrop-filter 0.2s ease, box-shadow 0.2s ease;
}
/* Glassmorphism kicks in only once the page has actually scrolled —
   a solid header reads more clearly at rest, so the translucent/
   blurred look is reserved for scrolled state as a deliberate accent
   rather than applied everywhere. */
.topbar.is-scrolled {
  /* rgba(var(--color-surface-rgb), 0.72) rather than a hardcoded
     rgba(255, 255, 255, 0.72) — the hardcoded white made this header
     switch to a light-only translucent background the moment the
     page was scrolled, completely ignoring dark theme even though
     the header is correctly dark at rest. rgba() needs raw numeric
     channels (can't take a hex/rgb() custom property and add its own
     alpha on top), hence the separate --color-surface-rgb token (see
     tokens.css) rather than reusing --color-surface directly — the
     @supports fallback right below this rule already correctly used
     var(--surface) for browsers without backdrop-filter support,
     which is what made it clear this rule specifically, not the
     general approach, was the one still hardcoded. */
  background: rgba(var(--color-surface-rgb), 0.72);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  box-shadow: 0 1px 12px rgba(15, 16, 19, 0.06);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .topbar.is-scrolled { background: var(--surface); }
}
/* 1280px per explicit request (was 1400px), applied site-wide to
   every page template. */
.topbar__inner {
  max-width: 1280px;
  margin: 0 auto;
  padding: 14px 24px;
  display: flex;
  align-items: center;
  gap: 28px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  color: var(--ink);
  font-weight: 600;
  white-space: nowrap;
}
.brand:hover { text-decoration: none; }
.brand__logo {
  /* Small "2x" badge added to the left of the wordmark, matching the
     reference logo lockup: a dark, rounded-rect badge with the
     initials in white, bold, small-caps-style letter spacing. */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 26px;
  padding: 0 8px;
  border-radius: 7px;
  background: #0F172A;
  color: #fff;
  font-size: 0.72rem;
  font-weight: 800;
  letter-spacing: 0.03em;
  line-height: 1;
}
.brand__name {
  /* 1.2rem / #0e172b / bold per explicit request (was 1.02rem, inherited
     var(--ink), and the regular-weight override below it used to
     need; color was #4f46e5, replaced per a later explicit request).
     font-family override removed per explicit request — inherits the
     site's default body font instead of the monospace brand font. */
  font-size: 1.2rem;
  color: #0e172b;
  font-weight: 700;
}

.toplinks {
  flex: 1;
  min-width: 0;
}
.toplinks__list {
  display: flex;
  align-items: center;
  gap: 18px;
  font-size: 0.9rem;
  white-space: nowrap;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* Only relevant once .toplinks becomes an off-canvas drawer on
   mobile/tablet (see the ≤1024px rule below) — hidden by default so
   it doesn't show up as a stray × sitting among the desktop nav
   links, where there's no drawer to close. */
.toplinks__close { display: none; }
/* Restyled as a solid button matching the redesign mockup (see its
   Tailwind classes in header.php) — no color/font-size/font-weight
   here anymore, since those would otherwise conflict with (and, at
   equal specificity with this file loading after Tailwind's, quietly
   win over) the Tailwind utility classes doing that job now. Confirmed
   this class isn't used anywhere else on the site before removing
   its old styling, so nothing else depends on these properties. */
.toplinks__tool-link {
  display: inline-block;
  /* The Tailwind text-white utility on this link (in header.php) loses
     to tokens.css's unlayered `a { color: var(--accent) }` rule: Tailwind
     v4 wraps its utilities in `@layer utilities`, and an unlayered rule
     always outranks a layered one regardless of specificity — the same
     layer-vs-unlayered gotcha documented elsewhere in this project, just
     surfacing on a link element instead of a heading/paragraph this
     time. Restating the color here, unlayered and scoped to this one
     class, wins cleanly (0,1,0 beats `a`'s 0,0,1 too, so it isn't
     relying on load order alone). */
  color: #fff;
  /* 12px per explicit request — replaces the rounded-full (pill)
     Tailwind class this used to carry directly in header.php's
     markup, now removed there since this is the one place actually
     controlling its shape. */
  border-radius: 12px;
}
/* Explicit override per request ("click text underline not required")
   — tokens.css's global a:hover rule underlines every link on hover
   (which persists through a click on desktop), and this button is no
   exception without this reset. */
.toplinks__tool-link:hover { text-decoration: none; }

.topbar__right {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* ---------------------------------------------------------------
   Header hamburger (mobile/tablet): toggles the Tutorials nav
--------------------------------------------------------------- */
.topbar-hamburger {
  display: none;
  align-items: center;
  justify-content: center;
  /* 44x44 rather than the 36x36 this used to be — this button is
     display:none on desktop unconditionally (only ever shown at
     <=1024px, see responsive.css), so there's no desktop-density
     tradeoff to weigh; sizing it to the standard minimum touch
     target from the start costs nothing and avoids a fiddly tap
     target on the devices that are the only place it ever appears. */
  width: 44px;
  height: 44px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--ink);
  cursor: pointer;
}
.topbar-hamburger__bars { display: flex; flex-direction: column; gap: 3px; width: 16px; }
.topbar-hamburger__bars span { display: block; height: 2px; background: var(--ink); border-radius: 1px; }

/* ---------------------------------------------------------------
   Header dropdowns (Tutorials, language switcher)
--------------------------------------------------------------- */
.nav-dropdown { position: relative; }

.nav-dropdown__trigger {
  display: flex;
  align-items: center;
  gap: 5px;
  background: none;
  border: none;
  border-radius: 6px;
  /* Mockup shows only the active-section trigger (bold, var(--ink))
     in full dark ink; the others ("Tools", "Interview Q & A",
     "Exercise" when not current) sit in a lighter slate-600, sampled
     directly from the mockup's own pixels (rgb(71,85,105) = #475569)
     rather than reused from an existing token, since this specific
     shade wasn't already defined anywhere in the token set. The
     langswitch/themeswitch icon buttons override this with their own
     more-specific color rules below, so they're unaffected. */
  color: #475569;
  font-family: var(--sans);
  font-size: 0.9rem;
  font-weight: 500;
  cursor: pointer;
  padding: 6px 8px;
  white-space: nowrap;
  transition: background 0.12s ease, color 0.12s ease;
}
/* .themeswitch shares .nav-dropdown__trigger above for its base
   button reset (no native border, cursor, transition), but needs its
   own distinct look on top of that: the mockup this redesign matches
   shows it as a small rounded-square button with a light gray fill
   behind the icon, not the plain icon-on-transparent-background the
   shared rule alone produces (that styling suits the text dropdown
   triggers it was written for, not an icon-only button standing on
   its own in the header's corner). */
.themeswitch,
.langswitch {
  justify-content: center;
  width: 36px;
  height: 36px;
  padding: 0;
  /* 12px per explicit request (was 8px). */
  border-radius: 12px;
  /* Sampled directly from the mockup's own pixels rather than reused
     from an existing token — this site's --bg is close (#F4F5F3,
     warm gray) but not an exact match to the mockup's own color here
     (#F1F5F9, Tailwind's slate-100, a cooler gray), and the point of
     this pass is pixel-level accuracy against that specific image. */
  background: #F1F5F9;
}
.themeswitch:hover,
.langswitch:hover { background: var(--border); color: var(--ink); }
.nav-dropdown__trigger.is-active-section {
  /* Bold, accent-colored text at rest, no underline bar — pixel-sampled
     directly from the current target reference (#4F46E5, this site's
     own --accent), which shows the active nav item just as bold
     indigo text with no background box and no bar under it (the bar
     is reserved for :hover/:open, in the rule further below). This is
     at least the third documented reversal of this rule's color
     across this project (blue+underline → plain dark → accent again)
     as the reference the redesign is matched against has itself
     changed; if a future reference calls for reverting again, the
     git history has the intermediate versions' reasoning. */
  background: none;
  color: var(--accent);
  font-weight: 700;
}
/* Hover/focus/open now match the active-section state exactly — same
   accent text color, no background box and no underline bar (the
   ::after bar and the :hover/:open background this used to add were
   both removed on request: the active-section trigger itself has
   neither, so hovering or opening any *other* trigger should read as
   the identical plain color-only treatment, not a heavier box the
   active item doesn't even get). Note on :visited: these are
   <button> elements (Tutorials/Tools/Interview Prep/Exercise), not
   <a href> links — :visited is a browser-history concept that
   literally doesn't apply to buttons at all, regardless of styling;
   there's no browser history state here to visually distinguish. */
.nav-dropdown__trigger:hover,
.nav-dropdown__trigger:focus-visible,
.nav-dropdown.is-open .nav-dropdown__trigger,
.nav-dropdown__trigger.is-active-section:hover,
.nav-dropdown.is-open .nav-dropdown__trigger.is-active-section {
  background: none;
  color: var(--accent);
}
/* The langswitch/themeswitch icon buttons share .nav-dropdown__trigger
   for their base reset but keep their own distinct hover look (light
   gray fill, ink icon, no accent color) — restated after the shared
   hover rule above, at the same specificity, so source order lets
   this win for those two buttons specifically instead of losing to
   the shared rule the way it silently did before. */
.themeswitch:hover,
.themeswitch:focus-visible,
.langswitch:hover,
.langswitch:focus-visible,
/* langswitch's dropdown opens on hover too (see app.js), which adds
   .is-open to its parent .nav-dropdown — .nav-dropdown.is-open
   .nav-dropdown__trigger above is three classes deep (0,3,0) and beats
   plain .langswitch:hover (0,2,0), so this needs the same three-class
   depth to actually win here instead of silently losing to it. */
.nav-dropdown.is-open .langswitch { background: var(--border); color: var(--ink); }
.nav-dropdown__caret { display: inline-flex; transition: transform 0.18s ease; }
.nav-dropdown.is-open .nav-dropdown__caret { transform: rotate(180deg); }

.nav-dropdown__menu {
  position: absolute;
  top: calc(100% + 8px);
  left: 0;
  min-width: 150px;
  margin: 0;
  padding: 6px;
  list-style: none;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 8px 24px rgba(28, 27, 43, 0.08);
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.12s ease, transform 0.12s ease;
  z-index: 20;
}
.nav-dropdown__menu--right { left: auto; right: 0; }
.nav-dropdown.is-open .nav-dropdown__menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

/* Mega menu (Tutorials dropdown specifically) — same open/close
   mechanism as the plain .nav-dropdown__menu above (position,
   opacity/visibility toggled by the same .nav-dropdown.is-open
   class, same JS), just a much wider panel with grouped columns
   instead of a single flat list, since 11 courses in one plain
   dropdown was long enough to be hard to scan at a glance. */
.mega-menu {
  /* position: fixed and centered on the viewport, rather than
     absolute + anchored to the trigger's own left edge — requested
     explicitly as "full width," and the previous approach
     (clamp-based width, anchored to wherever the Tutorials trigger
     happens to sit in the header) could only ever be as wide as the
     safety margin from that anchor point allowed, never actually
     spanning the header the way a true full-width panel should.
     Centering on the viewport and matching the header's own 1280px
     max-width (see .topbar__inner) makes this read as part of the
     header's own width, not a narrower floating island below one
     button in it. transform: translateX(-50%) needs a plain
     translateY for the open/closed slide animation combined into the
     same transform property, not two separate declarations, since a
     later transform always fully replaces an earlier one rather than
     combining. */
  position: fixed;
  top: 65px;
  left: 50%;
  display: flex;
  gap: 32px;
  width: min(1280px, calc(100vw - 48px));
  max-width: calc(100vw - 48px);
  padding: 24px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 12px 32px rgba(28, 27, 43, 0.12);
  opacity: 0;
  visibility: hidden;
  transform: translateX(-50%) translateY(-4px);
  transition: opacity 0.12s ease, transform 0.12s ease;
  z-index: 20;
}
.nav-dropdown.is-open .mega-menu {
  opacity: 1;
  visibility: visible;
  /* translateX(-50%) still needed here too, matching the closed
     state's own transform — this rule fully replaces the closed
     state's transform value rather than adding to it, so dropping
     the X component here would snap the panel's centering off by
     half its own width the instant it opens. */
  transform: translateX(-50%) translateY(0);
}
.mega-menu__columns {
  /* flexbox rather than CSS Grid — after two grid-based fixes still
     didn't resolve visibly cut-off column content, actually rendering
     this (not just reasoning about it — see the toolchain notes in
     this project's own history) showed the grid collapsing to a
     single stacked column entirely rather than 4 side-by-side ones,
     consistent with an older or partial CSS Grid implementation
     falling back to normal block stacking. flexbox has been reliably,
     widely supported for far longer and achieves the identical "4
     equal columns" result here (flex: 1 on each .mega-menu__group,
     same min-width: 0 need as before) without depending on Grid's
     newer, less consistently-implemented track-sizing algorithm. */
  display: flex;
  gap: 24px;
  flex: 1;
  min-width: 0;
}
/* Flex items, like grid items, default to min-width: auto — meaning
   a grid item won't shrink below its content's intrinsic minimum
   width even though its track has already been sized to fit. With 4
   narrower columns (since the mega menu grew from 3 groups to 4),
   a group's own content — the heading text ("GRAPHICS & RESPONSIVE
   DESIGN"), or an item description without a natural break point —
   could force that group wider than its actual grid track, visually
   overflowing into the next column's space rather than wrapping.
   Exactly the same root cause as the earlier flex-level fix on
   .mega-menu__item's text wrapper, one level up in the tree.
   overflow: hidden alongside min-width: 0 (not one or the other) —
   min-width: 0 lets the item actually shrink to its track's size,
   but doesn't by itself clip anything that still doesn't fit after
   shrinking (a stray un-wrapped run of characters, for instance);
   overflow: hidden is the hard backstop that guarantees nothing ever
   visibly escapes this column's box regardless of what caused it. */
.mega-menu__group { flex: 1; min-width: 0; overflow: hidden; }
.mega-menu__heading {
  margin: 0 0 8px;
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--ink-soft);
  overflow-wrap: break-word;
}
.mega-menu__item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 8px;
  margin: 0 -8px 2px;
  border-radius: 8px;
  color: var(--ink);
  transition: background 0.12s ease;
}
/* The text wrapper (title + description) is the second/last child,
   after the fixed-size icon — without flex:1 and specifically
   min-width:0 here, a flex child defaults to min-width:auto, which
   means it won't shrink below its content's natural width. A long
   description ("Draw shapes, images, and animation") then forces the
   whole item — and the grid column it sits in — wider than intended,
   overflowing and getting visually clipped rather than wrapping onto
   a second line the way it should. Same root cause as several other
   overflow bugs found and fixed earlier in this project. */
.mega-menu__item > span:last-child { flex: 1; min-width: 0; }
/* Hover background and underline both removed per explicit request —
   only the current-page item keeps its background tint now; hover
   itself no longer changes anything but relies on the item's own
   color (unchanged) with text-decoration explicitly reset since the
   global a:hover rule in tokens.css would otherwise underline it. */
.mega-menu__item:hover { text-decoration: none; }
/* Background removed per explicit request, including the current-page
   state — the active item is now carried by its title's accent color
   alone (just below), same treatment the sidebar's own active-row
   fixes elsewhere in this file settled on. */
.mega-menu__item[aria-current="page"] .mega-menu__item-title { color: var(--accent); }
.mega-menu__item-title { display: block; font-size: 0.9rem; font-weight: 600; overflow-wrap: break-word; word-break: break-word; }
.mega-menu__item-desc { display: block; font-size: 0.78rem; color: var(--ink-soft); margin-top: 1px; overflow-wrap: break-word; word-break: break-word; }
.mega-menu__icon {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  font-size: 0.82rem;
  font-weight: 700;
  color: #fff;
}
/* All four badges use the same accent color (this site's own accent)
   per explicit request, replacing the four distinct per-column-group
   colors this used to have — was #4f46e5, replaced with #0e172b per
   a later explicit request. */
.mega-menu__icon--a,
.mega-menu__icon--b,
.mega-menu__icon--c,
.mega-menu__icon--d { background: #0e172b; }
.nav-dropdown__menu li a,
.nav-dropdown__menu li button {
  display: flex;
  align-items: center;
  width: 100%;
  padding: 8px 10px 8px 14px;
  margin-left: -2px;
  border-left: 2px solid transparent;
  border-radius: 0 6px 6px 0;
  font-size: 0.9rem;
  color: var(--ink-soft);
  background: none;
  border-top: none;
  border-right: none;
  border-bottom: none;
  text-align: left;
  cursor: pointer;
  transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease;
}
/* Hover background/border removed per explicit request — hover now
   only changes text color and weight, matching the current-page item
   below minus its background/border-left tint. */
.nav-dropdown__menu li a:hover,
.nav-dropdown__menu li button:hover {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
.nav-dropdown__menu li a[aria-current="page"],
.nav-dropdown__menu li button[aria-current="page"] {
  color: var(--accent);
  background: var(--color-primary-subtle);
  border-left-color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
/* Background/border removed per explicit request, scoped to the
   Interview Q & A dropdown's own items — hover already has no
   background site-wide (rule above), so this only needs to strip the
   current-category item's background/border-left tint. */
.nav-dropdown__menu--interview li a[aria-current="page"] {
  background: none;
  border-left-color: transparent;
}
/* Background/border removed per explicit request, scoped to the
   language dropdown's own items (English/मराठी/हिन्दी) — hover already
   has no background site-wide (rule above), so this only needs to
   strip the current-language item's background/border-left tint. */
.nav-dropdown__menu--right li a[aria-current="page"] {
  background: none;
  border-left-color: transparent;
}
/* Background/border removed per explicit request, scoped to the
   Tools dropdown's own items (Bug Checker/Compare Code/Character
   Count) — hover already has no background site-wide (rule above),
   so this only needs to strip the current-tool item's
   background/border-left tint. */
.nav-dropdown__menu--tools li a[aria-current="page"] {
  background: none;
  border-left-color: transparent;
}
/* Background/border removed per explicit request, scoped to the
   Exercise dropdown's own items — hover already has no background
   site-wide (rule above), so this only needs to strip the
   current-course item's background/border-left tint. Every
   nav-dropdown's items have now had this stripped, but the base rule
   above (.nav-dropdown__menu li a[aria-current="page"]) is left as-is
   rather than edited directly, since it's still the shared default
   any future dropdown would start from. */
.nav-dropdown__menu--exercise li a[aria-current="page"] {
  background: none;
  border-left-color: transparent;
}
.langswitch__icon { display: inline-flex; align-items: center; color: var(--ink); }
.langswitch__icon .icon { color: var(--ink); }
/* Theme toggle icon swaps between sun (light active) and moon (dark
   active) rather than staying one static icon regardless of state —
   both are always rendered; only one is ever visible at a time,
   toggled by the exact same data-theme / prefers-color-scheme rules
   that switch the color tokens themselves (see tokens.css and this
   file's own dark-theme block in responsive.css), so the icon always
   agrees with whichever theme is actually active. */
.themeswitch__icon-dark { display: none; }

/* ---------------------------------------------------------------
   Layout
--------------------------------------------------------------- */
.layout {
  /* 1280px per explicit request (was 1400px), matching the header's
     own .topbar__inner max-width. */
  max-width: 1280px;
  margin: 0 auto;
  padding: 0 24px;
  display: grid;
  /* minmax(0, 1fr): the middle (article) column is where lesson code
     examples live, by far the most common source of this kind of
     wide, hard-to-wrap content on the whole site, so this track
     especially benefits from being constrained at the grid
     definition level rather than relying only on .article's own
     min-width: 0. */
  grid-template-columns: 260px minmax(0, 1fr) 220px;
  gap: 56px;
  align-items: start;
}
/* Tighter grid gap specifically for the redesigned lesson page,
   measured directly from the supplied mockup (multiple independent
   pixel-scanned rows consistently showed roughly 25-32px between
   columns, not this shared .layout's 56px) — a second, more specific
   class rather than editing the shared rule above, since .layout is
   reused across 7 other page templates (404 pages, style guide, quiz
   levels, the editor) where no equivalent reference image calls for
   a change, and the 56px value has no documented reasoning tying it
   specifically to this page that would justify changing it globally. */
.lesson-layout {
  /* Rebuilt as flexbox with per-element padding, matching an actual
     HTML/Tailwind reference file the mockup this whole redesign has
     targeted turned out to be generated from — supplied directly
     partway through this process, after several turns of inferring
     these exact pixel values indirectly from the screenshot alone
     (which, as it turned out, both got some values wrong — 260/622/
     220px columns instead of the reference's actual 288/302/256 —
     and used entirely the wrong layout mechanism: a CSS grid with a
     uniform gap, when the reference has no gap between the three
     flex items at all — the visual gutters are each column's own
     padding instead, asymmetric by column, which no uniform grid gap
     value could have reproduced exactly no matter how carefully
     tuned). Confirmed against a live, locally-recompiled render of
     that reference file directly (its own Tailwind CDN link is
     blocked by this sandbox's network policy the same as everywhere
     else in this project, so a local build was needed to actually
     render and measure it, not just read its class names). */
  display: flex;
  width: 1280px;
  padding: 0 16px;
  gap: 0;
}
/* Sidebar, main, and toc-rail widths/padding below, all scoped to
   .lesson-layout specifically (each of these three base classes is
   shared with other page templates, same reasoning as elsewhere in
   this file), matching the reference file's own w-72/flex-1+max-w-3xl
   /w-64 columns and pr-6/lg:px-10/pl-8 padding exactly — the padding
   is what actually creates each visual gutter now, not a grid gap. */
.lesson-layout .sidebar { width: 288px; flex: none; padding-right: 24px; }
.lesson-layout #docs-main { flex: 1 1 auto; max-width: 768px; padding: 32px 40px; }
.lesson-layout .toc-rail { width: 256px; flex: none; padding-left: 32px; }

/* ---------------------------------------------------------------
   Sidebar
--------------------------------------------------------------- */
.sidebar {
  position: sticky;
  top: 76px;
  padding-top: 25px;
  padding-bottom: 0;
  max-height: calc(100vh - 96px);
  padding-right: 20px;
  /* Vertical divider between the chapter sidebar and the lesson
     content column — the reference layout runs this the full height
     of the sidebar (border-r), so the two columns read as distinct
     regions rather than lesson content floating unbounded next to
     the chapter list. */
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.sidebar__scroll {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  padding-bottom: 50px;
}

/* Chapter cards ("HTML Basics", "HTML Elements", ...) each get their
   own visible gap below in the mockup, not just the current chapter's
   expanded lesson list — restated here instead of on .sidebar__group
   summary's own margin so the gap survives whether the card is open
   or collapsed.
   Each card is also its own bordered, white-backed box in the mockup
   (confirmed by pixel-sampling: a 1px slate-200 border running the
   full height of the card, white behind the expanded lesson list,
   ECEFF3 behind the header row only) — previously this site had
   neither the border nor the white fill, so an expanded/collapsed
   chapter card was indistinguishable from the page's own slate-50
   background. overflow: hidden here lets the header's square corners
   and the card's own border-radius combine cleanly without having to
   duplicate the radius on the header itself. */
.sidebar__group {
  margin-bottom: 8px;
  border: 1px solid var(--border);
  border-radius: 12px;
  background: #fff;
  overflow: hidden;
}
.sidebar__group:last-child { margin-bottom: 0; }

.sidebar__group summary {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 10px;
  /* #f2f5fa per explicit request (was #ECEFF3). */
  background: #f2f5fa;
  /* 6px 12px per explicit request (was 11px 12px). */
  padding: 6px 12px;
  cursor: pointer;
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--ink);
  transition: background 0.12s ease, color 0.12s ease;
}
/* #f2f5fa per explicit request (was var(--border)). */
.sidebar__group summary:hover { background: #f2f5fa; }
/* Same reasoning as the FAQ accordion's summary: <summary> isn't
   covered by the global a/button/input focus rule, so without this
   it falls back to the browser's default blue outline instead of
   the site's own accent color. */
.sidebar__group summary:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.sidebar__group summary::-webkit-details-marker { display: none; }

.sidebar__group-label { flex: 1; }
.sidebar__chevron {
  color: var(--ink-soft);
  transition: transform 0.15s ease;
}
/* icon_caret_down() points down at rest (collapsed) already matching
   the mockup, so open just flips it to point up — a plain 180°
   rotation, not the 90° right-to-down rotation this used when it
   wrapped icon_chevron_right() instead (the wrong glyph shape for a
   collapse/expand affordance to begin with). */
.sidebar__group[open] .sidebar__chevron { transform: rotate(180deg); }

/* No left-rail border here: the mockup's expanded lesson list has no
   continuous vertical line down the left edge at all, confirmed by
   sampling a column of pixels through where that line would fall —
   just isolated dot bullets per item (added below), not a rail. */
.sidebar ul {
  list-style: none;
  margin: 2px 0 10px;
  padding: 0;
}
.sidebar li a {
  display: flex;
  align-items: baseline;
  gap: 8px;
  /* Vertical padding 6px (was 5px), pixel-measured against the required
     reference: its active-item pill is 32px tall at this same font size,
     the current 5px padding produced 29px — 6px closes that gap
     (measured live via getBoundingClientRect: 31px, matching within
     rounding). */
  padding: 6px 6px 6px 20px;
  border-radius: 6px;
  color: var(--ink-soft);
  font-size: 0.88rem;
  line-height: 1.35;
  transition: color 0.12s ease, background 0.12s ease;
}
.sidebar__topic-text { flex: 1; min-width: 0; }
/* Every lesson row gets a small dot bullet before its label in the
   mockup, not just the current one — light gray by default, dark and
   slightly larger for the active row (its own rule further below). */
.sidebar__topic-text::before {
  content: '';
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-right: 8px;
  border-radius: 50%;
  /* Sampled from the mockup's own dot pixels (rgb(203,213,225)) —
     var(--border) here is a visibly lighter gray in this site's own
     palette and the dot all but disappeared against the white card. */
  background: #CBD5E1;
  vertical-align: middle;
}
/* Background removed per explicit request — hover/focus now only
   change the text color, same treatment as the active row's own
   background-free style below. */
.sidebar li a:hover,
.sidebar li a:focus-visible {
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}
/* The current chapter ("What is HTML?" on that lesson's own page, say):
   a persistent version of the hover state rather than a separate dark
   treatment — accent-tinted pill background, accent text and dot —
   matching the target design exactly. */
.sidebar li a.is-active {
  color: var(--accent);
  /* Background removed per explicit request — the active row is now
     carried by the accent text/dot alone, no pill fill behind it. */
  font-weight: 700;
  text-decoration: none;
}
.sidebar li a.is-active .sidebar__topic-text::before {
  /* 8px (was 6px) — pixel-measured against the required reference's own
     active-item dot, an 8x8px bbox at this same scale, clearly larger
     than the inactive dots (5px) it sits alongside. */
  width: 8px;
  height: 8px;
  background: var(--accent);
}
/* ---------------------------------------------------------------
   Sidebar progress tracker
--------------------------------------------------------------- */
/* Its own bordered card (was just a bottom-border separator) — matches
   the target design, which gives this widget the same white/bordered
   card treatment as the course-brand block just above it. */
.sidebar__progress {
  padding: 10px 12px;
  margin-bottom: 12px;
  border: 1px solid var(--border);
  border-radius: 12px;
  /* #f2f5fa per explicit request (was var(--surface)). */
  background: #f2f5fa;
}
.sidebar__progress-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-bottom: 8px;
  font-size: 0.78rem;
}
.sidebar__progress-label { color: var(--ink-soft); font-weight: 600; }
/* Accent-colored and bold, in the site's own --sans stack — was plain
   gray monospace, which the target design doesn't use here. */
/* Slate-900 (#0F172A) per pixel-sampling against the reference — the
   "9 / 219" count reads as plain dark ink there, not accent-colored
   (var(--accent) was a carry-over from an earlier round's guess). */
.sidebar__progress-count { color: #0F172A; font-family: var(--sans); font-weight: 700; }
.sidebar__progress-bar {
  height: 6px;
  border-radius: 999px;
  /* #e3e7f0 per explicit request (previous round had removed this
     track background entirely; now restored with a specific color). */
  background: #e3e7f0;
  overflow: hidden;
}
.sidebar__progress-fill {
  height: 100%;
  width: 0%;
  background: var(--accent);
  border-radius: 999px;
  transition: width 0.25s ease;
}
/* Progress is derived from localStorage after the page loads, so it
   briefly shows nothing real to report yet — a shimmering
   placeholder in that gap reads as "loading" rather than as a
   flash of 0/N that could look like a bug. */
.sidebar__progress.is-loading .sidebar__progress-count,
.sidebar__progress.is-loading .sidebar__progress-bar {
  position: relative;
  color: transparent;
  overflow: hidden;
}
.sidebar__progress.is-loading .sidebar__progress-count::before,
.sidebar__progress.is-loading .sidebar__progress-bar::before {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, var(--color-shimmer-1) 25%, var(--color-shimmer-2) 50%, var(--color-shimmer-1) 75%);
  background-size: 200% 100%;
  animation: skeleton-shimmer 1.4s ease-in-out infinite;
  border-radius: inherit;
}


/* ---------------------------------------------------------------
   Right-hand "On this page" rail
--------------------------------------------------------------- */
.toc-rail {
  position: sticky;
  top: 65px;
  padding-top: 40px;
  max-height: calc(100vh - 96px);
  overflow-y: auto;
}
.toc-rail--invisible { visibility: hidden; }
/* Shared by both the lesson page's "On this page" outline (which
   also carries text-xs/font-bold/uppercase/tracking-wider directly
   in its markup) and the chapter-listing pages' rail label (plain
   markup, no Tailwind classes of its own) — matching the mockup's
   text-xs/font-bold/uppercase/tracking-wider values here directly so
   both render the same way regardless of which one supplies them.
   Previously font-size: 0.78rem/font-weight: 600 here, unlayered CSS,
   silently beat the "On this page" label's own text-xs/font-bold
   utility classes (same Tailwind-layer bug fixed elsewhere in this
   project). */
.toc-rail__label {
  margin: 0 0 10px;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-soft);
}
.toc-rail ul {
  list-style: none;
  margin: 0;
  padding: 0;
  border-left: 2px solid var(--border);
}
.toc-rail li a {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 5px 8px 5px 14px;
  margin-left: -2px;
  border-left: 2px solid transparent;
  border-radius: 0 6px 6px 0;
  color: var(--ink-soft);
  /* Reverted back to 0.85rem per explicit request (had been changed
     to a flat 12px in an earlier round). */
  font-size: 0.85rem;
  line-height: 1.4;
  transition: color 0.12s ease, background 0.12s ease, border-color 0.12s ease;
}
/* Background and border-left removed per explicit request — hovering
   an "On this page" entry now only changes its text color, matching
   the active entry's own plain (no background, no border) treatment
   just below. */
.toc-rail li a:hover {
  color: var(--accent);
  font-weight: 500;
  text-decoration: none;
}
/* The active "On this page" entry ("Overview" while that section is in
   view) is bold accent-colored text — no background tint, and the
   rail's own border-left (.toc-rail ul, above) stays its plain gray
   rather than picking up a colored bar for the active item, unlike
   the sidebar chapter list's hover state. */
.toc-rail li a.is-active {
  color: var(--accent);
  font-weight: 700;
  text-decoration: none;
}

/* ---------------------------------------------------------------
   Footer
--------------------------------------------------------------- */
/* Matches the reference file exactly: a single centered copyright
   line, not the previous multi-line block (version number, tagline,
   visitor count) — replaced outright per the request to match the
   reference footer as-is rather than layer the reference's structure
   under the old content. */
.sitefooter {
  border-top: 1px solid var(--border);
  background: var(--surface);
  padding: 24px;
  text-align: center;
  font-size: 0.75rem;
  color: var(--ink-soft);
}
.sitefooter p { margin: 0; }

/* ---------------------------------------------------------------
   Responsive
   Breakpoints: desktop (>1280px, full 3-column) → laptop/small-
   desktop (1025–1280px, "On this page" rail hidden) → tablet
   (768–1024px) → mobile (≤767px). The threshold for hiding the
   toc-rail is deliberately higher than it might look like it needs
   to be at a glance: the 3-column grid's side columns (260px +
   220px, plus 112px of gaps) are fixed-width, so anywhere below
   ~1280px viewport width the main reading column — which wants up
   to 720px — was getting squeezed to as little as ~590px instead,
   noticeably cramped for a "desktop" layout. Dropping to 2 columns
   earlier avoids that squeeze entirely. The sidebar itself switches
   to the hamburger-triggered drawer at ≤1024px (see the dedicated
   block further down) — .layout's own grid collapses to a single
   column at that same breakpoint so it doesn't keep reserving an
   empty 240px track for a sidebar that's no longer part of the grid
   flow.
--------------------------------------------------------------- */


/* Tablet and mobile both need the sidebar out of the grid and the
   content column given real breathing room on every side — this
   block covers everything ≤1024px, with the tablet- and
   mobile-specific blocks below layering finer adjustments on top. */


/* Tablet: 768–992px. Slightly roomier than mobile — the article
   keeps a normal type scale and side-by-side pager, just with
   tablet-appropriate padding and a narrower search box. */


/* Mobile: ≤767px. */






/* ---------------------------------------------------------------
   Sidebar search
--------------------------------------------------------------- */
.sidebar__course-brand {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Plain white — re-sampled against the latest reference, which
     shows flat white here (255,255,255), not the light indigo wash
     an earlier round's reference had called for. */
  background: #fff;
  /* Tailwind's slate-200 (#E2E8F0) per explicit request — no
     --color-slate-200 token exists in this project's palette
     (tokens.css), so the literal value is used directly, same as
     other one-off colors pixel-sampled elsewhere in this file. */
  border: 1px solid #E2E8F0;
}
.sidebar__course-count { color: var(--accent); }
/* Shape/size properties replicated from .mega-menu__icon (layout.css)
   rather than reusing that class directly. Unlike the mega menu —
   which lists several courses together and uses the --a/--b/--c/--d
   category colors so they read apart from each other — this sidebar
   only ever shows the single current course, so it uses the site's
   own accent color directly rather than a per-category one. */
.sidebar__course-icon {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border-radius: 8px;
  font-size: 0.82rem;
  font-weight: 700;
  color: #fff;
  background: var(--accent);
}
.sidebar__course-name { font-weight: 700; font-size: 0.95rem; color: var(--ink); }

/* margin-bottom, not padding-bottom: this element is the icon's own
   positioning context (position: relative + the icon's top: 50% /
   translateY(-50%)), so padding here would make that 50% land in the
   middle of (input + padding) instead of the middle of the input
   itself — pixel-measured on the live page as the icon sitting ~7px
   below true center, confirming exactly that bug. A margin keeps this
   element's own box exactly the input's height, so 50% actually means
   the input's own vertical center. */
.sidebar__search { position: relative; margin-bottom: 14px; }
.sidebar__search-icon {
  position: absolute;
  left: 12px;
  top: 50%;
  transform: translateY(-50%);
  /* Re-sampled against the latest reference: the icon's darkest core
     pixels are rgb(91,94,102), this site's own --ink-soft — the
     lighter gray-400 this used to be was based on an earlier,
     lower-resolution reference crop. */
  color: var(--ink-soft);
  pointer-events: none;
  display: flex;
}
.sidebar__search-input {
  width: 100%;
  padding: 9px 12px 9px 34px;
  /* Flat slate-100 fill (rgb(241,245,249), pixel-sampled), now paired
     with the same slate-200 (#E2E8F0) border the course-brand and
     progress cards use — re-sampled against the latest reference,
     which does show a thin border line around this box after all. */
  border: 1px solid #E2E8F0;
  background: #F1F5F9;
  border-radius: 10px;
  font-size: 0.85rem;
  font-family: var(--sans);
  color: var(--ink);
}
.sidebar__search-input:focus { border-color: var(--accent); outline: none; }
.sidebar__group.is-search-hidden,
.sidebar li.is-search-hidden { display: none; }
.sidebar__no-results {
  display: none;
  color: var(--ink-soft);
  font-size: 0.85rem;
  padding: 8px 4px;
}
.sidebar__no-results.is-visible { display: block; }
