/* =============================================================
   PUBLIC LANDING PAGES

   Two shells share one vocabulary:

     .xl-doc   documentation layout, sticky tool nav + article.
               Used by /tool/<slug>. Reads like a product page you
               can trust: version, last updated, changelog.
     .xl-page  marketing layout, hero + centred sections.
               Used by /institucional.

   Shared primitives (.xl-section, .xl-cards, .xl-steps, .xl-faq,
   .xl-final) are defined once and used by both, so the two pages
   cannot drift apart visually. Buttons are NOT among them: both
   pages use the design system's .btn (see the CTA note below).

   Served by a per-page <link>, not bundled into dist/main.min.css
   (checked against main.css's @import list), so no css:build is
   required for changes here. Every selector is prefixed `xl-`
   anyway: the file is one @import away from becoming sitewide.

   Colour comes only from semantic tokens, derived locally with
   color-mix, so both themes are correct without a single literal.
   ============================================================= */

/* Sticky only works when the viewport is the scroll container. base/layout.css
   sets `html, body { overflow-x: hidden }` sitewide, and `hidden` on one axis
   computes the other to `auto`: both elements become scroll boxes, and every
   sticky inside them anchors to a box that never scrolls. That is why this
   page's nav and the folio numerals scrolled away for as long as they carried
   `position: sticky`. `clip` blocks the same horizontal scroll without
   creating a scroll box. Scoped by linkage: only the landing pages load this
   file, so the sitewide rule is untouched. */
html,
body {
  overflow-x: clip;
}

.xl-doc,
.xl-page {
  --xl-accent: var(--action-primary);
  --xl-accent-soft: color-mix(in srgb, var(--xl-accent) 12%, transparent);
  --xl-surface: var(--background-surface);
  --xl-rule: var(--border-subtle);
  --xl-rule-strong: color-mix(in srgb, var(--text-secondary) 28%, transparent);

  /* Mild overshoot (an under-damped spring, roughly damping 0.8) for the nav
     icon micro-motion only. The house --ease-out never overshoots; buttons and
     surfaces keep using it. Overshoot is reserved for the one place where a
     tiny bounce reads as personality rather than noise. */
  --xl-ease-spring: cubic-bezier(0.34, 1.4, 0.64, 1);

  /* One source of truth for the grid. The empty mirror track has to match the
     sidebar exactly, or the article column stops being centred on the page,
     which is the whole reason the track exists. */
  --xl-nav-width: 236px;
  --xl-shell-gap: clamp(1.5rem, 4vw, 3.5rem);

  color: var(--text-primary);
  background: var(--background-page);
}

/* =============================================================
   SHARED PRIMITIVES
   ============================================================= */

.xl-section {
  padding-block: clamp(2rem, 4vw, 3rem);
  border-block-start: 1px solid var(--xl-rule);
  scroll-margin-block-start: 5rem;
}

.xl-section__title {
  font-size: clamp(1.25rem, 2.4vw, 1.625rem);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--text-primary);
  margin-block: 0 1.25rem;
}

.xl-section__note {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-secondary);
  margin-block: -0.5rem 1.5rem;
  max-width: 62ch;
}

/* ---------- Justified body copy ----------

   Justification only works with hyphenation. Without it, Portuguese words are
   long enough that the browser opens visible rivers of whitespace between them.
   `hyphens: auto` needs the language declared, which base.html does on <html>.

   Applied to running prose only. Card and step text sit in ~260px columns where
   justification would produce gaps wider than the word spacing, and the lede is
   centred, which cannot be justified at the same time. */
.xl-prose,
.xl-section__note,
.xl-method__text,
.xl-method__list li,
.xl-faq__answer,
.xl-log__items li {
  text-align: justify;
  hyphens: auto;
}

.xl-prose {
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--text-secondary);
  max-width: 68ch;
  margin: 0;
}

/* ---------- CTA ---------- */

/* There is no bespoke button here on purpose.

   This file used to define .xl-cta, pairing --action-primary with
   --action-primary-text and then swapping only the background to
   --action-primary-hover on hover. That pair is broken: the system has no
   --action-primary-text-hover, so the label stayed put while the surface moved
   underneath it. In dark theme that put navy text on cyan-dark; in light theme,
   white text on a lighter cyan. Both failed contrast on hover only, which is
   why it looked fine in every screenshot taken at rest.

   The canonical .btn (components/buttons.css, bundled sitewide) already solves
   it by keeping the label at --neutral-black across both states and moving the
   surface cyan to green, which is the product's own hover decision. Pages use
   `class="btn btn-primary"` and `class="btn btn-secondary"`. Do not reintroduce
   a local button: this page has no styling need the design system lacks. */

/* ---------- Steps ---------- */

.xl-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1rem;
}

.xl-step {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 1rem;
  align-items: start;
  padding: 1.25rem;
  border: 1px solid var(--xl-rule);
  border-radius: var(--radius-md);
  background: var(--xl-surface);
}

.xl-step__number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background: var(--xl-accent-soft);
  color: var(--xl-accent);
  font-weight: 700;
  font-size: 0.875rem;
  font-variant-numeric: tabular-nums;
}

.xl-step__title {
  font-size: 1rem;
  line-height: 1.3;
  color: var(--text-primary);
  margin-block: 0 0.375rem;
}

