/* ==========================================================================
   EnigmaERP documentation theme

   Fully grayscale — no hue anywhere, matching the source site, whose every
   colour measures as oklch(L 0 0), i.e. zero chroma.

   There is no accent. Links are not tinted and are not underlined; they are
   set in the heading colour at weight 500 against lighter body text, which is
   exactly how the source distinguishes them. Do not reintroduce a hue for
   emphasis — weight and value carry it.

   Everything is defined in the three blocks below. Change it there, not
   further down.
   ========================================================================== */

/* The neutral ramp. Values converted from the source site's oklch lightnesses:
   body text oklch(0.341) -> #383838, headings and links oklch(0.168) ->
   #0f0f0f, page oklch(1) -> #ffffff. The previous ramp was very slightly
   blue (#17191c has more blue than red); these are true greys. */
:root {
  --enigma-ink: #171717; /* PRIMARY — header, headings   */
  --enigma-ink-deep: #0d0d0d; /* footer                       */
  --enigma-ink-soft: #3d3d3d;

  --enigma-grey-600: #5e5e5e;
  --enigma-grey-500: #8a8a8a;
  --enigma-grey-200: #e5e5e5;
  --enigma-grey-100: #f2f2f2;
  --enigma-grey-050: #f9f9f9;

  /* Emphasis: what the accent used to do. Near-black on light, near-white on
     dark — value contrast rather than hue. */
  --enigma-emphasis: #0f0f0f;
  --enigma-emphasis-dark: #f2f2f2;

  /* The header's measured height. Three things need it and none can read it
     from the theme: the rails size themselves against it, and anchors need it
     as scroll-margin or a heading lands behind the header. Material's own
     2.4rem resolves to 48px only because the root is 20px — spell it out. */
  --enigma-header-h: 48px;

  /* Stroked magnifier for the search field, used as a mask so it takes its
     colour from currentColor. Scheme-independent: only the alpha matters. */
  --enigma-search-glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23000' stroke-width='2.1' stroke-linecap='round'%3E%3Ccircle cx='10.5' cy='10.5' r='6.75'/%3E%3Cpath d='M20 20l-4.7-4.7'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------- light --- */
[data-md-color-scheme="enigma"] {
  --md-primary-fg-color: var(--enigma-ink);
  --md-primary-fg-color--light: var(--enigma-ink-soft);
  --md-primary-fg-color--dark: var(--enigma-ink-deep);
  --md-accent-fg-color: var(--enigma-emphasis);
  /* Foreground for anything the theme fills WITH the accent. The accent is
     now a near-black/near-white value rather than a mid-tone hue, so a
     component that paints it as a background needs the opposite end of the
     ramp for its text — the theme's default here is white, which was
     invisible on our light-mode accent. */
  --md-accent-bg-color: #ffffff;

  --md-default-bg-color: #ffffff;
  --md-default-fg-color: var(--enigma-emphasis);
  --md-default-fg-color--light: var(--enigma-grey-600);
  --md-default-fg-color--lighter: var(--enigma-grey-500);

  /* Prose sits lighter than headings, as on the source site — that gap is
     what makes a link read as a link once the hue is gone. */
  --md-typeset-color: #383838;
  --md-typeset-a-color: var(--enigma-emphasis);
  --md-code-bg-color: var(--enigma-grey-100);
  --md-code-fg-color: #2e2e2e;

  --enigma-surface: var(--enigma-grey-050);
  --enigma-border: var(--enigma-grey-200);
  --enigma-note-bg: var(--enigma-grey-050);

  --enigma-search-bg: var(--enigma-grey-100);
  --enigma-search-bg-hover: var(--enigma-grey-200);

  --enigma-nav-active-bg: var(--enigma-grey-100);

  /* Rail scrollbar. Mid-grey rather than the near-black the browser default
     paints: a 6px bar at that value reads as a hard black stripe down the
     nav. Grey enough to find, quiet enough to ignore. */
  --enigma-scrollbar: #c4c4c4;
  --enigma-scrollbar-hover: #a8a8a8;
}

/* ---------------------------------------------------------------- dark --- */
[data-md-color-scheme="enigma-dark"] {
  color-scheme: dark;

  --md-primary-fg-color: #131313;
  --md-primary-fg-color--light: #2b2b2b;
  --md-primary-fg-color--dark: #0a0a0a;
  --md-accent-fg-color: var(--enigma-emphasis-dark);
  --md-accent-bg-color: #101010;

  --md-default-bg-color: #101010;
  --md-default-fg-color: var(--enigma-emphasis-dark);
  --md-default-fg-color--light: #a0a0a0;
  --md-default-fg-color--lighter: #7a7a7a;

  /* Mirrors the light scheme's heading/prose gap rather than inverting the
     hex values, so links stay distinguishable by value here too. */
  --md-typeset-color: #c8c8c8;
  --md-typeset-a-color: var(--enigma-emphasis-dark);

  --md-code-bg-color: #1a1a1a;
  --md-code-fg-color: #d6d6d6;

  --enigma-surface: #171717;
  --enigma-border: #2a2a2a;
  --enigma-note-bg: #161616;

  /* The search pill needs to read as an inset field against the header, so it
     sits a step lighter than the page rather than reusing --enigma-surface. */
  --enigma-search-bg: #1f1f1f;
  --enigma-search-bg-hover: #262626;

  --enigma-nav-active-bg: #272727;

  /* Dark counterpart. The same trap inverted — a light thumb on a #101010
     rail glares, so this sits only a little above the surface grey. */
  --enigma-scrollbar: #3a3a3a;
  --enigma-scrollbar-hover: #4d4d4d;
}

/* ------------------------------------------------------------ typography - */
/* Measured off the source: 15px prose on a 25.5px line (1.7), h1 24px and
   h2 19.3px, both at weight 600. Our headings had been eyeballed from a
   screenshot and were running ~25% large. */
.md-typeset {
  font-size: 0.68rem;
  line-height: 1.7;
}

.md-typeset h1 {
  font-size: 1.6em;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--md-default-fg-color);
  margin-bottom: 0.8em;
}

