/* Cartesian School — Python с нуля
   Design tokens mirror the Figma variables 1:1 (see design/figma/state_ledger.json
   -> sourcedColors for provenance of the brand hues). */

:root {
  /* primitives */
  --navy-950: #08011C;
  --navy-900: #0D0230;
  --navy-800: #15104A;
  --violet-300: #B9A0FC;
  --violet-400: #8355FA;
  --violet-500: #5B24F9;
  --blue-300: #8FB7FE;
  --blue-500: #185DFA;
  --blue-600: #0C43F1;
  --cyan-400: #22D3EE;
  --white: #FFFFFF;
  --offwhite: #FAFAFC;
  --gray-100: #F2F2F6;
  --gray-200: #E4E4EC;
  --gray-400: #B4B4C4;
  --gray-600: #6B6B7D;
  --gray-800: #2B2B3D;
  --amber-500: #F59E0B;
  --red-500: #EF4444;
  --green-500: #22C55E;

  /* semantic */
  --color-bg-canvas: var(--white);
  --color-bg-surface: var(--offwhite);
  --color-bg-inverse: var(--navy-900);
  --color-bg-code: var(--navy-950);
  --color-text-primary: var(--navy-900);
  --color-text-inverse: var(--white);
  --color-text-muted: var(--gray-600);
  --color-text-code: var(--gray-100);
  --color-brand-violet: var(--violet-500);
  --color-brand-blue: var(--blue-500);
  --color-border-default: var(--gray-200);
  --color-border-inverse: var(--navy-800);
  --color-callout-info-bg: var(--blue-300);
  --color-callout-info-border: var(--blue-500);
  --color-callout-tip-bg: var(--violet-300);
  --color-callout-tip-border: var(--violet-500);
  --color-callout-warning-bg: #FEF3E2;
  --color-callout-warning-border: var(--amber-500);
  --color-callout-security-bg: #FDE8E8;
  --color-callout-security-border: var(--red-500);

  /* spacing */
  --spacing-xs: 4px;
  --spacing-sm: 8px;
  --spacing-md: 16px;
  --spacing-lg: 24px;
  --spacing-xl: 32px;
  --spacing-2xl: 48px;
  --spacing-3xl: 64px;
  --spacing-4xl: 96px;

  /* radius */
  --radius-sm: 6px;
  --radius-md: 12px;
  --radius-lg: 20px;
  --radius-full: 999px;

  /* elevation — shared across theory/practice/homepage so shadows read as
     one consistent system rather than page-local one-offs */
  --shadow-sm: 0 1px 2px rgba(13, 2, 48, 0.06);
  --shadow-md: 0 8px 24px rgba(13, 2, 48, 0.10);
  --shadow-lg: 0 16px 48px rgba(13, 2, 48, 0.16);
  --shadow-xl: 0 28px 64px rgba(13, 2, 48, 0.22);
  --shadow-glow-violet: 0 0 0 1px rgba(91, 36, 249, 0.25), 0 12px 32px rgba(91, 36, 249, 0.18);
  --shadow-glow-blue: 0 0 0 1px rgba(24, 93, 250, 0.22), 0 12px 32px rgba(24, 93, 250, 0.16);

  /* surfaces — an explicit elevation ladder layered on top of the existing
     --color-bg-* tokens (those are untouched; this just names the steps
     between them so cards/panels/boards read as intentionally stacked) */
  --surface-0: var(--color-bg-canvas);
  --surface-1: var(--color-bg-surface);
  --surface-2: #FFFFFF;
  --surface-inverse-0: var(--navy-950);
  --surface-inverse-1: var(--navy-900);
  --surface-inverse-2: var(--navy-800);

  /* borders — hairline + highlight edges so elevated cards don't rely on
     box-shadow alone */
  --border-hairline: 1px solid rgba(13, 2, 48, 0.08);
  --border-hairline-strong: 1px solid rgba(13, 2, 48, 0.14);
  --border-hairline-inverse: 1px solid rgba(255, 255, 255, 0.10);
  --border-highlight-inverse: 1px solid rgba(255, 255, 255, 0.06);

  /* motion */
  --duration-fast: 120ms;
  --duration-base: 220ms;
  --duration-slow: 420ms;
  --ease-standard: cubic-bezier(0.22, 0.9, 0.28, 1);
  --ease-out: cubic-bezier(0, 0, 0.2, 1);

  /* layout */
  --content-width: 1180px;
  --content-width-narrow: 760px;

  /* Anchor-offset model (see the html{scroll-padding-top} rule below):
     --site-header-height is a measured-at-first-paint fallback (desktop
     .site-header renders at 65px, see the @media override below for
     mobile's 49px) that nav.js immediately overwrites with the header's
     actual getBoundingClientRect().height once it runs, so direct hash
     loads land correctly even in the brief window before that JS executes.
     --anchor-breathing-room is the only hand-picked constant here: a small
     deliberate gap so the target doesn't touch the header. */
  --site-header-height: 65px;
  --anchor-breathing-room: 10px;
}

* { margin: 0; padding: 0; box-sizing: border-box; }

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  color: var(--color-text-primary);
  background: var(--color-bg-canvas);
  line-height: 1.6;
}

h1, h2, h3, h4, .display {
  font-family: 'Sora', sans-serif;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: var(--color-text-primary);
}

/* JetBrains Mono ships built-in "calt" ligatures for operator sequences
   like !=, ==, <=, >=, ->. Left enabled, the browser silently redraws the
   literal two-character token != as a single glyph that reads as ≠ (and
   similarly for the others) -- the underlying text a learner would copy or
   paste is still the correct, literal Python source, but what they SEE does
   not match what they must TYPE on a keyboard. For a beginner course this
   is actively confusing, so ligatures are disabled everywhere code is
   rendered. */