.xl-step__text {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-secondary);
  margin: 0;
  max-width: 62ch;
}

/* ---------- Cards ---------- */

.xl-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.xl-card {
  padding: 1.25rem;
  border: 1px solid var(--xl-rule);
  border-radius: var(--radius-md);
  background: var(--xl-surface);
}

.xl-card__icon {
  display: block;
  font-size: 1.125rem;
  color: var(--xl-accent);
  margin-block-end: 0.75rem;
}

.xl-card__title {
  font-size: 1rem;
  line-height: 1.3;
  color: var(--text-primary);
  margin-block: 0 0.375rem;
}

.xl-card__text {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-secondary);
  margin: 0;
}

/* ---------- FAQ ---------- */

.xl-faq {
  border-block-end: 1px solid var(--xl-rule);
}

.xl-faq:first-of-type {
  border-block-start: 1px solid var(--xl-rule);
}

.xl-faq__question {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding-block: 1rem;
  font-size: 1rem;
  font-weight: var(--font-weight-semibold);
  line-height: 1.45;
  color: var(--text-primary);
  cursor: pointer;
  list-style: none;
}

.xl-faq__question::-webkit-details-marker {
  display: none;
}

/* Plus/minus from a pseudo-element, not an icon font: it cannot go missing and
   it cannot be mistaken for an arrow. */
.xl-faq__question::after {
  content: "+";
  margin-inline-start: auto;
  font-weight: 400;
  color: var(--xl-accent);
}

.xl-faq[open] .xl-faq__question::after {
  content: "\002212";
}

.xl-faq__question:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

.xl-faq__answer {
  padding-block-end: 1.125rem;
  font-size: 0.9375rem;
  line-height: 1.7;
  color: var(--text-secondary);
  max-width: 68ch;
  margin: 0;
}

/* ---------- Final CTA ---------- */

.xl-final {
  padding-block: clamp(2.5rem, 5vw, 3.5rem);
  border-block-start: 1px solid var(--xl-rule);
  text-align: center;
}

.xl-final__title {
  font-size: clamp(1.25rem, 2.6vw, 1.75rem);
  line-height: 1.25;
  color: var(--text-primary);
  margin-block: 0 0.625rem;
}

.xl-final__text {
  font-size: 1rem;
  line-height: 1.6;
  color: var(--text-secondary);
  max-width: 52ch;
  margin: 0 auto 1.5rem;
}

/* =============================================================
   DOC SHELL (/tool/<slug>)
   ============================================================= */

/* Sidebar and article, and nothing else. There used to be a third empty track
   mirroring the sidebar, so the article landed on the centre of the PAGE: with
   a centred hero that mattered, because its optical axis otherwise fell right
   of the viewport centre and fought the site navbar, which is centred on the
   viewport.

   Nothing in this shell is centred any more. The page reads as documentation:
   everything is flush left and the body runs to the right gutter, so a mirror
   would only be 236px of dead space between the last word and the edge. The
   axis it protected no longer exists. */
.xl-doc__shell {
  display: grid;
  grid-template-columns: var(--xl-nav-width) minmax(0, 1fr);
  gap: var(--xl-shell-gap);
  width: 100%;
  max-width: 1180px;
  margin-inline: auto;
  padding-block: clamp(1.5rem, 3vw, 2.5rem) clamp(2rem, 4vw, 3rem);
  padding-inline: clamp(1.5rem, 2.5vw, 3rem);
  align-items: start;
}

/* The cap is deliberately far above any laptop: the shell is meant to run to
   the edges of a wide monitor and leave only the padding as a gutter, instead
   of the two empty bands a 1360px cap left on a 1080p screen and up. 2100px is
   where it stops, so an ultrawide does not stretch one paragraph across a
   metre of glass. */
@media (min-width: 1240px) {
  .xl-doc__shell {
    max-width: 2100px;
  }
}

/* Inside the doc shell the column IS the measure, so the ch caps come off and
   the body runs from the sidebar to the right gutter. They stay in place for
   /institucional, which shares these classes and is a centred marketing page
   where a bounded measure is still right.

   This is only legible because the same rules justify and hyphenate: at this
   width an unjustified ragged edge is what read as "badly distributed" in the
   first place. .xl-step__text joins the justified set here, since a step is no
   longer the ~260px card the exclusion above was written for. */
.xl-doc .xl-prose,
.xl-doc .xl-section__note,
.xl-doc .xl-step__text,
.xl-doc .xl-faq__answer,
.xl-doc .xl-log__items li {
  max-width: none;
}

.xl-doc .xl-step__text {
  text-align: justify;
  hyphens: auto;
}

/* ---------- Sidebar ---------- */

/* A <details> so the mobile disclosure is native. On desktop it is forced open
   and the toggle is hidden: no JS anywhere in this navigation, which is the
   point on the one page that has to work for a crawler.

   The sticky MUST sit on this element, never on .xl-nav__panel inside it.
   Chromium renders open <details> content inside a ::details-content box that
   carries containment, and a sticky child of it gets zero travel: it scrolls
   away exactly like static content, silently. The <details> itself is a grid
   item, so its containing block is the grid area, which spans the whole row,
   and there the offset below is real travel. 5rem clears the fixed topbar. */
.xl-nav {
  position: sticky;
  top: 5rem;
}