/* No rule above headings — the source site separates sections with
   whitespace alone. Keep it that way. */
.md-typeset h2 {
  font-size: 1.29em;
  font-weight: 600;
  letter-spacing: -0.012em;
  margin-top: 2em;
  padding-top: 0;
  border-top: 0;
}

.md-typeset h3 {
  font-weight: 600;
  letter-spacing: -0.008em;
}

/* Heading anchors sit out in the left margin rather than trailing the text,
   as on the source site. The theme already fades them in on hover, so only
   placement and weight change here — pulling them out of the flow also stops
   them nudging the heading when they appear. */
.md-typeset h1,
.md-typeset h2,
.md-typeset h3,
.md-typeset h4,
.md-typeset h5,
.md-typeset h6 {
  position: relative;
  /* Headings take the darker end of the ramp; only h1 had this set, so the
     rest were inheriting prose colour and sitting too light against it. */
  color: var(--md-default-fg-color);
  /* Anchors must clear the fixed header. Measured at 0, so following a
     contents link scrolled the heading to y=0 — behind the header — and the
     entry the scroll-spy then reported was the section above the one on
     screen. The extra 8px is breathing room, not padding. */
  scroll-margin-top: calc(var(--enigma-header-h) + 8px);
}

/* rem, not em: `left` in em resolves against the anchor's own font-size
   rather than the heading's, so it barely moved. rem also keeps every marker
   on one vertical line regardless of heading level. */
.md-typeset .headerlink {
  position: absolute;
  left: -1.05rem;
  margin: 0;
  color: var(--md-default-fg-color--lighter);
  font-weight: 400;
}

/* The theme's own margin is set from `[dir=ltr] .md-typeset .headerlink`,
   whose extra attribute selector beats a plain `margin: 0` above — it was
   pushing the marker 11px back toward the heading and cancelling most of the
   offset. Matched here so source order decides in our favour. */
[dir] .md-typeset .headerlink {
  margin-inline: 0;
}

.md-typeset .headerlink:hover {
  color: var(--md-accent-fg-color);
}

/* --------------------------------------------------- top bar (announce) -- */
/* A persistent route back to the product site. Deliberately quiet: it sits
   above the header, so it must not compete with it. */
.md-banner {
  background: var(--md-primary-fg-color--dark);
  color: rgba(255, 255, 255, 0.72);
}

.md-banner__inner {
  margin: 0;
}

.enigma-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  max-width: 61rem;
  margin: 0 auto;
  padding: 0.32rem 0.8rem;
  font-size: 0.62rem;
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.72);
}