code, pre, .mono {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-ligatures: none;
  -webkit-font-variant-ligatures: none;
  font-feature-settings: "calt" 0, "liga" 0;
}

a { color: var(--color-brand-blue); transition: color var(--duration-fast) var(--ease-standard); }

/* Keep fragment-link targets clear of the sticky header (.site-header) —
   one authoritative offset for the whole document's fragment navigation
   (a click, a direct #hash URL load, or a reload), rather than tagging
   every [id] individually. Was a blanket [id]{scroll-margin-top:96px}:
   96px was picked without measuring the real header, overshot its actual
   rendered height (65px desktop, 49px mobile) by tens of pixels, and left
   the previous section's tail visible above the target. */
html {
  scroll-padding-top: calc(var(--site-header-height) + var(--anchor-breathing-room));
}

/* scroll-padding-top above is not just for #fragment navigation — browsers
   also apply it whenever ANY element is scrolled into view, including their
   own "scroll the focused element into view" behavior on every focus (a
   click OR Tab, from any page, since .site-header is site-wide). For an
   element inside a position:sticky container that behavior misfires: the
   header is already always fully visible at the top, needing no extra
   clearance, but the browser doesn't know that and scrolls the whole page
   by a large, wrong amount trying to satisfy scroll-padding-top against a
   sticky element whose position doesn't actually change as you scroll.
   Cancel it for exactly this container with an equal-and-opposite
   scroll-margin-top (the two combine additively, so this nets to zero) —
   do not add scroll-margin-top anywhere else, or it will stack with this. */
.site-header, .site-header * {
  scroll-margin-top: calc(-1 * (var(--site-header-height) + var(--anchor-breathing-room)));
}

/* ---------- Accessibility: focus-visible everywhere ---------- */
a:focus-visible,
button:focus-visible,
summary:focus-visible,
input:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--color-brand-blue);
  outline-offset: 3px;
  border-radius: 4px;
}
.panel-inverse a:focus-visible,
.site-header a:focus-visible,
.site-header button:focus-visible,
.home-hero a:focus-visible,
.home-footer a:focus-visible,
#mobile-nav-panel a:focus-visible,
#mobile-nav-panel button:focus-visible {
  outline-color: var(--blue-300);
}

/* ---------- Shared button system (used by hero, practice shell, cards) ----------
   .btn-primary  = strongest CTA, gradient fill, any background
   .btn-secondary = outlined, for light/canvas surfaces
   .btn-ghost     = outlined, for dark/inverse surfaces */
.btn, .btn-primary, .btn-secondary, .btn-ghost {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 13px 26px; border-radius: var(--radius-full);
  font-family: inherit; font-weight: 600; font-size: 15px;
  text-decoration: none; cursor: pointer; border: 1px solid transparent;
  transition: transform var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              background var(--duration-fast) var(--ease-standard);
}
.btn-primary {
  background: linear-gradient(90deg, var(--violet-500), var(--blue-500));
  color: #fff; box-shadow: var(--shadow-sm);
}
.btn-primary:hover { transform: translateY(-1px); box-shadow: var(--shadow-glow-violet); }
.btn-secondary { background: var(--surface-0); border-color: var(--color-border-default); color: var(--color-text-primary); }
.btn-secondary:hover { border-color: var(--color-brand-blue); color: var(--color-brand-blue); }
.btn-ghost { border-color: rgba(255,255,255,.25); color: #fff; background: rgba(255,255,255,.02); }
.btn-ghost:hover { border-color: rgba(255,255,255,.5); background: rgba(255,255,255,.06); }

/* ---------- Top navigation ---------- */
.site-header {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--spacing-md) var(--spacing-2xl);
  background: rgba(13, 2, 48, 0.88);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  border-bottom: 1px solid var(--color-border-inverse);
  box-shadow: 0 1px 0 rgba(0,0,0,.2), 0 12px 24px -12px rgba(0,0,0,.4);
}
.brand { display: flex; align-items: center; gap: 10px; text-decoration: none; }
.brand img { height: 32px; width: 32px; display: block; }
.brand-word {
  font-family: 'Sora', sans-serif; font-weight: 700; font-size: 20px; line-height: 1;
  color: var(--color-text-inverse); display: inline-flex; align-items: baseline;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
.brand-word .school {
  font-size: inherit; font-weight: inherit;
  background: linear-gradient(90deg, var(--violet-500), var(--blue-500));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.top-nav { display: flex; gap: var(--spacing-lg); list-style: none; }
.top-nav a {
  position: relative; color: var(--gray-400); text-decoration: none; font-size: 14px; font-weight: 500;
  padding: 6px 2px; transition: color var(--duration-fast) var(--ease-standard);
}
.top-nav a::after {
  content: ""; position: absolute; left: 2px; right: 2px; bottom: -3px; height: 2px;
  border-radius: var(--radius-full); background: linear-gradient(90deg, var(--violet-400), var(--blue-300));
  transform: scaleX(0); transform-origin: left; transition: transform var(--duration-base) var(--ease-standard);
}
.top-nav a:hover { color: var(--color-text-inverse); }
.top-nav a:hover::after { transform: scaleX(1); }
.top-nav a.active { color: var(--color-text-inverse); }
.top-nav a.active::after { transform: scaleX(1); }
.nav-toggle { display: none; }

/* ---------- Layout: sidebar + content ---------- */
.layout { display: grid; grid-template-columns: 280px minmax(0, 760px); gap: var(--spacing-3xl); max-width: 1180px; margin: 0 auto; padding: var(--spacing-2xl); }
.layout > * { min-width: 0; } /* prevent grid-item blowout from unwrapped <pre> content */

.sidebar { position: sticky; top: 76px; align-self: start; max-height: calc(100vh - 96px); overflow-y: auto; }
.mobile-nav-links { display: none; }
/* Chapter-opener/homepage-style pages have no page-local sidebar — this
   panel exists purely to hold the mobile-only site nav for the hamburger
   toggle, hidden outside the mobile breakpoint regardless of .open state. */
.mobile-nav-panel { display: none; }
.sidebar-title { font-size: 12px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-text-muted); margin-bottom: var(--spacing-sm); }
.toc-list { list-style: none; display: flex; flex-direction: column; gap: 2px; margin-bottom: var(--spacing-lg); }
.toc-list a { display: block; padding: 6px 10px; border-radius: var(--radius-sm); text-decoration: none; color: var(--color-text-primary); font-size: 14px; transition: background var(--duration-fast) var(--ease-standard); }
.toc-list a.active { background: var(--color-callout-info-bg); color: var(--navy-900); font-weight: 600; }
.toc-list a:hover { background: var(--gray-100); }
.toc-list .sub { padding-left: 22px; font-size: 13px; color: var(--color-text-muted); }

/* ---------- Breadcrumb ---------- */
.breadcrumb { font-size: 13px; color: var(--color-text-muted); margin-bottom: var(--spacing-md); }
.breadcrumb a { color: var(--color-text-muted); text-decoration: none; }
.breadcrumb a:hover { text-decoration: underline; }

/* ---------- Article ---------- */
article h1 { font-size: 34px; margin-bottom: var(--spacing-sm); }
.technology-heading { display: flex; align-items: flex-start; gap: 12px; }
.technology-heading > span:last-child { min-width: 0; }
.technology-heading__brands { display: inline-flex; align-items: center; flex: 0 0 auto; min-height: 42px; }
.technology-heading__mark { display: block; width: auto; object-fit: contain; }
.technology-heading__mark--git { width: 40px; height: 40px; }
.technology-heading__brands--pair { gap: 7px; margin-right: 3px; }
.technology-heading__brand-token { display: inline-flex; align-items: center; gap: 5px; font-size: 14px; font-weight: 700; white-space: nowrap; }
.technology-heading__brand-token .technology-heading__mark--git { width: 34px; height: 34px; }
.technology-heading__mark--github { width: 34px; height: 33px; }
.technology-heading__not-equal { color: var(--color-text-muted); font-size: 17px; font-weight: 600; }
article .section-kicker { display: flex; align-items: flex-start; gap: 7px; font-size: 13px; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--color-brand-blue); margin-bottom: var(--spacing-sm); }
article .section-kicker img { width: 20px; height: 20px; display: block; flex-shrink: 0; }
article h2 { font-size: 24px; margin-top: var(--spacing-2xl); margin-bottom: var(--spacing-md); }
article h3 { font-size: 19px; margin-top: var(--spacing-xl); margin-bottom: var(--spacing-sm); }
article p { margin-bottom: var(--spacing-md); font-size: 17px; }
article ul, article ol { margin: 0 0 var(--spacing-md) var(--spacing-lg); font-size: 17px; }
article li { margin-bottom: 6px; }
.lede { font-size: 19px; color: var(--color-text-muted); margin-bottom: var(--spacing-xl); }
.stage-tracker-compact { display: flex; align-items: center; gap: 9px; margin: 0 0 20px; color: var(--color-text-muted); font-size: 12px; }
.stage-tracker-compact span { letter-spacing: .04em; text-transform: uppercase; }
.stage-tracker-compact strong { color: var(--color-brand-blue); }