.xl-nav__toggle {
  display: none;
}

.xl-nav__panel {
  border: 1px solid var(--xl-rule);
  border-radius: var(--radius-md);
  background: var(--xl-surface);
  padding: 0.5rem;
  /* Never taller than what is left of the viewport under the 5rem offset, with
     1.5rem of air at the bottom. The box keeps its border still and the list
     scrolls inside it, so the footer link stays reachable at any height.
     dvh second: on the browsers that have it, it survives the mobile URL bar. */
  max-height: calc(100vh - 6.5rem);
  max-height: calc(100dvh - 6.5rem);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.xl-nav__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 1px;
}

.xl-nav__link {
  position: relative;
  display: flex;
  align-items: center;
  gap: 0.625rem;
  padding: 0.5rem 0.625rem;
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
  line-height: 1.35;
  color: var(--text-secondary);
  text-decoration: none;
  transition:
    background-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out),
    transform 100ms var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .xl-nav__link:hover {
    background: var(--xl-accent-soft);
    color: var(--text-primary);
  }

  /* The icon answers the hover with a small lift and a hint of overshoot,
     the way macOS sidebar symbols do. The row itself stays put: one element
     moving reads as alive, two reads as busy. */
  .xl-nav__link:hover .xl-nav__icon {
    transform: translateY(-1px) scale(1.12);
  }
}

/* Feedback on the press, not on the release: the row acknowledges the tap the
   instant it happens. Subtle (0.98), and it also gives touch users a response
   the hover rules deliberately never gave them. */
.xl-nav__link:active {
  transform: scale(0.98);
}

.xl-nav__link:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: -2px;
}

/* The current page is stated twice, by weight and by the rail, because colour
   alone is not an accessible way to say "you are here". */
.xl-nav__link.is-current {
  background: var(--xl-accent-soft);
  color: var(--text-primary);
  font-weight: var(--font-weight-semibold);
}

/* The rail is a pseudo-element rather than an inset box-shadow so it can be
   shorter than the row and can draw itself in on arrival: a single, quiet
   "you are here" gesture. It grows from the middle out, pointing at the label
   it marks, and runs exactly once per page load. */
.xl-nav__link.is-current::before {
  content: "";
  position: absolute;
  inset-block: 24%;
  inset-inline-start: 0;
  width: 0.1875rem;
  border-radius: 999px;
  background: var(--xl-accent);
  animation: xl-rail-in 320ms var(--ease-out) both;
}

@keyframes xl-rail-in {
  from {
    transform: scaleY(0.4);
    opacity: 0;
  }

  to {
    transform: scaleY(1);
    opacity: 1;
  }
}

.xl-nav__icon {
  width: 1rem;
  flex-shrink: 0;
  text-align: center;
  color: var(--xl-accent);
  font-size: 0.8125rem;
  transition: transform 240ms var(--xl-ease-spring);
}

.xl-nav__name {
  min-width: 0;
}

.xl-nav__footer {
  border-block-start: 1px solid var(--xl-rule);
  margin-block-start: 0.5rem;
  padding: 0.75rem 0.625rem 0.375rem;
}

.xl-nav__footer-link {
  font-size: 0.875rem;
  font-weight: var(--font-weight-semibold);
  color: var(--xl-accent);
  text-decoration: none;
}

.xl-nav__footer-link:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

/* ---------- Article header ---------- */

/* Centered on purpose. Left-aligned, the title and the nav icons formed two
   competing left rails of equal weight and the page read as split. Centering
   the hero gives the article column its own axis; the running text below
   stays left-aligned, because centered multi-line paragraphs are harder to
   read and this audience reads. */
.xl-doc__header {
  padding-block-end: clamp(1.5rem, 3vw, 2rem);
}

.xl-doc__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8125rem;
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--xl-accent);
  margin-block-end: 0.75rem;
}

.xl-doc__title {
  font-size: clamp(1.75rem, 4vw, 2.5rem);
  line-height: 1.15;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  margin-block: 0 0.75rem;
  /* No 22ch cap. That number was tuned for a centred title, where the point
     was to force a short, evenly stacked block; flush left in a full-width
     column it only broke a headline that had room to breathe. The column is
     the bound now, and balance still evens out whatever does wrap, so a title
     never ends on a single orphan word. */
  text-wrap: balance;
}

.xl-doc__lede {
  font-size: clamp(1rem, 1.6vw, 1.125rem);
  line-height: 1.6;
  color: var(--text-secondary);
  margin-block: 0 1.5rem;
}

/* ---------- Version / dates ---------- */

.xl-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 1.75rem;
  margin-block: 0 1.75rem;
  padding-block: 1rem;
  border-block: 1px solid var(--xl-rule);
}

.xl-meta__item {
  display: grid;
  gap: 0.25rem;
}