.enigma-topbar a {
  color: #fff;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}

.enigma-topbar a:hover {
  text-decoration: underline;
}

@media screen and (max-width: 44em) {
  .enigma-topbar span {
    display: none;
  }
  .enigma-topbar {
    justify-content: flex-end;
  }
}

/* ---------------------------------------------------------------- header - */
/* The header sits on the page surface rather than above it in a dark bar:
   it takes the page background and is separated by a hairline only. Both
   colours come from the scheme, so this flips correctly in dark mode. */
.md-header {
  height: 48px;
  background-color: var(--md-default-bg-color);
  color: var(--md-default-fg-color);
  box-shadow: none;
  border-bottom: 1px solid var(--enigma-border);
}

.md-header__inner {
  position: relative;
  height: 48px;
}

.md-header__button.md-logo img {
  width: 1.4rem;
  height: 1.4rem;
  object-fit: contain;
}

/* One mark per scheme. The dark mark carries the light header; the white one
   carries the dark header. Only one is ever displayed.

   Selector has to name `.md-logo img` rather than just the class: the theme
   sets `display: block` from `.md-header__button.md-logo img`, whose element
   selector outranks a bare `[scheme] .class` and left both marks stacked. */
[data-md-color-scheme="enigma"] .md-logo img.enigma-logo--on-dark,
[data-md-color-scheme="enigma-dark"] .md-logo img.enigma-logo--on-light {
  display: none;
}

/* ---------------------------------------------------------- header lockup */
/* Mark and wordmark are separate elements, so they only read as one unit if
   they are set that way: the theme leaves 26px between them and sets the
   wordmark at 19.8px, which reads as an icon and a page heading that happen to
   be adjacent. Tighten the gap and drop the wordmark to the same weight and
   size the source uses. */
.md-header__button.md-logo {
  padding: 0;
  margin: 0 0 0 0.55rem;
}

/* The theme stacks the two topics by absolutely positioning them, which leaves
   the title itself zero-width. That was harmless while the title was a growing
   flex item and the text simply overflowed — but centring the search made it
   `flex: 0 1 auto`, and .md-ellipsis clips at overflow:hidden, so the wordmark
   vanished entirely while still measuring as present in the DOM. With only one
   topic left there is nothing to stack, so lay it out normally. */
.md-header__title .md-header__ellipsis,
.md-header__title .md-header__topic {
  position: static;
}

/* …and the theme gives the ellipsis `height: 100%`, which would re-create the
   full-height box inside the centred title and park the text at its top. */
.md-header__title .md-header__ellipsis {
  height: auto;
}

/* The theme sizes this box to the full header height (2.4rem) and centres the
   text inside it with a matching `line-height`. Overriding the line-height to
   1.2 without touching the height left the wordmark sitting in the top 17px of
   a 48px box — ~16px above the mark, the search pill and the header links,
   which the flex row centres on the header's own midline. Centre the line box
   inside the title instead of leaning on line-height to do it. */
.md-header__title {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  font-size: 0.7rem;
  line-height: 1.2;
  font-weight: 600;
  letter-spacing: -0.012em;
  color: var(--md-default-fg-color);
}

/* The theme's own margin here is `[dir=…]`-scoped, so it needs matching. */
[dir] .md-header__title {
  margin-left: 0.45rem;
}

/* The theme fades the current page's title in over the site name once you
   scroll. That is reasonable for a plain title bar, but here it would swap the
   wordmark out of the lockup and leave the mark beside an unrelated heading. */
.md-header__topic[data-md-component="header-topic"] {
  display: none;
}

/* Hiding the incoming topic is only half of it: the swap is a crossfade, and
   the outgoing half still runs. Material adds `.md-header__title--active` on
   scroll, which sets the FIRST topic to `opacity: 0` and slides it up — so
   past the fold the wordmark faded out and nothing faded in, leaving the mark
   beside empty space. Pin it visible in both states.

   Both declarations are needed: opacity alone still leaves the transform, and
   the theme's rule carries `--active` so it outweighs a bare class. */
.md-header__title .md-header__topic:first-child,
.md-header__title--active .md-header__topic:first-child {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  z-index: auto;
}

/* Material collapses the palette switcher to max-width:0 and lets its one
   visible label overflow the box. That is invisible while the switcher is the
   last thing in the header — but the website button now follows it, and the
   overflowing label sits on top of it. Give the form its real width back.

   The theme re-collapses it from a checked-state selector when search opens,
   so that case needs matching specificity or the two icons stack again the
   moment you focus the search box. */
.md-header__option,
[data-md-toggle="search"]:checked ~ .md-header .md-header__option {
  max-width: none;
}

/* The theme also fades the switcher out while search is open, to free up room
   for a box that widens on focus. Ours no longer widens, so there is no room
   to free — and one of the two icons disappearing while its neighbour stays
   just looks broken. */
[data-md-toggle="search"]:checked ~ .md-header .md-header__option {
  opacity: 1;
}

/* Search: a centred pill of fixed width, the way the source site frames it,
   rather than Material's right-aligned box that widens on focus.

   Centred with auto margins, NOT by positioning it absolutely: Material's
   search overlay is absolutely positioned and its JS sizes it from the search
   box's own geometry, so taking the box out of the flex flow leaves the
   overlay covering a 430px column instead of the viewport. */
@media screen and (min-width: 60em) {
  .md-header__title {
    flex: 0 1 auto;
  }

  .md-search {
    flex: 0 0 430px;
    margin-inline: auto;
    padding: 0;
  }

  .md-search__inner {
    width: 100%;
    float: none;
  }

  /* Material widens the box to 34.4rem while it has focus. We want the pill
     to hold one width in both states, and this rule has to carry the theme's
     own specificity to win — a bare .md-search__inner loses to it. Left
     unpinned, the flex item grows to fit the wider inner and the whole header
     shifts every time the box is focused. */
  [data-md-toggle="search"]:checked ~ .md-header .md-search__inner {
    width: 100%;
  }

  /* The palette switcher and the website button sit before the search box in
     the theme's markup, which put them hard left of it once the box became a
     normal flex item. Order them explicitly instead of relying on source
     order: title, then the centred search, then the buttons on the right. */
  .md-header__title {
    order: 1;
  }

  .md-search {
    order: 2;
  }

  .enigma-header-nav {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    margin-right: 0.88rem;
    order: 3;
  }

  .md-header__option {
    order: 4;
  }
}

/* Text routes out of the docs, sized and spaced off the source site: ~15px,
   ~20px between links, ~24px before the icon buttons. Hidden below the
   breakpoint where the search pill appears — the header is too tight for them
   on a phone, and the footer carries both links anyway. */
/* Scoped to the narrow side rather than a bare `display: none`, which would
   sit after the flex rule above and beat it on source order. */
@media screen and (max-width: 59.98em) {
  .enigma-header-nav {
    display: none;
  }
}

.enigma-header-nav a {
  font-size: 0.68rem;
  font-weight: 400;
  color: var(--md-default-fg-color--light);
  text-decoration: none;
  white-space: nowrap;
  transition: color 120ms;
}

.enigma-header-nav a:hover {
  color: var(--md-default-fg-color);
}

.md-search__form {
  position: relative;
  height: 38px;
  border-radius: 8px;
  background-color: var(--enigma-search-bg);
  border: 1px solid var(--enigma-border);
  transition: background-color 140ms, border-color 140ms;
}

/* Keyboard hint. The fallback is "/" because that is a shortcut the theme
   binds on its own; page-tools.js swaps in the platform's ⌘K / Ctrl K only
   after it has actually bound that combination, so the badge never advertises
   a key that does nothing. */
@media screen and (min-width: 60em) {
  .md-search__form::after {
    content: var(--enigma-search-key, "/");
    position: absolute;
    top: 50%;
    right: 0.7rem;
    transform: translateY(-50%);
    font-size: 0.6rem;
    line-height: 1.6;
    letter-spacing: 0.02em;
    color: var(--md-default-fg-color--lighter);
    pointer-events: none;
  }

  [data-md-toggle="search"]:checked ~ .md-header .md-search__form::after {
    display: none;
  }
}

.md-search__form:hover {
  background-color: var(--enigma-search-bg-hover);
}

/* The suggestion is a separate overlay drawn under the input at the same
   origin, so the two only line up while their type matches exactly. Resizing
   the input alone left the completion wider than what had been typed, and the
   two rendered on top of each other. */
.md-search__input,
.md-search__suggest {
  font-size: 0.66rem;
}

.md-search__input {
  color: var(--md-default-fg-color);
}

.md-search__input::placeholder {
  color: var(--md-default-fg-color--lighter);
}

/* The leading magnifier.

   Material ships MDI's `magnify`, a solid glyph with a heavy ring, coloured
   with --md-primary-bg-color (white — it assumes a dark primary header bar).
   Matching its size to the text was not enough: a filled mark reads much
   heavier than Inter's letterforms beside it, which is what kept it looking
   wrong even at the right dimensions. Swap in a stroked outline instead,
   masked from currentColor so it still follows the scheme.

   Absolutely positioned because the theme's own placement left it sitting
   ~2px below the text's optical centre.

   Scoped with a child combinator: the clear and share buttons inside
   .md-search__options carry the same class and keep the theme's treatment. */
.md-search__form > .md-search__icon {
  position: absolute;
  top: 50%;
  left: 0.62rem;
  transform: translateY(-50%);
  width: auto;
  height: auto;
  margin: 0;
  padding: 0;
  color: var(--md-default-fg-color--lighter);
}

.md-search__form > .md-search__icon svg {
  display: none;
}

.md-search__form > .md-search__icon::after {
  content: "";
  display: block;
  width: 0.74rem;
  height: 0.74rem;
  background-color: currentColor;
  -webkit-mask: var(--enigma-search-glyph) center / contain no-repeat;
  mask: var(--enigma-search-glyph) center / contain no-repeat;
}

[data-md-toggle="search"]:checked ~ .md-header .md-search__form {
  border-radius: 8px 8px 0 0;
}

/* ---------------------------------------------------------------- tables - */
.md-typeset table:not([class]) {
  border: 1px solid var(--enigma-border);
  border-radius: 8px;
  font-size: 0.72rem;
  overflow: hidden;
}

.md-typeset table:not([class]) th {
  background-color: var(--enigma-surface);
  font-weight: 600;
  white-space: nowrap;
}

.md-typeset table:not([class]) td,
.md-typeset table:not([class]) th {
  border-bottom: 1px solid var(--enigma-border);
}

/* Wide tables scroll inside their own box instead of the page */
.md-typeset__table {
  overflow-x: auto;
}

/* -------------------------------------------------------------- figures -- */
.md-typeset img {
  border: 1px solid var(--enigma-border);
  border-radius: 8px;
  max-width: 100%;
}

/* Placeholder shown for screenshots that have not been synced yet */
.md-typeset img[src$="placeholder.svg"] {
  border-style: dashed;
  opacity: 0.85;
}

/* ------------------------------------------------------ attribution bar -- */
.enigma-attribution {
  font-size: 0.62rem;
  line-height: 1.6;
  color: var(--md-default-fg-color--lighter);
  border-top: 1px solid var(--enigma-border);
  margin-top: 2.5rem;
  padding-top: 0.8rem;
}

.enigma-attribution a {
  color: inherit;
  text-decoration: underline;
}

/* ------------------------------------------------------------ home grid -- */
.enigma-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 0.9rem;
  margin: 1.6rem 0 2rem;
}