/* ---------- Author page (front-matter/ob-avtore.html) ---------- */
@keyframes author-page-hero-enter { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
.author-page__hero {
  display: flex; flex-direction: row-reverse; align-items: center; gap: 30px;
  margin: 0 0 var(--spacing-xl); padding: 26px 28px;
  background: linear-gradient(155deg, #F7F8FF 0%, #FFFFFF 62%);
  border: var(--border-hairline); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
  animation: author-page-hero-enter 480ms var(--ease-out) both;
}
.author-page__portrait {
  flex: 0 0 168px; width: 168px; aspect-ratio: 4 / 5; border-radius: var(--radius-md);
  overflow: hidden; border: 2px solid var(--blue-300); box-shadow: var(--shadow-md);
}
.author-page__portrait img { display: block; width: 100%; height: 100%; object-fit: cover; }
.author-page__hero-copy { min-width: 0; }
.author-page__name { margin: 0 0 6px; font: 700 26px/1.22 'Sora', sans-serif; color: var(--color-text-primary); }
.author-page__role { margin: 0 0 10px; font: 600 15px/1.4 'Sora', sans-serif; color: var(--gray-800); }
.author-page__specialization {
  display: flex; flex-wrap: wrap; gap: 0; margin: 0; font: 500 11.5px/1.6 'JetBrains Mono', monospace;
  color: var(--color-brand-blue);
}
.author-page__specialization span:not(:last-child)::after { content: " · "; color: var(--gray-400); }

.author-page__section h2:first-child { margin-top: 0; }

.author-page__domains {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 24px;
  margin: 18px 0 0; padding: 0; list-style: none; border-top: 1px solid var(--color-border-default);
}
.author-page__domain {
  display: grid; grid-template-columns: 26px minmax(0, 1fr); gap: 10px; min-width: 0;
  padding: 14px 0 13px; border-bottom: 1px solid var(--color-border-default);
}
.author-page__domain--wide { grid-column: 1 / -1; }
.author-page__domain-index { padding-top: 2px; color: var(--color-brand-blue); font: 600 11px/1.4 'JetBrains Mono', monospace; }
.author-page__domain h3 { margin: 0 0 3px; font: 650 14px/1.3 'Sora', sans-serif; color: var(--color-text-primary); }
.author-page__domain p { margin: 0; font: 400 12.5px/1.5 'JetBrains Mono', monospace; color: var(--color-text-muted); }

.author-page__projects { display: flex; flex-wrap: wrap; gap: 9px; margin: 16px 0 0; padding: 0; list-style: none; }
.author-page__projects li {
  padding: 7px 15px; background: var(--gray-100); border-radius: var(--radius-full);
  font: 600 13px/1.4 'Sora', sans-serif; color: var(--color-text-primary);
}

@media (max-width: 860px) {
  .author-page__hero { flex-direction: column; align-items: flex-start; }
  .author-page__portrait { width: 148px; align-self: center; }
  .author-page__domains { grid-template-columns: 1fr; }
}

/* ---------- Code blocks ---------- */
.code-block { position: relative; background: var(--color-bg-code); border-radius: var(--radius-md); margin: var(--spacing-lg) 0; overflow: hidden; box-shadow: var(--shadow-sm); border: var(--border-hairline-inverse); }
.code-block .code-label { display: flex; align-items: center; justify-content: space-between; padding: 10px 16px; background: rgba(255,255,255,0.04); border-bottom: 1px solid rgba(255,255,255,0.08); }
.code-block .code-label span { font-size: 12px; color: var(--gray-400); font-family: 'JetBrains Mono', monospace; }
.copy-btn { font-size: 12px; color: var(--gray-400); background: transparent; border: 1px solid rgba(255,255,255,0.15); border-radius: var(--radius-sm); padding: 4px 10px; cursor: pointer; font-family: inherit; }
.copy-btn:hover { color: var(--color-text-inverse); border-color: rgba(255,255,255,0.4); }
.code-block pre { padding: var(--spacing-lg); overflow-x: auto; }
.code-block code { color: var(--color-text-code); font-size: 14px; line-height: 1.7; white-space: pre; }
.tok-kw { color: #8FB7FE; } .tok-str { color: #B9F27C; } .tok-com { color: #7D7D95; } .tok-fn { color: #C9A6FF; } .tok-num { color: #FFB86B; }
code.inline { background: var(--gray-100); color: var(--navy-900); padding: 2px 6px; border-radius: 4px; font-size: 0.9em; }

/* ---------- Callouts ---------- */
.callout { display: flex; align-items: flex-start; gap: var(--spacing-md); border-radius: var(--radius-md); padding: var(--spacing-md); margin: var(--spacing-lg) 0; border-left: 4px solid; box-shadow: var(--shadow-sm); }
.callout .callout-title { font-weight: 600; margin-bottom: 4px; font-size: 15px; }
.callout .callout-body { font-size: 15px; }
.callout-info { background: #EAF1FF; border-color: var(--color-callout-info-border); }
.callout-tip { background: #F3EEFF; border-color: var(--color-callout-tip-border); }
.callout-warning { background: var(--color-callout-warning-bg); border-color: var(--color-callout-warning-border); }
.callout-security { background: var(--color-callout-security-bg); border-color: var(--color-callout-security-border); }
.callout-debug { background: var(--color-callout-security-bg); border-color: var(--red-500); }

/* ---------- Cartesian School icon system ----------
   Original local SVG sprite (site/assets/icons/cartesian/icons.svg), referenced
   via <svg class="cs-icon cs-icon--NAME"><use href="...#icon-NAME"></use></svg>.
   Base size scales with the surrounding text (em units) — this is the
   Level C / plain-inline presentation (sidebar links, directory-tree
   illustrations, homepage feature/reference icons): visible, but not meant
   to shout. See scripts/site_lib.py:cs_icon() and docs/ICON-SYSTEM.md for
   the full "signal level" system and the semantic mapping.

   idea/launch/warning/debug/note are drawn with fixed multicolor fills
   inside the sprite itself (lightbulb yellow, rocket red/blue, hazard
   amber, bug coral, book blue) — not currentColor — so no color rule for
   them here; the five archetype icons read the same everywhere they're
   used, on purpose. */
.cs-icon {
  width: 1.15em;
  height: 1.15em;
  flex-shrink: 0;
  vertical-align: -0.2em;
  margin-right: 0.32em;
  color: currentColor;
}
.cs-icon--success { color: var(--green-500); }
.cs-icon--error { color: var(--red-500); }
.cs-icon--experiment, .cs-icon--search, .cs-icon--compare { color: var(--color-brand-blue); }
.cs-icon--license { color: var(--violet-500); }

/* Level B — secondary semantic marker (chapter-hero metadata badges). Not a
   shouting signal, but clearly larger/bolder than a plain inline glyph. */
.chapter-meta .cs-icon {
  width: 22px;
  height: 22px;
  margin-right: 7px;
  vertical-align: -6px;
}

/* Level A — the "signal" tile: reserved for important semantic blocks
   (callout()/exercise() titles, Debug Lab headings). Full-opacity, strongly
   saturated icon (via the .cs-icon--NAME color rules above) sitting on a
   light tile tinted with the same semantic hue, so the symbol commands
   attention before the title text is read — see docs/ICON-SYSTEM.md. */
.cs-icon-emblem {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  border-radius: 12px;
}
.cs-icon-emblem .cs-icon {
  width: 26px;
  height: 26px;
  margin: 0;
  vertical-align: 0;
}
.cs-icon-emblem--idea { background: rgba(255, 196, 61, .22); }
.cs-icon-emblem--launch { background: rgba(255, 90, 95, .13); }
.cs-icon-emblem--warning { background: rgba(245, 158, 11, .17); }
.cs-icon-emblem--success { background: rgba(34, 197, 94, .17); }
.cs-icon-emblem--error { background: rgba(239, 68, 68, .15); }
.cs-icon-emblem--debug { background: rgba(232, 84, 62, .15); }
.cs-icon-emblem--note { background: rgba(30, 79, 196, .12); }
.cs-icon-emblem--experiment, .cs-icon-emblem--search, .cs-icon-emblem--compare { background: rgba(24, 93, 250, .14); }
.cs-icon-emblem--license { background: rgba(91, 36, 249, .14); }
.cs-icon-emblem--practice, .cs-icon-emblem--timer, .cs-icon-emblem--game, .cs-icon-emblem--code,
.cs-icon-emblem--file, .cs-icon-emblem--folder, .cs-icon-emblem--architecture, .cs-icon-emblem--palette,
.cs-icon-emblem--tools, .cs-icon-emblem--loop, .cs-icon-emblem--profile, .cs-icon-emblem--device {
  background: var(--color-bg-surface);
}
@media (max-width: 480px) {
  .cs-icon-emblem { width: 34px; height: 34px; border-radius: 10px; }
  .cs-icon-emblem .cs-icon { width: 22px; height: 22px; }
}

/* ---------- Classic vs Modern ---------- */
.cvm { border: 1px solid var(--color-border-default); border-radius: var(--radius-lg); margin: var(--spacing-xl) 0; overflow: hidden; }
.cvm-header { padding: var(--spacing-md) var(--spacing-lg); background: var(--color-bg-surface); font-family: 'Sora', sans-serif; font-weight: 600; }
.cvm-grid { display: grid; grid-template-columns: 1fr 1fr; }
.cvm-grid > * { min-width: 0; } /* prevent grid-item blowout from unwrapped <pre> content */
.cvm-col { padding: var(--spacing-lg); }
.cvm-col + .cvm-col { border-left: 1px solid var(--color-border-default); }
.cvm-label { font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; margin-bottom: var(--spacing-sm); }
.cvm-col.classic .cvm-label { color: var(--gray-600); }
.cvm-col.modern .cvm-label { color: var(--color-brand-blue); }
.cvm-col pre { background: var(--color-bg-code); border-radius: var(--radius-sm); padding: var(--spacing-md); overflow-x: auto; }
.cvm-col code { color: var(--color-text-code); font-size: 13px; }
.cvm-verdict { padding: var(--spacing-md) var(--spacing-lg); background: var(--color-bg-surface); font-size: 14px; border-top: 1px solid var(--color-border-default); }

/* ---------- Notebook card ---------- */
.notebook-card { display: flex; align-items: center; justify-content: space-between; gap: var(--spacing-lg); background: var(--color-bg-inverse); border-radius: var(--radius-lg); padding: var(--spacing-lg); margin: var(--spacing-2xl) 0; color: var(--color-text-inverse); }
.notebook-card .nc-title { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 18px; margin-bottom: 4px; }
.notebook-card .nc-sub { font-size: 14px; color: var(--gray-400); }
.notebook-card .nc-btn { flex-shrink: 0; background: linear-gradient(90deg, var(--violet-500), var(--blue-500)); color: white; text-decoration: none; padding: 10px 20px; border-radius: var(--radius-full); font-weight: 600; font-size: 14px; white-space: nowrap; }

/* ---------- Exercises ---------- */
.exercise { border: 1px solid var(--color-border-default); border-radius: var(--radius-md); padding: var(--spacing-lg); margin-bottom: var(--spacing-md); box-shadow: var(--shadow-sm); transition: box-shadow var(--duration-base) var(--ease-standard), border-color var(--duration-base) var(--ease-standard); }
.exercise.exercise-signal { display: flex; align-items: flex-start; gap: var(--spacing-md); }
.exercise:hover { box-shadow: var(--shadow-md); border-color: var(--color-border-default); }
.exercise-stars { color: var(--amber-500); font-size: 13px; letter-spacing: 2px; margin-bottom: 6px; }
.exercise-title { font-family: 'Sora', sans-serif; font-weight: 600; font-size: 16px; margin-bottom: 6px; }

/* ---------- Section nav (prev/next) ---------- */
.section-nav { display: flex; justify-content: space-between; gap: var(--spacing-lg); margin-top: var(--spacing-3xl); padding-top: var(--spacing-lg); border-top: 1px solid var(--color-border-default); }
.section-nav a { flex: 1; text-decoration: none; color: var(--color-text-primary); padding: var(--spacing-md); border: 1px solid var(--color-border-default); border-radius: var(--radius-md); transition: border-color var(--duration-base) var(--ease-standard), box-shadow var(--duration-base) var(--ease-standard), transform var(--duration-base) var(--ease-standard); }
.section-nav a:hover { border-color: var(--color-brand-blue); box-shadow: var(--shadow-sm); transform: translateY(-1px); }
.section-nav .dir { font-size: 12px; color: var(--color-text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
.section-nav .lbl { font-weight: 600; margin-top: 2px; }
.section-nav .next { text-align: right; }

/* ---------- Summary box ---------- */
.summary-box { background: var(--color-bg-surface); border-radius: var(--radius-lg); padding: var(--spacing-lg); margin: var(--spacing-2xl) 0; }
.summary-box h3 { margin-top: 0; }
.summary-box ul { margin-left: var(--spacing-lg); }

/* ---------- Predmetnyj ukazatel (index) ---------- */
.idx-list { list-style: none; margin: 0 0 var(--spacing-2xl); padding: 0; columns: 2; column-gap: var(--spacing-2xl); }
.idx-entry { display: flex; justify-content: space-between; gap: var(--spacing-sm); padding: 6px 0; border-bottom: 1px dashed var(--color-border-default); break-inside: avoid; }
.idx-term { flex: 1; min-width: 0; }
.idx-entry a { color: var(--color-text-primary); text-decoration: none; }
.idx-entry a:hover { color: var(--color-brand-blue); }
.idx-note { color: var(--color-text-muted); font-size: 13px; }
.idx-page { flex: none; font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--color-text-muted); white-space: nowrap; }

/* ---------- Comparison tables (tool/environment comparisons) ---------- */
.compare-table { width: 100%; border-collapse: collapse; font-size: 14px; min-width: 480px; }
@media (max-width: 480px) {
  .compare-table { min-width: 0; table-layout: fixed; }
  .compare-table th, .compare-table td { overflow-wrap: break-word; }
}
.compare-table th, .compare-table td { text-align: left; padding: 10px 14px; border-bottom: 1px solid var(--color-border-default); vertical-align: top; }
.compare-table th { font-family: 'Sora', sans-serif; font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: .04em; color: var(--color-text-muted); background: var(--color-bg-surface); }
.compare-table td:first-child, .compare-table th:first-child { font-weight: 600; color: var(--color-text-primary); }
.compare-table tbody tr:hover { background: var(--color-bg-surface); }
@media screen and (max-width: 640px) {
  .compare-table--cards,
  .compare-table--cards tbody,
  .compare-table--cards tr,
  .compare-table--cards td { display: block; width: 100%; }
  .compare-table--cards { table-layout: auto; }
  .compare-table--cards thead {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
  }
  .compare-table--cards tr {
    margin-bottom: var(--spacing-md);
    padding: var(--spacing-sm) var(--spacing-md);
    border: 1px solid var(--color-border-default);
    border-radius: var(--radius-md);
    background: var(--color-bg-surface);
  }
  .compare-table--cards td,
  .compare-table--cards td:first-child {
    padding: var(--spacing-sm) 0;
    border-bottom: 1px solid var(--color-border-default);
    overflow-wrap: normal;
    word-break: normal;
  }
  .compare-table--cards td:last-child { border-bottom: 0; }
  .compare-table--cards td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 4px;
    font-family: 'Sora', sans-serif;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    line-height: 1.35;
    text-transform: uppercase;
    color: var(--color-text-muted);
  }
}

/* ---------- Chapter figures: reusable sizing presets ----------
   A figure's img is never stretched to fill the article column at full
   width by default — .chapter-figure--* caps how wide it may grow, so a
   small supporting screenshot stays a supporting screenshot instead of
   ballooning into a giant blurry poster. Pick the preset by how much
   detail the figure needs to show, not by habit: narrow for a short
   before/after snippet, wide for evidence the reader must read without
   zooming (a real API response, a finished app screen). */
.chapter-figure { margin: 24px auto; }
.chapter-figure img { width: 100%; height: auto; border-radius: var(--radius-lg, 20px); border: 1px solid var(--color-border-default, #ddd); display: block; }
.chapter-figure figcaption { text-align: center; font-size: 13px; color: var(--color-text-muted, #6B6B7D); margin-top: 8px; }
.chapter-figure--narrow { max-width: 460px; }
.chapter-figure--medium { max-width: 620px; }
.chapter-figure--wide { max-width: 840px; }

/* Two figures compared side by side (e.g. before/after a click) — equal
   boxes so mismatched source-image proportions don't throw off the
   comparison; object-fit:contain shows each image whole, never cropped
   or distorted to fit. */
.chapter-figure-pair { display: flex; gap: 20px; margin: 24px 0; align-items: flex-start; justify-content: center; }
.chapter-figure-pair .chapter-figure { flex: 1 1 0; max-width: 420px; margin: 0; }
.chapter-figure-pair .chapter-figure-imgbox { display: flex; align-items: center; justify-content: center; height: 200px; background: var(--color-bg-surface, #FAFAFC); border-radius: var(--radius-lg, 20px); border: 1px solid var(--color-border-default, #ddd); overflow: hidden; padding: 12px; }
.chapter-figure-pair .chapter-figure-imgbox img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; display: block; }

/* An ordered chronological sequence of screenshots (step 1, 2, 3...) —
   always stacked vertically, desktop and mobile alike, so the reading
   order matches the process order. */
.chapter-figure-sequence { display: flex; flex-direction: column; gap: 28px; margin: 24px 0; align-items: center; }
.chapter-figure-sequence .chapter-figure { margin: 0; width: 100%; }

@media (max-width: 860px) {
  .chapter-figure-pair { flex-direction: column; }
  .chapter-figure-pair .chapter-figure { max-width: 100%; }
}

/* ---------- Mobile navigation drawer: Cartesian identity ----------
   Python.org's mobile menu is the interaction reference (viewport-anchored,
   full-width, unmissable open/close state, large tap targets) — none of its
   navy/yellow visual styling is used. This is drawn entirely from the
   existing violet/blue/navy token family plus one new accent
   (--cyan-400, added for "Практика" alongside "Главы"'s existing
   --blue-500 — the palette had violet + one blue but nothing to tell two
   blue-family items apart). */
/* Opacity only, deliberately — no transform: this animates the drawer
   itself, so a translateY here would mean its measured position (where
   the header-attachment/no-gap contract is verified) is briefly "wrong"
   for the ~200ms this runs, even though it settles correctly. The child
   entrance animations below (hero mark, rows) carry the translateY settle
   effect instead, where it's purely decorative and never affects the
   drawer's own geometry. */
@keyframes mobile-nav-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes mobile-nav-backdrop-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes mobile-nav-row-in { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }
@keyframes mobile-nav-hero-mark-in { from { opacity: 0; transform: scale(.85); } to { opacity: 1; transform: none; } }

/* ---------- Responsive ---------- */
@media (max-width: 860px) {
  /* .site-header renders shorter here (smaller padding, below) — keep the
     pre-JS fallback in the html{scroll-padding-top} rule accurate at this
     breakpoint too; nav.js's measured value supersedes this immediately. */
  :root { --site-header-height: 49px; }
  .layout { grid-template-columns: 1fr; padding: var(--spacing-md); gap: var(--spacing-lg); }
  .sidebar { position: static; max-height: none; display: none; }
  .mobile-nav-links { display: block; }

  /* The mobile drawer (id="mobile-nav-panel" — rendered as either .sidebar
     on chapter/front-matter pages or .mobile-nav-panel elsewhere, see
     site_lib.py's site_header()/mobile_nav_links()) must be anchored to the
     viewport, not to its position in document flow: a reader who has
     scrolled deep into the page opens it expecting it under the header
     they can see, not wherever that DOM node happens to sit near the top
     of the document (which is off-screen and unusable once scrolled).
     --mobile-nav-top tracks the sticky header's actual rendered bottom
     edge (site/assets/js/nav.js measures it — never hardcoded, since
     header height differs by viewport and content) so the drawer's top
     edge always lines up with it exactly. */
  .sidebar.open,
  .mobile-nav-panel.open {
    display: block;
    position: fixed;
    top: var(--mobile-nav-top, 64px);
    left: 0; right: 0;
    z-index: 39;
    margin: 0; padding: 0;
    background:
      radial-gradient(circle at 1px 1px, rgba(255,255,255,.05) 1px, transparent 0) 0 0 / 18px 18px,
      linear-gradient(180deg, var(--navy-950), var(--navy-900) 65%);
    color: var(--color-text-inverse);
    border-bottom: var(--border-hairline-inverse);
    box-shadow: var(--shadow-xl);
    max-height: calc(100dvh - var(--mobile-nav-top, 64px));
    overflow-y: auto;
    animation: mobile-nav-in 200ms var(--ease-out) both;
  }

  /* A dim, softly blurred backdrop behind the drawer — :has() lets this
     stay pure CSS (no extra DOM element threaded through 5 page templates,
     no JS coordination): it only ever matches while the drawer's own .open
     class is present, and disappears the instant nav.js removes it.
     Clicks on it already close the menu via nav.js's existing "click
     anywhere outside the panel/toggle" handler — the backdrop doesn't
     intercept anything special. Browsers without :has() simply never show
     a backdrop; the menu itself is unaffected either way. */
  body:has(#mobile-nav-panel.open)::before {
    content: "";
    position: fixed; inset: 0;
    z-index: 38;
    background: rgba(8, 1, 28, .45);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    animation: mobile-nav-backdrop-in 200ms var(--ease-out) both;
  }

  /* Brand block: identity reinforcement at the top of the drawer, restrained
     to roughly one third of a typical phone's screen height at most (not
     "half the phone") — see mobile_nav_links() in scripts/site_lib.py. */
  .mobile-nav-hero {
    position: relative; overflow: hidden;
    display: flex; flex-direction: column; align-items: center; text-align: center; gap: 6px;
    padding: 28px var(--spacing-lg) 22px;
    border-bottom: var(--border-hairline-inverse);
  }
  .mobile-nav-hero::before {
    content: ""; position: absolute; inset: -20% -10% auto -10%; height: 140px;
    background: radial-gradient(ellipse at 50% 0%, rgba(91, 36, 249, .35), transparent 70%);
    pointer-events: none;
  }
  .mobile-nav-hero__mark {
    position: relative; width: 56px; height: 56px; display: block;
    animation: mobile-nav-hero-mark-in 260ms var(--ease-out) both;
  }
  .mobile-nav-hero__word { position: relative; font-size: 17px; }
  .mobile-nav-hero__tag {
    position: relative; font-family: 'JetBrains Mono', monospace; font-size: 12px;
    color: var(--blue-300); letter-spacing: .02em;
  }

  /* Site-wide nav rows: large (58px) tap targets, one flat accent hue per
     item (a small always-on identity signal, not five differently-colored
     rows — see docs discussion in the PR), a route arrow, and a restrained
     entrance stagger (see keyframes above; capped near the top of this
     file). "Проекты" (violet -> blue) reads as the deliberate two-tone
     exception, matching its "violet-blue" spec, while the rest stay flat. */
  .mobile-nav-links .toc-list { margin: 0; padding: 0; }
  .mobile-nav-links .toc-list li {
    list-style: none;
    opacity: 0;
    animation: mobile-nav-row-in 220ms var(--ease-out) both;
  }
  .mobile-nav-links .toc-list li:nth-child(1) { animation-delay: 40ms; }
  .mobile-nav-links .toc-list li:nth-child(2) { animation-delay: 64ms; }
  .mobile-nav-links .toc-list li:nth-child(3) { animation-delay: 88ms; }
  .mobile-nav-links .toc-list li:nth-child(4) { animation-delay: 112ms; }
  .mobile-nav-links .toc-list li:nth-child(5) { animation-delay: 136ms; }
  .mobile-nav-links .toc-list a {
    --item-accent: var(--blue-300);
    position: relative;
    display: flex; align-items: center; justify-content: space-between; gap: 12px;
    min-height: 58px; padding: 0 var(--spacing-lg) 0 27px;
    background-image: linear-gradient(var(--item-accent), var(--item-accent));
    background-repeat: no-repeat; background-size: 3px 100%; background-position: left center;
    border-bottom: 1px solid rgba(255, 255, 255, .08);
    color: rgba(255, 255, 255, .92);
    font: 600 15px/1.3 'Sora', sans-serif; text-decoration: none;
    transition: background-color var(--duration-fast) var(--ease-standard);
  }
  .mobile-nav-links .toc-list a::after {
    content: "→"; flex: none;
    font-family: 'JetBrains Mono', monospace; font-size: 15px; color: rgba(255, 255, 255, .35);
    transition: transform var(--duration-fast) var(--ease-standard), color var(--duration-fast) var(--ease-standard);
  }
  .mobile-nav-links .toc-list a:hover,
  .mobile-nav-links .toc-list a:focus-visible { background-color: rgba(255, 255, 255, .05); }
  .mobile-nav-links .toc-list a:hover::after,
  .mobile-nav-links .toc-list a:focus-visible::after,
  .mobile-nav-links .toc-list a.active::after { color: var(--item-accent); transform: translateX(3px); }
  .mobile-nav-links .toc-list a[href$="#o-kurse"] { --item-accent: var(--violet-500); }
  .mobile-nav-links .toc-list a[href$="#glavy"] { --item-accent: var(--blue-500); }
  .mobile-nav-links .toc-list a[href$="#praktika"] { --item-accent: var(--cyan-400); }
  .mobile-nav-links .toc-list a[href$="#proekty"] {
    --item-accent: var(--blue-500);
    background-image: linear-gradient(var(--violet-500), var(--blue-500));
  }
  .mobile-nav-links .toc-list a[href$="#spravochnik"] { --item-accent: var(--violet-300); }
  .mobile-nav-links .toc-list a.active {
    background-color: color-mix(in srgb, var(--item-accent) 18%, transparent);
    background-size: 4px 100%;
    color: #fff;
  }

  /* The chapter/front-matter TOC (.sidebar's own content, a sibling of
     .mobile-nav-links — see render_page()/render_sidebar() in site_lib.py)
     shares this same dark drawer now, so its light-panel text colors need
     a dark-panel equivalent here. Scoped to .sidebar.open specifically so
     the always-light desktop sidebar (a completely different context) is
     untouched, and to direct children so it never reaches into
     .mobile-nav-links' own .toc-list one level down. */
  .sidebar.open > .sidebar-title { padding: 0 var(--spacing-lg); margin-top: var(--spacing-lg); color: rgba(255, 255, 255, .5); }
  .sidebar.open > .toc-list { padding: 0 var(--spacing-lg); }
  .sidebar.open > .toc-list a { color: rgba(255, 255, 255, .82); }
  .sidebar.open > .toc-list a:hover { background: rgba(255, 255, 255, .06); }
  .sidebar.open > .toc-list a.active { background: rgba(91, 36, 249, .22); color: #fff; }
  .sidebar.open > .toc-list .sub { color: rgba(255, 255, 255, .5); }

  .top-nav { display: none; }

  /* Hamburger <-> close: a 3-bar icon that morphs via CSS alone, driven by
     the aria-expanded nav.js already toggles — no extra JS. */
  .nav-toggle {
    display: flex; align-items: center; justify-content: center;
    width: 44px; height: 44px; padding: 0;
    background: rgba(255, 255, 255, .06); border: var(--border-hairline-inverse);
    border-radius: var(--radius-sm); cursor: pointer;
    transition: background var(--duration-fast) var(--ease-standard), border-color var(--duration-fast) var(--ease-standard);
  }
  .nav-toggle:hover { background: rgba(255, 255, 255, .12); border-color: rgba(255, 255, 255, .3); }
  .nav-toggle[aria-expanded="true"] { background: rgba(255, 255, 255, .14); border-color: rgba(143, 183, 254, .5); }
  .nav-toggle__bars { position: relative; width: 18px; height: 13px; display: block; }
  .nav-toggle__bars span {
    position: absolute; left: 0; right: 0; height: 2px; border-radius: 1px;
    background: var(--color-text-inverse);
    transition: transform var(--duration-base) var(--ease-standard), opacity var(--duration-fast) var(--ease-standard), top var(--duration-base) var(--ease-standard);
  }
  .nav-toggle__bars span:nth-child(1) { top: 0; }
  .nav-toggle__bars span:nth-child(2) { top: 50%; margin-top: -1px; }
  .nav-toggle__bars span:nth-child(3) { top: 100%; margin-top: -2px; }
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(1) { top: 50%; margin-top: -1px; transform: rotate(45deg); }
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(2) { opacity: 0; }
  .nav-toggle[aria-expanded="true"] .nav-toggle__bars span:nth-child(3) { top: 50%; margin-top: -1px; transform: rotate(-45deg); }

  article h1 { font-size: 26px; }
  .technology-heading { gap: 9px; }
  .technology-heading__brands { min-height: 32px; }
  .technology-heading__mark--git { width: 31px; height: 31px; }
  .technology-heading__brands--pair { align-items: flex-start; gap: 4px; }
  .technology-heading__brand-token { gap: 3px; font-size: 11px; }
  .technology-heading__brand-token .technology-heading__mark--git { width: 28px; height: 28px; }
  .technology-heading__mark--github { width: 29px; height: 28px; }
  .technology-heading__not-equal { font-size: 14px; line-height: 28px; }
  .cvm-grid { grid-template-columns: 1fr; }
  .cvm-col + .cvm-col { border-left: none; border-top: 1px solid var(--color-border-default); }
  .notebook-card { flex-direction: column; align-items: flex-start; }
  .site-header { padding: var(--spacing-sm) var(--spacing-md); }
  .idx-list { columns: 1; }
}

/* ---------- Back to top ----------
   Site-wide (homepage, chapters, front matter, practice, projects): built
   and managed entirely by site/assets/js/nav.js, which already loads on
   every page, so this needs no threading through the 5 separate page
   templates that would otherwise each need their own copy. */
.back-to-top {
  position: fixed;
  right: 14px;
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  z-index: 30;
  display: flex; align-items: center; justify-content: center;
  width: 46px; height: 46px;
  border-radius: var(--radius-full);
  background: linear-gradient(155deg, var(--navy-900), var(--navy-950));
  border: var(--border-hairline-inverse);
  box-shadow: var(--shadow-lg), 0 0 0 1px rgba(91, 36, 249, .25);
  color: var(--color-text-inverse);
  cursor: pointer;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--duration-base) var(--ease-standard), transform var(--duration-base) var(--ease-standard), box-shadow var(--duration-fast) var(--ease-standard);
}
.back-to-top.visible { opacity: 1; transform: none; }
.back-to-top:hover { box-shadow: var(--shadow-glow-violet); transform: translateY(-2px); }
.back-to-top:disabled { cursor: default; }
.back-to-top svg { width: 18px; height: 18px; display: block; }
@media (min-width: 861px) {
  .back-to-top { right: 28px; bottom: 28px; }
}
body:has(#mobile-nav-panel.open) .back-to-top { display: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
}