.xl-meta dt {
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.xl-meta dd {
  margin: 0;
  font-size: 0.9375rem;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.xl-meta__version {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: var(--radius-sm);
  background: var(--xl-accent-soft);
  color: var(--xl-accent);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
}

.xl-doc__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-block-end: 0.875rem;
}

.xl-doc__microcopy {
  font-size: 0.875rem;
  color: var(--text-secondary);
  margin: 0;
}

/* ---------- Método ---------- */

/* Reads as documentation, not as a feature grid: generous line height, one
   idea per block, and the two blocks that carry the trust (where AI is used,
   and what the tool refuses to do) marked with a rail so they are findable
   when someone is scanning for exactly them. */
.xl-method {
  display: grid;
  gap: 1.25rem;
}

.xl-method__block {
  padding: 1.25rem;
  border: 1px solid var(--xl-rule);
  border-radius: var(--radius-md);
  background: var(--xl-surface);
}

.xl-method__block--ai {
  border-inline-start: 3px solid var(--xl-accent);
}

/* Not a warning tint. These are honest limits, not errors, and painting them
   amber would read as "something went wrong" instead of "here is the edge". */
.xl-method__block--limits {
  border-inline-start: 3px solid var(--xl-rule-strong);
}

.xl-method__title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.9375rem;
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.01em;
  color: var(--text-primary);
  margin-block: 0 0.75rem;
}

.xl-method__icon {
  color: var(--xl-accent);
  font-size: 0.875rem;
}

/* No measure on the text here: inside a drawn box, the box IS the measure.

   This used to cap the paragraph at 66ch. The card grows with the column, so
   above roughly 1300px the justified text ended 82px short of the border it
   sits in while hugging the other side at 21px, and the block read as pushed
   left. Bare prose can carry its own max-width because there is no edge nearby
   to compare it against; a paragraph inside a bordered card cannot.

   Letting it fill the card is safe because the card is already bounded: the
   article caps at 780px (.xl-doc__main), so the line lands around 74ch at its
   widest. That bound used to come from the shell's own max-width; the shell
   now runs to the edge of the screen, and the article carries it instead. */
.xl-method__text {
  font-size: 0.9375rem;
  line-height: 1.7;
  color: var(--text-secondary);
  margin: 0;
}

.xl-method__list {
  margin: 0;
  padding-inline-start: 1.125rem;
  display: grid;
  gap: 0.4375rem;
}

/* Same reason as .xl-method__text. Worse here, in fact: at 62ch the limits list
   stopped 100px short of its card. In the two-column blocks the item is ~294px
   wide, so the cap never bound there and nothing about them changes. */
.xl-method__list li {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-secondary);
}

.xl-method__columns {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 1.25rem;
}

/* ---------- Ficha técnica ---------- */

.xl-spec {
  display: flex;
  flex-wrap: wrap;
  gap: 1.75rem;
  margin: 0;
  padding: 1rem 1.25rem;
  border: 1px dashed var(--xl-rule);
  border-radius: var(--radius-md);
}

.xl-spec__item {
  display: grid;
  gap: 0.25rem;
}

.xl-spec dt {
  font-size: 0.75rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
}

.xl-spec dd {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

/* ---------- Jump link ---------- */

.xl-doc__jump {
  margin-inline-start: 0.75rem;
  color: var(--xl-accent);
  text-decoration: none;
  font-weight: var(--font-weight-semibold);
}

@media (hover: hover) and (pointer: fine) {
  .xl-doc__jump:hover {
    text-decoration: underline;
  }
}

.xl-doc__jump:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

/* ---------- Changelog: o resto ---------- */

/* The collapsed list keeps the rail flowing from the entry above it, so opening
   it reads as the timeline continuing rather than a second list appearing. */
.xl-log--earlier {
  align-self: stretch;
  margin-block-start: 2rem;
}

/* The control itself is the design system's .btn (btn-secondary btn-sm in
   the markup). Only three things are added here, and none of them touches
   colour, radius, focus or motion:

     1. where it sits: centred in the column;
     2. a size trim below btn-sm, which is a size modifier and nothing more;
     3. the +/- mark, which .btn has no concept of because no other button
        in the app carries open/closed state.

   A local pill lived here for one commit and was wrong: it was the third
   time a button already in the system got redrawn on this page. */
/* Flush left with the entries it expands, like everything else in this shell.
   It was centred back when the article was a narrow centred column. */
.xl-log__more {
  display: flex;
  flex-direction: column;
  align-items: start;
  margin-block-start: 0.25rem;
}

.xl-log__more-toggle {
  padding: 0.375rem 0.875rem;
  min-height: 32px;
  font-size: 0.8125rem;
  list-style: none;
}

.xl-log__more-toggle::-webkit-details-marker {
  display: none;
}

/* Fixed square, centred on both axes. Left to sit inline, '+' and the minus
   sign have different glyph heights and side bearings, so the mark drifted
   between the two states. line-height 1 keeps it off the button's leading. */
.xl-log__more-toggle::after {
  content: "+";
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1em;
  height: 1em;
  line-height: 1;
  font-weight: 400;
  opacity: 0.75;
}

/* U+2212 MINUS SIGN, all six digits.

   The four-digit form went through one escaping layer too many when this
   block was generated and reached the file as the control character U+0091
   followed by a literal "2", which rendered as a broken box and a stray
   digit: invisible in the editor, visible only in the browser. Six digits
   cannot be truncated by whatever follows. Guarded by a test. */
.xl-log__more[open] .xl-log__more-toggle::after {
  content: "\002212";
}

/* Same 4px settle the sidebar panel uses, for the same reason: <details> cannot
   animate its own height without JS, so the entrance belongs to the content. */
.xl-log__more[open] .xl-log--earlier {
  animation: xl-panel-in 200ms var(--ease-out) both;
}

/* ---------- Como citar ---------- */

.xl-cite {
  display: grid;
  gap: 1rem;
}

.xl-cite__item {
  padding: 1rem 1.25rem;
  border: 1px solid var(--xl-rule);
  border-radius: var(--radius-md);
  background: var(--xl-surface);
}

.xl-cite__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-block-end: 0.625rem;
}