.enigma-card {
  display: block;
  padding: 1rem 1.05rem;
  background: var(--enigma-surface);
  border: 1px solid var(--enigma-border);
  border-radius: 10px;
  color: inherit !important;
  text-decoration: none !important;
  transition: border-color 140ms, transform 140ms;
}

.enigma-card:hover {
  border-color: var(--md-accent-fg-color);
  transform: translateY(-1px);
}

.enigma-card h3 {
  margin: 0 0 0.3rem !important;
  font-size: 0.78rem;
  font-weight: 600;
}

.enigma-card p {
  margin: 0 !important;
  font-size: 0.68rem;
  color: var(--md-default-fg-color--light);
}

/* ------------------------------------------------------------- geometry --- */
/* "Full width" here means the two rails sit at the screen edges — not that
   the prose stretches with the window. A 735px measure is what keeps the
   source site readable on a wide monitor; the leftover space becomes the
   gutter that gives the page its air. Removing the cap makes 120-character
   lines on a 1600px screen, which is what made our version feel dense. */
.md-grid {
  max-width: none;
}

.md-content__inner {
  max-width: 735px;
  padding-inline: 1rem;
}

/* Material pins this column to a 1.2rem margin from a selector carrying more
   specificity than a bare class, so centring it needs the flag — matching
   that selector by hand would break the next time the theme rewrites it. */