.xl-cite__label {
  font-size: 0.75rem;
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-secondary);
  margin: 0;
}

.xl-cite__copy {
  flex-shrink: 0;
  padding: 0.3125rem 0.75rem;
  border: 1px solid var(--xl-rule);
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text-primary);
  font-size: 0.8125rem;
  font-weight: var(--font-weight-semibold);
  font-family: inherit;
  cursor: pointer;
  transition:
    border-color var(--duration-fast) var(--ease-out),
    color var(--duration-fast) var(--ease-out);
}

@media (hover: hover) and (pointer: fine) {
  .xl-cite__copy:hover {
    border-color: var(--xl-accent);
    color: var(--xl-accent);
  }
}

/* Confirmation in colour AND in the label (the script swaps the text), because
   colour alone does not confirm anything for a colour-blind reader. */
.xl-cite__copy.is-done {
  border-color: var(--feedback-success);
  color: var(--feedback-success);
}

/* No global button:focus-visible exists in this codebase, and this is a real
   <button>, so it would otherwise have only the UA ring. */
.xl-cite__copy:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

/* Never justified: a reference is copied verbatim, and stretched word spacing
   would suggest spaces that are not in the string. */
.xl-cite__text {
  margin: 0;
  font-size: 0.875rem;
  line-height: 1.65;
  color: var(--text-secondary);
  text-align: start;
  hyphens: none;
  /* A URL has no space to break on and would otherwise push the card wide. */
  overflow-wrap: anywhere;
}

.xl-cite__text--code {
  font-family: var(--font-family-mono, ui-monospace, monospace);
  font-size: 0.8125rem;
  line-height: 1.55;
  white-space: pre-wrap;
  overflow-x: auto;
}

.xl-cite__status {
  margin-block: 0.75rem 0;
  font-size: 0.875rem;
  color: var(--feedback-success);
  min-height: 1.25rem;
}

/* ---------- Changelog ---------- */

.xl-log {
  list-style: none;
  margin: 0;
  padding: 0;
}

.xl-log__entry {
  position: relative;
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 1rem;
  padding-block-end: 1.75rem;
}

/* The rail is drawn by the entry itself rather than a wrapper, so the last
   entry can stop it cleanly instead of trailing into empty space. */
.xl-log__entry::before {
  content: "";
  position: absolute;
  inset-block: 1.25rem 0;
  inset-inline-start: 0.3125rem;
  width: 1px;
  background: var(--xl-rule);
}

/* Driven by a modifier, not by :last-child. The log is split across two lists
   (the latest one, and the collapsed rest), so "last of its parent" stopped
   meaning "last of the log" and the rail would have died under the first
   entry while five more sat below it. */
.xl-log__entry--last {
  padding-block-end: 0;
}

.xl-log__entry--last::before {
  display: none;
}

.xl-log__marker {
  width: 0.6875rem;
  height: 0.6875rem;
  margin-block-start: 0.4375rem;
  border-radius: 50%;
  border: 2px solid var(--xl-rule-strong);
  background: var(--background-page);
}

.xl-log__marker--latest {
  border-color: var(--xl-accent);
  background: var(--xl-accent);
}

.xl-log__head {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  margin-block-end: 0.5rem;
}

.xl-log__date {
  font-size: 0.9375rem;
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}

.xl-log__badge {
  padding: 0.0625rem 0.4375rem;
  border-radius: var(--radius-sm);
  background: var(--xl-accent-soft);
  color: var(--xl-accent);
  font-size: 0.6875rem;
  font-weight: var(--font-weight-semibold);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.xl-log__items {
  margin: 0;
  padding-inline-start: 1.125rem;
  display: grid;
  gap: 0.375rem;
}

.xl-log__items li {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-secondary);
  max-width: 66ch;
}

/* =============================================================
   MARKETING SHELL (/institucional)
   ============================================================= */

.xl-shell {
  width: 100%;
  max-width: 1120px;
  margin-inline: auto;
  padding-inline: 1.5rem;
}

.xl-shell--narrow {
  max-width: 760px;
}

.xl-hero {
  padding-block: clamp(2.75rem, 5vw, 4.25rem);
}

.xl-breadcrumb {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.875rem;
  color: var(--text-secondary);
  margin-block-end: 1.25rem;
}

.xl-breadcrumb a {
  color: var(--xl-accent);
  text-decoration: none;
}

.xl-breadcrumb a:focus-visible {
  outline: 2px solid var(--border-focus);
  outline-offset: 2px;
}

@media (hover: hover) and (pointer: fine) {
  .xl-breadcrumb a:hover {
    text-decoration: underline;
  }
}

.xl-hero__title {
  font-size: clamp(1.9rem, 5vw, 3rem);
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  margin-block: 0 1rem;
  max-width: 20ch;
}

.xl-hero__subtitle {
  font-size: clamp(1.0625rem, 2vw, 1.25rem);
  line-height: 1.55;
  color: var(--text-secondary);
  max-width: 62ch;
  margin-block: 0 2rem;
}

.xl-hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-block-end: 1rem;
}

.xl-hero__microcopy {
  font-size: 0.875rem;
  color: var(--text-secondary);
  margin: 0;
}

/* The marketing page has no sidebar, so its sections carry the shell width
   themselves instead of inheriting the article column. */
.xl-page .xl-section,
.xl-page .xl-final {
  padding-inline: 0;
}

/* =============================================================
   RESPONSIVE
   ============================================================= */

@media (max-width: 900px) {
  .xl-doc__shell {
    grid-template-columns: minmax(0, 1fr);
    gap: 1.25rem;
  }

  /* Below the breakpoint the sidebar becomes a real disclosure: the summary
     shows the current tool so the control says what it does before it is
     opened. */
  .xl-nav__toggle {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.75rem 1rem;
    border: 1px solid var(--xl-rule);
    border-radius: var(--radius-md);
    background: var(--xl-surface);
    cursor: pointer;
    list-style: none;
  }

  .xl-nav__toggle::-webkit-details-marker {
    display: none;
  }

  .xl-nav__toggle::after {
    content: "+";
    margin-inline-start: auto;
    color: var(--xl-accent);
  }

  .xl-nav[open] .xl-nav__toggle::after {
    content: "\002212";
  }

  .xl-nav__toggle:focus-visible {
    outline: 2px solid var(--border-focus);
    outline-offset: 2px;
  }

  .xl-nav__toggle-label {
    font-size: 0.75rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-secondary);
  }

  .xl-nav__toggle-current {
    font-weight: var(--font-weight-semibold);
    color: var(--text-primary);
  }

  /* No pinning on a phone, and the disclosure gets its natural height back:
     a nested scroll area inside a page that already scrolls is a trap on
     touch, and there is no fixed sidebar to keep in view here anyway. */
  .xl-nav {
    position: static;
  }

  .xl-nav__panel {
    margin-block-start: 0.5rem;
    max-height: none;
    overflow: visible;
  }

  /* The panel settles into place when the disclosure opens: a short fade with
     a 4px drop, nothing more. Native <details> cannot animate height without
     JS, and this page carries none on purpose, so the reveal is the entrance
     of the content, not the growth of the box. */
  .xl-nav[open] .xl-nav__panel {
    animation: xl-panel-in 200ms var(--ease-out) both;
  }
}

@keyframes xl-panel-in {
  from {
    opacity: 0;
    transform: translateY(-4px);
  }

  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@media (max-width: 640px) {
  .xl-doc__title,
  .xl-hero__title {
    max-width: none;
  }

  .xl-meta {
    gap: 1.25rem;
  }

  /* Full-width CTAs on a phone. Scoped to these two rows so the rule cannot
     reach a .btn anywhere else, since buttons.css is sitewide. */
  .xl-doc__actions .btn,
  .xl-hero__actions .btn {
    width: 100%;
  }
}

/* =============================================================
   REDUCED MOTION

   The global guard in animations.css only caps duration and
   iteration count. It cannot remove a transform, so the press
   feedback is disabled here explicitly.
   ============================================================= */

@media (prefers-reduced-motion: reduce) {
  /* .btn brings its own reduced-motion guard from components/buttons.css, so
     only what this file adds is listed here. */
  .xl-nav__link,
  .xl-nav__icon {
    transition: none;
  }

  .xl-nav__link:active {
    transform: none;
  }

  .xl-nav__link:hover .xl-nav__icon {
    transform: none;
  }

  /* animation: none leaves the rail and the panel at their resting styles,
     so nothing disappears; only the movement does. */
  .xl-nav__link.is-current::before,
  .xl-nav[open] .xl-nav__panel {
    animation: none;
  }
}

/* =============================================================
   THE PREPRINT (/institucional)

   The page is typeset as the front matter of a methods paper:
   centered title block, the subtitle framed as an abstract
   between a thick and a thin rule, four numbered sections with
   ghost numerals in a margin column, and a correspondence block
   closing under a double rule, the way a ledger closes on its
   total line.

   The design argument is the sales argument: a coordinator who
   lives inside this genre of document recognizes it on sight,
   and that recognition does the work three identical card grids
   never did. Hierarchy comes from type scale, numerals and rule
   weight (2px to open a section, 1px inside it), so the screen
   stays flat and quiet.

   Everything here is scoped under .xl-page--paper or a --rail
   modifier: the /tool doc pages share this file and must not
   change. Numerals are aria-hidden decoration, hand-numbered in
   the template; inserting a section renumbers them by hand.
   ============================================================= */

.xl-shell--article {
  max-width: 880px;
}

/* ---------- Running head ---------- */

.xl-paper-head {
  font-size: 0.75rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: var(--font-weight-semibold);
  padding-block-end: 0.75rem;
  border-block-end: 1px solid var(--xl-rule);
  margin-block-end: 2.5rem;
}

/* ---------- Title block ---------- */

.xl-paper-hero {
  position: relative;
  padding-block: clamp(2.5rem, 5vw, 4rem) clamp(1.75rem, 3vw, 2.5rem);
  /* The network canvas is clipped to the hero: the reading column below it
     never has anything moving behind the text. */
  overflow: hidden;
}

/* The living network. All the restraint lives in the paint (alphas fade the
   graph out before the text column starts); here it only gets its place: the
   whole hero, behind the content, untouchable. */
.xl-net {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.xl-paper-hero__content {
  position: relative;
}

.xl-paper-hero__title {
  font-size: clamp(2rem, 5vw, 3.125rem);
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--text-primary);
  text-align: center;
  text-wrap: balance;
  max-width: 18ch;
  margin: 0 auto;
}

/* The abstract: thick-over-thin table rules say "abstract" to this audience
   without a single new word of copy. This frame is the page's one signature
   moment, and the rules draw themselves in on load. */
.xl-paper-abstract {
  max-width: 58ch;
  margin: 1.75rem auto 0;
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--text-secondary);
  text-align: justify;
  hyphens: auto;
}