@media screen and (min-width: 76.25em) {
  .md-content__inner {
    margin-inline: auto !important;
  }
}

/* --------------------------------------------------------------- sidebar -- */
.md-sidebar--primary,
.md-sidebar--secondary {
  width: 285px;
}

/* Both rails start close under the header. The theme stacks 33px of shared
   top margin (which the article needs, so it stays), 26px of its own padding
   and 11px more from .md-sidebar__inner — 70px in total before the first row.
   Zero the two that belong to the rail and pull back the share of the margin
   that the article wants but the nav does not.

   The 14px is padding *inside* the rail rather than a margin above it: the
   theme's JS pins the sticky `top` to the header height, so a margin-based
   gap is discarded the moment you scroll and the rows jump up under the
   header. Held as padding, it travels with the rail. */
@media screen and (min-width: 76.25em) {
  .md-sidebar {
    margin-top: -1.5rem;
    /* 9, not 14: the theme's JS already parks the rail 5px below the header,
       and that offset applies in both the resting and stuck states. */
    padding-top: 9px;
  }
}

/* The site title is already in the header; repeating it at the top of the
   rail just costs a row. */
@media screen and (min-width: 76.25em) {
  .md-nav--primary > .md-nav__title {
    display: none;
  }
}

/* Disclosure arrow to the left of the label, the way a file tree reads.
   Material ships it flush right, which is the single detail that most makes
   the two sidebars look unrelated at a glance.

   Two shapes to cover: with `navigation.indexes` a section that has an
   index page renders as a container div holding <a> then <label>, and the
   arrow lives in that trailing label; a section without one renders as a
   single <label> holding the text then the icon. */
.md-nav__item--nested > .md-nav__container {
  justify-content: flex-start;
}

.md-nav__item--nested > .md-nav__container > label.md-nav__link {
  order: -1;
  margin: 0 0.15rem 0 0;
  padding-inline: 0;
}

/* The theme's chevron box is 20px around a much smaller glyph, which pushes
   every section label a third of the way across the rail. */
.md-nav__item--nested > .md-nav__container > label.md-nav__link .md-nav__icon,
.md-nav__item--nested > label.md-nav__link .md-nav__icon {
  width: 0.62rem;
  height: 0.62rem;
}

.md-nav__item--nested > label.md-nav__link {
  justify-content: flex-start;
}

/* The theme pushes this chevron to the far end with `margin-inline-start:
   auto`, from a `[dir=…]`-prefixed rule. Re-ordering it alone is not enough —
   the auto margin still eats all the free space ahead of it and shoves both
   the arrow and the label to the right of their own children. Has to be
   cancelled with the same logical property and enough specificity to win. */
[dir] .md-nav__item--nested > label.md-nav__link > .md-nav__icon,
[dir] .md-nav__item--nested > .md-nav__container > label.md-nav__link > .md-nav__icon {
  order: -1;
  margin-inline: 0 0.35rem;
}

/* Both rails own a fixed slice of the viewport and scroll inside it.
   Without a height the sidebar box collapses (33px measured) while its
   scrollwrap grows to the full 1423px of nav, so nothing scrolls internally:
   the rail rides the page instead, and the footer coming up under it squeezes
   the column. Pin the height to the viewport below the header and cap the
   scrollwrap to it, and the rail stays put with its own scrollbar. */