.xl-paper-abstract::before {
  content: "";
  display: block;
  height: 2px;
  background: var(--xl-rule-strong);
  margin-block-end: 1.25rem;
}

.xl-paper-abstract::after {
  content: "";
  display: block;
  height: 1px;
  background: var(--xl-rule-strong);
  margin-block-start: 1.25rem;
}

.xl-paper-hero__actions {
  justify-content: center;
  margin-block-start: 1.75rem;
}

.xl-paper-hero__microcopy {
  text-align: center;
}

/* Load sequence: title, then abstract and its rules, then the actions. The
   .reveal utility is banned above the fold, so this is plain CSS animation
   with `both` fill: the resting styles are the final state, and nothing can
   be left invisible if the animation never runs. */
@keyframes xl-fade-up {
  from {
    opacity: 0;
    transform: translateY(10px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes xl-rule-draw {
  from {
    transform: scaleX(0);
  }

  to {
    transform: scaleX(1);
  }
}

/* The title enters through a mask: the classic editorial line reveal. The
   resting state of every element here is the FINAL state; keyframes only
   declare `from`, so no JS, a failed animation, or reduced motion can ever
   leave something invisible. */
.xl-mask {
  display: block;
  overflow: hidden;
}

.xl-mask__line {
  display: block;
  animation: xl-mask-up 700ms var(--ease-out) 80ms both;
}

@keyframes xl-mask-up {
  from {
    transform: translateY(110%);
  }
}

.xl-paper-head {
  animation: xl-fade-up 400ms var(--ease-out) both;
}

.xl-paper-abstract {
  animation: xl-fade-up 450ms var(--ease-out) 250ms both;
}

.xl-paper-abstract::before,
.xl-paper-abstract::after {
  transform-origin: center;
  animation: xl-rule-draw 560ms var(--ease-out) 250ms both;
}

.xl-paper-hero__actions,
.xl-paper-hero__microcopy {
  animation: xl-fade-up 450ms var(--ease-out) 430ms both;
}

/* ---------- Numbered sections ---------- */

.xl-page--paper .xl-sec {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr);
  column-gap: clamp(1.25rem, 3vw, 2rem);
  border-block-start: 2px solid var(--xl-rule-strong);
}

/* A ghost folio numeral, printed matter rather than UI: strong enough to
   structure the page, quiet enough to never compete with the heading. It is
   decoration, so contrast rules do not apply to it. */
.xl-sec__no {
  font-size: clamp(2.25rem, 5vw, 3rem);
  font-weight: 700;
  line-height: 0.9;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: color-mix(in srgb, var(--text-secondary) 38%, transparent);
}

.xl-sec__body {
  min-width: 0;
}

.xl-page--paper .xl-section__title {
  margin-block: 0.35rem 1.75rem;
}

/* ---------- 1. The audiences ledger ---------- */

.xl-ledger {
  list-style: none;
  margin: 0;
  padding: 0;
  border-block-end: 1px solid var(--xl-rule);
}

.xl-ledger__row {
  display: grid;
  grid-template-columns: 3.25rem minmax(11rem, 14rem) minmax(0, 1fr);
  column-gap: 1.5rem;
  align-items: start;
  padding-block: 1.25rem;
  border-block-start: 1px solid var(--xl-rule);
}

.xl-ledger__no {
  font-size: 0.9375rem;
  font-weight: var(--font-weight-semibold);
  color: var(--xl-accent);
  font-variant-numeric: tabular-nums;
  line-height: 1.3;
}

.xl-ledger__title {
  font-size: 1rem;
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  line-height: 1.3;
  margin: 0;
}

.xl-ledger__text {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--text-secondary);
  margin: 0;
  max-width: 52ch;
  text-align: justify;
  hyphens: auto;
}

/* ---------- 2. The findings ---------- */

.xl-findings {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(270px, 1fr));
  gap: 2rem 2.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}

.xl-finding__no {
  display: block;
  font-size: 0.9375rem;
  font-weight: var(--font-weight-semibold);
  color: var(--xl-accent);
  font-variant-numeric: tabular-nums;
  margin-block-end: 0.375rem;
}

.xl-finding__title {
  font-size: 1.0625rem;
  font-weight: var(--font-weight-semibold);
  color: var(--text-primary);
  line-height: 1.3;
  margin: 0 0 0.5rem;
}

.xl-finding__text {
  font-size: 0.9375rem;
  line-height: 1.65;
  color: var(--text-secondary);
  margin: 0;
}

/* ---------- 3. The steps rail ---------- */