@media screen and (min-width: 76.25em) {
  .md-sidebar {
    height: calc(100vh - var(--enigma-header-h));
  }

  .md-sidebar__scrollwrap {
    max-height: 100%;
    overflow-y: auto;
    /* Room for the scrollbar so it does not sit on top of the labels. */
    padding-right: 6px;
  }

  /* The article rail stays put for the whole page, footer included.
     `position: sticky` cannot do that: it releases at the end of its
     containing block, and .md-main ends where the footer starts, so the rail
     slid up by the footer's height (measured -142px) exactly when the reader
     reached the bottom and might want to navigate on.

     Fix the scrollwrap rather than the sidebar itself — the sidebar stays a
     285px flex item holding the column open, so the content beside it does
     not reflow. The layout is full-width with the rail flush at left: 0, so
     there is no grid offset to track. */
  .md-sidebar--primary .md-sidebar__scrollwrap {
    position: fixed;
    top: var(--enigma-header-h);
    left: 0;
    /* The theme's own `margin: 0 4px` still applies and offsets this box to
       x=4, so a full 285px would end at 289 — 4px past the divider .md-main
       draws at 285, putting the scrollbar right on top of the line. Subtract
       both margins so the right edge lands inside it. */
    width: calc(285px - 8px);
    /* !important because the theme's JS writes `height: <px>` INLINE on this
       element, and an inline declaration beats any stylesheet rule without it.
       It measured 756px against the 852px the column actually has, so the rail
       stopped 96px above the fold — the list looked cut off partway down.

       Material sizes it that way for a sticky rail that has to stop short of
       the footer. Ours is fixed and there is no footer, so its arithmetic no
       longer describes our layout. Same class of trap as the sticky `top`,
       which the theme also sets from JS. */
    height: calc(100vh - var(--enigma-header-h)) !important;
    max-height: none;
    /* The 9px that the shared rail padding used to provide. It has to move in
       here with the box it was spacing. */
    padding-top: 9px;
    box-sizing: border-box;
  }
}

/* The rail's scrollbar is a permanent dark stripe at default weight; thin it
   down so it reads as a hairline against the nav.

   Its own token rather than --enigma-border: the border grey is tuned to be
   near-invisible as a hairline, which leaves the thumb too faint to grab in
   light mode and — as a wide bar rather than a 1px line — too bright in dark.
   Both schemes define it explicitly below. */
.md-sidebar__scrollwrap {
  scrollbar-width: thin;
  scrollbar-color: var(--enigma-scrollbar) transparent;
}

.md-sidebar__scrollwrap::-webkit-scrollbar {
  width: 6px;
}

.md-sidebar__scrollwrap::-webkit-scrollbar-track {
  background: transparent;
}

.md-sidebar__scrollwrap::-webkit-scrollbar-thumb {
  background-color: var(--enigma-scrollbar);
  border-radius: 3px;
}

.md-sidebar__scrollwrap::-webkit-scrollbar-thumb:hover {
  background-color: var(--enigma-scrollbar-hover);
}

/* Row pitch. Material's default rhythm runs ~55px per row against the source
   site's ~30px, which is most of why our rail felt loose next to theirs. */
.md-nav__link {
  margin-top: 0;
  padding-block: 0.2rem;
  line-height: 1.45;
}

/* The wrapper around a section's link + chevron carries the .md-nav__link
   class itself, so the rule above lands on both it and the anchor inside,
   padding every section row twice over. It is a layout box, not a row. */
.md-nav__link.md-nav__container {
  padding-block: 0;
}

/* Only the top-level list is flush. Scoping this to every list is what left
   child rows sitting further LEFT than the parent they belong to, which reads
   as an inverted hierarchy — and is why the children looked larger than the
   section heading despite both being 14.96px. */
.md-nav--primary > .md-nav__list {
  padding-inline-start: 0;
}

/* An expanded section indents its children and marks the group with a guide
   line, so the run of rows reads as belonging to the heading above it. */
.md-nav__item--nested > .md-nav > .md-nav__list {
  margin-inline-start: 0.62rem;
  padding-inline-start: 0.8rem;
  border-inline-start: 1px solid var(--enigma-border);
}

/* Long titles truncate rather than wrapping onto a second line — the rail
   holds ~28 characters and a wrapped row breaks the 30px rhythm. */