/* A 1px thread connects the three steps: a small protocol nested inside the
   page-protocol, in the one section that talks about how adoption runs. The
   overrides live under the modifier ONLY: .xl-step is shared with the /tool
   doc shell and must keep its boxed look there. */
.xl-steps--rail {
  gap: 0;
}

.xl-steps--rail .xl-step {
  position: relative;
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0 0 1.75rem;
}

.xl-steps--rail .xl-step:last-child {
  padding-block-end: 0;
}

/* Starts below the 1.75rem number chip, so the translucent chip background
   never has a line showing through it. */
.xl-steps--rail .xl-step::before {
  content: "";
  position: absolute;
  inset-block: 2rem 0;
  inset-inline-start: calc(0.875rem - 0.5px);
  width: 1px;
  background: var(--xl-rule-strong);
}

.xl-steps--rail .xl-step:last-child::before {
  display: none;
}

/* ---------- Correspondence block ---------- */

/* One stroke, not two.

   This closed on a `4px double` rule: the ledger convention of a double line
   above the total. It read as a duplicated border rather than as a device,
   and a device that reads as a defect is not working, so it is a single 2px
   stroke now, matching the weight that opens every numbered section.

   Still a pseudo-element rather than the section's own border, because it
   draws itself in as the reader arrives (scroll-driven block below). Without
   support it is simply there, complete.

   The block is centred: it is the one place on the page that stops being a
   document and becomes an invitation, and the axis break is what marks that. */
.xl-final--paper {
  text-align: center;
  border-block-start: 0;
  padding-block-start: 0;
}

.xl-final--paper::before {
  content: "";
  display: block;
  border-block-start: 2px solid var(--xl-rule-strong);
  margin-block-end: clamp(2rem, 4vw, 3rem);
  transform-origin: center;
}

.xl-final__text--paper {
  margin-inline: auto;
}

/* ---------- Paper, narrow screens ---------- */

@media (max-width: 720px) {
  .xl-page--paper .xl-sec {
    grid-template-columns: minmax(0, 1fr);
  }

  .xl-sec__no {
    font-size: 1.5rem;
    line-height: 1;
    margin-block-end: 0.25rem;
  }

  .xl-ledger__row {
    grid-template-columns: 3.25rem minmax(0, 1fr);
  }

  .xl-ledger__text {
    grid-column: 2;
    margin-block-start: 0.375rem;
    text-align: start;
  }
}

@media (max-width: 480px) {
  /* Justified text opens rivers at this measure even with hyphenation. */
  .xl-paper-abstract {
    text-align: start;
  }
}

/* ---------- Paper in motion: the page composes itself ----------

   Scroll-driven, native CSS (animation-timeline: view()), shipped as pure
   progressive enhancement inside @supports: Chrome, Edge, Firefox and Safari
   current all run it; anything older simply shows the finished page, because
   every keyframe declares only `from` and the resting styles are the final
   state.

   Three gestures, all tied to reading position rather than to a timer:
   the ghost folio numeral of each section prints itself as the section
   scrolls in; the 1px rail between the adoption steps draws downward as the
   reader descends it; and the closing double rule traces in from the left as
   the total line arrives. Motion communicates exactly one idea: the document
   is being typeset as you read it. */

/* The folio follows the reader: on tall sections the numeral stays in the
   margin while the section's content scrolls, the way a folio sits fixed on
   a printed page. Sticky costs nothing and works everywhere. */
@media (min-width: 721px) {
  .xl-sec__no {
    position: sticky;
    top: 5.5rem;
  }
}

@supports (animation-timeline: view()) {
  .xl-page--paper .xl-sec {
    view-timeline: --xl-sec-in block;
  }

  .xl-sec__no {
    animation: xl-folio-in linear both;
    animation-timeline: --xl-sec-in;
    animation-range: entry 0% entry 60%;
  }

  /* The rail draws downward as the reader moves down the steps. */
  .xl-steps--rail .xl-step {
    view-timeline: --xl-step-in block;
  }

  .xl-steps--rail .xl-step::before {
    transform-origin: top;
    animation: xl-rail-grow linear both;
    animation-timeline: --xl-step-in;
    animation-range: entry 20% cover 60%;
  }

  .xl-final--paper {
    view-timeline: --xl-final-in block;
  }

  .xl-final--paper::before {
    animation: xl-rule-draw-x linear both;
    animation-timeline: --xl-final-in;
    animation-range: entry 0% entry 70%;
  }
}

@keyframes xl-folio-in {
  from {
    opacity: 0;
    transform: translateY(1.5rem);
  }
}

@keyframes xl-rail-grow {
  from {
    transform: scaleY(0);
  }
}

@keyframes xl-rule-draw-x {
  from {
    transform: scaleX(0);
  }
}

/* ---------- Paper, reduced motion ---------- */

/* Resting styles are final styles throughout, so removing the animation
   removes only the movement: nothing disappears, nothing stays hidden. */
@media (prefers-reduced-motion: reduce) {
  .xl-mask__line,
  .xl-paper-head,
  .xl-paper-abstract,
  .xl-paper-abstract::before,
  .xl-paper-abstract::after,
  .xl-paper-hero__actions,
  .xl-paper-hero__microcopy,
  .xl-sec__no,
  .xl-steps--rail .xl-step::before,
  .xl-final--paper::before {
    animation: none;
  }
}