.md-nav__link .md-ellipsis {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The page you are on is marked by a filled row rather than by tinting the
   text — matching the source site, where the current item reads in the same
   colour as its neighbours and only the fill sets it apart.

   The padding is cancelled by an equal negative margin so the fill extends
   past the label on both sides without shifting the text out of line with
   the rows above and below it. */
.md-nav--primary .md-nav__link--active {
  background: var(--enigma-nav-active-bg);
  border-radius: 6px;
  color: var(--md-default-fg-color);
  padding-inline: 0.5rem;
  margin-inline: -0.5rem;
  /* The anchor shrink-wraps its label rather than filling the row, which
     would end the fill at whatever the title happens to be — short entries
     got a stub. Widened so every selected row reads the same length. */
  box-sizing: border-box;
  width: calc(100% + 0.9rem);
}

/* A section whose only child is its own index page renders with no chevron,
   so its label loses the gutter every sibling has and hangs 31px to the left.
   `:only-child` is exactly that case: the link with no disclosure beside it. */
.md-nav__container > a.md-nav__link:only-child,
.md-nav--primary > .md-nav__list > .md-nav__item > a.md-nav__link {
  padding-left: 1.4rem;
}

/* Hairlines between each rail and the page, running the full height as on the
   source site.

   Drawn on .md-main rather than as borders on the sidebars: Material gives
   .md-sidebar a height of ~53px and lets the scrollwrap inside it overflow,
   so a border on the rail itself renders as a short stub near the top.

   .md-main's box already ends exactly at the rail boundaries and excludes the
   scrollbar, so `left`/`right: 285px` land on them without having to guess at
   scrollbar width. min-height keeps the rules running to the bottom of the
   window on pages too short to fill it. */
@media screen and (min-width: 76.25em) {
  .md-main {
    position: relative;
    min-height: calc(100vh - 48px);
  }

  .md-main::before {
    content: "";
    position: absolute;
    top: 0;
    bottom: 0;
    left: 285px;
    width: 1px;
    background: var(--enigma-border);
    pointer-events: none;
  }
}

/* -------------------------------------------------------- modern enhancements */

/* Better navigation styling */
.md-nav__item {
  transition: background-color 120ms, color 120ms;
}

.md-nav__link {
  transition: color 120ms, background-color 120ms;
  border-radius: 4px;
}

.md-nav__link:hover {
  color: var(--md-default-fg-color);
}

/* Improved code styling */
.md-typeset code {
  padding: 0.2em 0.4em;
  border-radius: 4px;
  font-size: 0.95em;
}

.md-typeset pre {
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

[data-md-color-scheme="enigma-dark"] .md-typeset pre {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
}

/* Better blockquote styling */
.md-typeset blockquote {
  border-left: 4px solid var(--md-accent-fg-color);
  padding-left: 1rem;
  margin-left: 0;
  opacity: 0.95;
}

/* With no hue and no underline, weight plus the darker value is the entire
   affordance — the same treatment the source site uses. */
.md-typeset a {
  font-weight: 500;
  text-decoration: none;
  transition: color 120ms;
}

.md-typeset a:hover {
  text-decoration: underline;
}

/* Better admonition styling */
.md-typeset .admonition {
  border-radius: 8px;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
}

[data-md-color-scheme="enigma-dark"] .md-typeset .admonition {
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
}

.md-sidebar__inner {
  padding: 0 0 0.5rem;
}

.md-nav {
  font-size: 0.68rem;
}

/* ------------------------------------------------------- table of contents */
/* Material's label is "Table of contents" and it comes from the theme's
   translation catalogue, not the config — swapping the visible string here
   is cheaper than shipping a language override for one line. */
/* The track must land on a whole pixel. Material indents this rail with a
   4.4px margin and 13.2px padding — both rem values against a 22px root, so
   both are fractional, and a 1px rule on a half-pixel boundary is antialiased
   across two device pixels. The near-white idle segments hide that, but the
   dark active one renders soft and half a pixel off, which is what makes the
   line look broken where it changes colour. Whole-pixel indent instead. */
.md-sidebar--secondary .md-sidebar__scrollwrap {
  margin-left: 0;
}

.md-sidebar--secondary .md-nav {
  padding-left: 18px;
}

/* Needs the extra specificity: the theme sets this list's padding from a
   `[dir=…]`-prefixed selector, so a two-class rule loses and the 13.2px
   sneaks back in — which is the whole cause of the fractional offset. */
[dir] .md-sidebar--secondary .md-nav .md-nav__list {
  padding-inline: 0;
}

/* font-size: 0 hides the theme's own label so ::after can carry ours, but it
   also collapses the line box to nothing — the pseudo-element then paints
   over the first entries. A block-level ::after gives the title real height
   again. 17px aligns its text with the entries (1px border + 16px indent). */
.md-sidebar--secondary .md-nav__title {
  font-size: 0;
  line-height: 0;
  padding: 0 0 0 17px;
  /* 22px of clearance under the header. At the shared 9px rail padding the
     label sat 9px below the header edge and read as part of the chrome. */
  margin: 22px 0 0.5rem;
}

.md-sidebar--secondary .md-nav__title::after {
  content: "On this page";
  display: block;
  font-size: 0.59rem;
  line-height: 1.5;
  font-weight: 400;
  color: var(--md-typeset-color);
}

/* Every entry carries its own 1px left border, so the column of them forms a
   continuous track and the entry being read darkens its own segment of it.
   That is how the source builds it — there is deliberately no separate rail
   rule on this side, which is why .md-main draws only a left-hand one.

   Measured from the source: 14px text, 16px indent from the track, 31px rows,
   and no weight change on the active entry — value alone marks it. */
.md-sidebar--secondary .md-nav__link {
  font-size: 0.64rem;
  line-height: 1.45;
  padding: 0.24rem 0 0.24rem 16px;
  border-left: 1px solid var(--enigma-border);
  color: var(--md-default-fg-color--light);
  /* The theme rounds every nav link by 4px. On a link whose left border IS
     the track, that curls both ends inward and the active segment reads as a
     bracket instead of a straight rule. */
  border-radius: 0;
}

.md-sidebar--secondary .md-nav__link--active {
  border-left-color: var(--md-typeset-color);
  color: var(--md-default-fg-color);
  font-weight: 400;
  box-shadow: none;
}

/* Screenshots read as inset panels rather than bare bitmaps. */
.md-typeset p > img {
  display: block;
  box-sizing: border-box;
  padding: 1.1rem;
  background: var(--enigma-surface);
}

/* ------------------------------------------- controls filled with accent -- */
/* The theme paints several controls WITH the accent on hover. That reads as a
   deliberate colour pop while the accent is a hue — but ours is near-white on
   dark and near-black on light, so each one flashed as a solid slab. There are
   14 such selectors in the theme; these are the ones a reader actually meets.
   Give them the same quiet raised surface used elsewhere.

   --md-accent-bg-color is set per scheme as well, so anything not caught here
   at least keeps legible text instead of white-on-white. */
.md-top:hover,
.md-top:focus,
.md-typeset .md-button:hover,
.md-typeset .md-button:focus,
.md-typeset .md-tag[href]:hover,
.md-typeset .md-tag[href]:focus {
  background-color: var(--enigma-nav-active-bg);
  color: var(--md-default-fg-color);
  border-color: var(--enigma-border);
}

.md-sidebar__scrollwrap:hover::-webkit-scrollbar-thumb:hover,
.md-sidebar__scrollwrap:focus-within::-webkit-scrollbar-thumb:hover,
.md-search__scrollwrap::-webkit-scrollbar-thumb:hover,
.md-typeset pre > code::-webkit-scrollbar-thumb:hover {
  background-color: var(--md-default-fg-color--lighter);
}

/* ------------------------------------------------------------------ print */
@media print {
  .md-header,
  .md-sidebar,
  .enigma-pagenav,
  .md-content__button {
    display: none !important;
  }

  .md-main__inner,
  .md-content {
    margin: 0 !important;
  }

  .md-typeset a {
    color: inherit;
    text-decoration: none;
  }

  .enigma-attribution {
    page-break-inside: avoid;
  }
}

/* ------------------------------------------------- previous / next links -- */
/* At the end of the article, not in a footer — there is no footer. Measured
   off the source: a two-up row 40px below the content, 20px gap, each link a
   32px pill with 10px padding, 4px radius, 14px at weight 500, filled with the
   surface grey and darkening on hover. No top border.

   Material's own version lives in the footer as full-width bars stacking a
   "Previous"/"Next" label above the title. The source shows the title alone
   and lets the arrow carry the direction, so the label is not rendered. */
.md-typeset .enigma-pagenav {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  margin-top: 2rem;
}

.md-typeset .enigma-pagenav__link {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  /* 48%, so two long titles truncate rather than meet in the middle. */
  max-width: 48%;
  min-width: 0;
  height: 1.6rem;
  padding: 0 0.5rem;
  border-radius: 4px;
  background: var(--enigma-surface);
  color: var(--md-default-fg-color);
  /* 0.7rem = 14px against the 20px root, matching the source. */
  font-size: 0.7rem;
  font-weight: 500;
  text-decoration: none;
  transition: background-color 120ms;
}

.md-typeset .enigma-pagenav__link:hover,
.md-typeset .enigma-pagenav__link:focus-visible {
  background: var(--enigma-nav-active-bg);
  color: var(--md-default-fg-color);
}

/* The title is the only part allowed to shrink; the arrow keeps its size. */
.enigma-pagenav__title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.enigma-pagenav__arrow {
  width: 0.7rem;
  height: 0.7rem;
  flex-shrink: 0;
  color: var(--md-default-fg-color--light);
}

/* ------------------------------------------- attribution in the TOC rail -- */
/* The licence link, moved out of the deleted footer. Sits under the contents
   list, aligned to the same 17px text column the entries use so it reads as
   part of the rail rather than something floating beside it. */
/* Material includes partials/toc.html twice — once in the right-hand rail and
   once inline under the active page in the left nav, for the mobile drawer.
   Both are visible at desktop width, so the link rendered twice, at x=45 and
   x=1140. It belongs to the right-hand column only. */
.md-sidebar--primary .enigma-toc-meta {
  display: none;
}

.enigma-toc-meta {
  margin-top: 1rem;
  padding: 0 0 0 17px;
}

/* Only when a contents list sits above it — see overrides/partials/toc.html. */
.enigma-toc-meta--ruled {
  padding-top: 0.7rem;
  border-top: 1px solid var(--enigma-border);
}

/* Anchor it to the foot of the rail rather than letting it trail the contents
   list. On a short list it was landing mid-column — measured 97px above the
   bottom on /stock/item/ — which reads as a stray item rather than the rail's
   footer.

   The scrollwrap needs a definite height for this: it sizes to its content by
   default, so `min-height: 100%` on the inner would resolve against a height
   that depends on the inner, and collapse. Give the wrap the column's height,
   let the inner fill it as a flex column, and `margin-top: auto` takes the
   remaining space. When the list IS longer than the column there is no free
   space to take, so the link simply follows it and scrolls — which is what
   should happen. */
@media screen and (min-width: 76.25em) {
  .md-sidebar--secondary .md-sidebar__scrollwrap {
    height: 100%;
  }

  .md-sidebar--secondary .md-sidebar__inner {
    display: flex;
    flex-direction: column;
    min-height: 100%;
  }

  .md-sidebar--secondary .enigma-toc-meta {
    margin-top: auto;
  }
}

.enigma-toc-meta a {
  font-size: 0.59rem;
  font-weight: 400;
  color: var(--md-default-fg-color--light);
  text-decoration: none;
  transition: color 120ms;
}

.enigma-toc-meta a:hover,
.enigma-toc-meta a:focus-visible {
  color: var(--md-default-fg-color);
}
