/* ========================================
   Components (every page but Desktop, and the style prototype)
   The parts more than one section uses, the menu and the footer, in the redesign's look. Loaded after site.css,
   then the page's own stylesheet. A component moves here from the style prototype's stylesheet or a page's once a second section
   uses it. Component rules use site.css's role tokens only and define none (node tools/check-tokens.mjs checks both).
   Desktop never loads this file: its Start menu shares the names .menu-button and .menu-link.
   Names follow big-bang-visibility-website's CUBE naming without its underscores: a block (.btn), a variant with a
   double hyphen (.btn--primary), a part with a single hyphen (.field-label), a state with is- (.is-hover, for the
   prototype's still examples; a live component gets its states from the browser). An FR number in a comment
   below is the Style Prototype PRD's (PRD 02, in knowledge/workflow/plans/completed/site-redesign/), which
   approved the component; each moved here unchanged from the prototype on 2026-10-19 (Shared Layer PRD, FR 10).
   ======================================== */

/* Buttons, and links styled as buttons. They have no transitions: a press is instant, so nothing animates
   (FR 27). Every variant sets its own fill. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  min-width: 4.4rem;
  min-height: 4.4rem;
  padding: var(--space-xs) var(--space-md);
  color: var(--text); /* a classed link declares its color (big-bang's css-conventions.md, Classed Links Must Declare Color) */
  font-family: var(--font-reading);
  font-size: var(--type-body);
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  border: var(--border-thick) solid var(--border);
  border-radius: var(--radius-none);
  box-shadow: var(--shadow-md);
  cursor: pointer;
}

/* A hidden button stays hidden: .btn's display outranks the hidden attribute, which a script sets on Wonderland's
   search and Load More buttons and on a post page's Share until it runs (Carlo, 2026-10-21; Pages task list 4.14) */
.btn[hidden] {
  display: none;
}

.btn--primary,
.btn--play {
  background-color: var(--surface-button);
}

.btn--primary:hover,
.btn--primary.is-hover,
.btn--play:hover,
.btn--play.is-hover {
  background-color: var(--surface-button-hover);
}

.btn--secondary,
.btn--icon {
  background-color: var(--surface-button-secondary);
}

.btn--secondary:hover,
.btn--secondary.is-hover,
.btn--icon:hover,
.btn--icon.is-hover {
  background-color: var(--surface-button-secondary-hover);
}

/* Pressed: the button moves into its shadow */
.btn:active,
.btn.is-pressed {
  box-shadow: none;
  transform: translate(var(--shadow-offset-md), var(--shadow-offset-md));
}

.btn--icon {
  width: 4.8rem;
  height: 4.8rem;
  padding: 0;
}

.btn--play {
  width: 8.8rem;
  height: 8.8rem;
  padding: 0;
  border-radius: var(--radius-circle);
}

.btn--play .icon {
  width: 3.6rem;
  height: 3.6rem;
  margin-left: var(--space-2xs); /* the triangle's weight sits left of its box: this centers it by eye */
}

/* The ring keeps 3px clear of the button's hard shadow: at the base's 3px out, its first pixel fell on the 4px
   shadow, ink on ink, and the two fused on the right and bottom (Carlo, 2026-10-21, Pages task list 3.13, from the
   board https://claude.ai/artifact/2wvmT9TSMKyRbGj3r4sfQM). Before the menu's rules, so the Menu button, a .btn
   that draws its ring inside its border, keeps its own. */
.btn:focus-visible,
.btn.is-focus {
  outline-offset: calc(var(--shadow-offset-md) + var(--border-thick));
}

/* Disabled: no shadow and a dashed border, so it differs by shape as well as color. Last, so it wins over
   hover and pressed. */
.btn:disabled,
.btn.is-disabled {
  color: var(--text-muted);
  background-color: var(--surface-card);
  border-style: dashed;
  box-shadow: none;
  transform: none;
  cursor: default;
}

/* The NEW sticker: a starburst cut with clip-path, an ink layer under a coral one. Decoration: it is
   aria-hidden, and the card says "New" in hidden text. */
.sticker {
  position: relative;
  display: inline-grid;
  place-items: center;
  width: 6.4rem;
  height: 6.4rem;
  color: var(--text);
  font-family: var(--font-display);
  font-size: var(--type-label);
  line-height: 1;
  text-transform: uppercase;
}

.sticker::before,
.sticker::after {
  content: '';
  position: absolute;
  inset: 0;
  clip-path: polygon(100% 50%, 83.3% 63.8%, 85.4% 85.4%, 63.8% 83.3%, 50% 100%, 36.2% 83.3%, 14.6% 85.4%, 16.7% 63.8%, 0% 50%, 16.7% 36.2%, 14.6% 14.6%, 36.2% 16.7%, 50% 0%, 63.8% 16.7%, 85.4% 14.6%, 83.3% 36.2%);
}

.sticker::before {
  background-color: var(--border);
}

.sticker::after {
  inset: var(--border-thick);
  background-color: var(--surface-sticker);
}

.sticker-text {
  position: relative;
  z-index: var(--z-content);
  transform: rotate(-10deg);
}

/* Label plates: display type on a tilted plate with a hard shadow, for a heading over color, a subtitle and
   the Featured badge. The plate's 12px of side padding is part of the 320px heading test's measure
   (--type-h1): wider padding would push the widest word out at 320px. */
.plate {
  display: inline-block;
  max-width: 100%;
  padding: var(--space-2xs) var(--space-sm);
  color: var(--text);
  background-color: var(--surface-plate);
  border: var(--border-thick) solid var(--border);
  box-shadow: var(--shadow-lg);
  transform: rotate(-2deg);
}

.plate--accent {
  background-color: var(--surface-plate-accent);
  transform: rotate(1.2deg);
}

.plate--small {
  padding: 0 var(--space-sm);
  font-family: var(--font-display);
  font-size: var(--type-plate);
  line-height: 1.08;
  box-shadow: var(--shadow-md);
  transform: rotate(-4deg);
}

/* Fields, from the contact form. The error state is the field's own aria-invalid, so a still shows it the
   same way a live field does. Its one column never grows past the field: sized by its widest part, a select's
   longest option ("Published (newest first)" on Wonderland) widened it past the field at 320px, its ring over the
   window's border (Pages task list 4.7). */
.field {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  align-content: start;
  gap: var(--space-xs);
  min-width: 0;
}

.field-label {
  font-weight: 700;
}

.field-required {
  margin-left: var(--space-2xs);
  color: var(--error);
}

.field-input {
  width: 100%;
  min-width: 0;
  min-height: 4.8rem;
  padding: var(--space-xs) var(--space-sm);
  color: var(--text);
  font-family: var(--font-reading);
  font-size: var(--type-body);
  line-height: 1.4;
  background-color: var(--surface-field);
  border: var(--border-thick) solid var(--border);
  border-radius: var(--radius-none);
  box-shadow: inset var(--shadow-offset-sm) var(--shadow-offset-sm) 0 var(--shadow-faint);
}

textarea.field-input {
  min-height: 13.2rem;
  resize: vertical;
}

.field-input::placeholder {
  color: var(--text-muted);
  opacity: 1;
}

.field-input:focus,
.field-input.is-focus {
  background-color: var(--surface-field-focus);
}

.field-input[aria-invalid="true"] {
  border-left-width: calc(var(--border-thick) * 3);
  border-left-color: var(--error);
}

.field-help {
  color: var(--text-muted);
  font-size: var(--type-small);
}

.field-error {
  justify-self: start;
  padding: var(--space-2xs) var(--space-sm);
  font-size: var(--type-small);
  font-weight: 700;
  background-color: var(--surface-error);
  border: var(--border-thin) solid var(--border);
}

.field-error::before {
  content: '! ' / '';
}

/* The select: the browser's own control, restyled; its arrow is drawn on the wrapper, which the control can't
   hold */
.select {
  position: relative;
  justify-self: start;
  max-width: 100%;
}

.select::after {
  content: '';
  position: absolute;
  top: 50%;
  right: var(--space-sm);
  width: 1.2rem;
  height: 0.8rem;
  background-color: var(--text);
  clip-path: polygon(0 0, 100% 0, 50% 100%);
  transform: translateY(-50%);
  pointer-events: none;
}

/* A set line height, as the buttons have: at normal, Chromium sized the select by whichever font was showing when it
   was first laid out, and kept that height, so it measured 44px or 46px from one load to the next (5.9) */
.select-input {
  max-width: 100%;
  min-height: 4.4rem;
  padding: var(--space-xs) var(--space-2xl) var(--space-xs) var(--space-sm);
  color: var(--text);
  font-family: var(--font-reading);
  font-size: var(--type-body);
  font-weight: 700;
  line-height: 1.2;
  background-color: var(--surface-field);
  border: var(--border-thick) solid var(--border);
  border-radius: var(--radius-none);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  appearance: none;
}

.select-input:hover,
.select-input.is-hover {
  background-color: var(--surface-button-secondary-hover);
}

/* As a button's, the ring keeps 3px clear of the select's 3px shadow (3.13) */
.select-input:focus-visible,
.select-input.is-focus {
  outline-offset: calc(var(--shadow-offset-sm) + var(--border-thick));
}

/* The window: a frame with a three-dot bar, for messages and Share's fallback. A visual frame, not a modal:
   what it says is announced through a polite live region, as today. */
.window {
  min-width: 0;
  background-color: var(--surface-card);
  border: var(--border-thick) solid var(--border);
  box-shadow: var(--shadow-lg);
}

.window-bar {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
  background-color: var(--surface-window-bar);
  border-bottom: var(--border-thick) solid var(--border);
}

.window-dot {
  width: 1rem;
  height: 1rem;
  background-color: var(--border);
  border-radius: var(--radius-circle);
}

/* Where the window is, after its dots: "Wonderland › AI" on the tabs window */
.window-path {
  margin-left: var(--space-xs);
  font-size: var(--type-label);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.window-body {
  display: grid;
  gap: var(--space-xs);
  padding: var(--space-md);
}

.window-title {
  font-family: var(--font-display);
  font-size: var(--type-h3);
  line-height: 1.08;
}

/* Pixel art (FR 25-26): PNGs that node tools/pixel-art.mjs render draws from content/pixel-art/ at one image
   pixel per art pixel, shown only at whole-number scales, so every art pixel is a square of whole CSS pixels
   and no edge blurs. Decoration only: alt="", and never behind body text.
   CSS can't read an image's natural size, so each image names its natural width by a width class, which sets
   --pixel-width (a fact about the file, like its width attribute, not a token). Its width and height attributes
   hold its size at its largest scale: they keep its space before it loads, and that size if the stylesheet
   doesn't arrive. A scale class sets the width; the height follows from the attributes' aspect ratio. Scales are
   in px, never rem: a reader's larger default font size would make a rem scale fractional.
     <img class="pixel-art pixel-art--x4 pixel-art--w16" src="/assets/images/pixel/white-rabbit.png"
          width="64" height="96" alt="">
   An image without a width class keeps its attribute size at every width, still a whole-number scale. */
.pixel-art {
  flex-shrink: 0;
  max-width: none;
  height: auto;
  image-rendering: pixelated;
}

/* The width classes: one for each width the art comes in, in art pixels. A class, not a style attribute, which
   Hardening's Content Security Policy will refuse (Shared Layer PRD, FR 11); --pixel-width is the one custom
   property set outside site.css's token block, and only here (node tools/check-tokens.mjs). Art in a new width
   gets its class here. */
.pixel-art--w12 {
  --pixel-width: 12;
}

.pixel-art--w16 {
  --pixel-width: 16;
}

.pixel-art--w18 {
  --pixel-width: 18;
}

.pixel-art--w22 {
  --pixel-width: 22;
}

.pixel-art--w24 {
  --pixel-width: 24;
}

.pixel-art--w26 {
  --pixel-width: 26;
}

.pixel-art--w32 {
  --pixel-width: 32;
}

.pixel-art--w56 {
  --pixel-width: 56;
}

/* Each scale class steps down by whole numbers at the breakpoints: x6 to x5 and then x4, x5 to x4 and then x3,
   x4 to x3 and then x2, x3 to x2 on a phone, and x2 stays, below which an art pixel is too small to read as one.
   x5 and x6 are for the largest scenery, Discover Me's cabin and castle (Carlo, 2026-10-21, from the size board). */
.pixel-art--x2 {
  width: calc(var(--pixel-width) * 2px);
}

.pixel-art--x3 {
  width: calc(var(--pixel-width) * 3px);
}

.pixel-art--x4 {
  width: calc(var(--pixel-width) * 4px);
}

.pixel-art--x5 {
  width: calc(var(--pixel-width) * 5px);
}

.pixel-art--x6 {
  width: calc(var(--pixel-width) * 6px);
}

/* The ground: grass-tile.png repeated across at x2, the footer's and Discover Me's, which stays at every width.
   Its 16px tile is written here, since a background has no attributes to carry it. */
.pixel-ground {
  background: url('../images/pixel/grass-tile.png') repeat-x left top;
  image-rendering: pixelated;
}

.pixel-ground--x2 {
  height: calc(16px * 2);
  background-size: calc(16px * 2) calc(16px * 2);
}

@media (max-width: 768px) {
  .pixel-art--x4 {
    width: calc(var(--pixel-width) * 3px);
  }

  .pixel-art--x5 {
    width: calc(var(--pixel-width) * 4px);
  }

  .pixel-art--x6 {
    width: calc(var(--pixel-width) * 5px);
  }
}

@media (max-width: 480px) {
  .pixel-art--x3,
  .pixel-art--x4 {
    width: calc(var(--pixel-width) * 2px);
  }

  .pixel-art--x5 {
    width: calc(var(--pixel-width) * 3px);
  }

  .pixel-art--x6 {
    width: calc(var(--pixel-width) * 4px);
  }
}

/* A page's intro paragraph on a panel over its scenery: Rabbit Hole's, Wonderland's */
.page-intro {
  max-width: 30em;
  padding: var(--space-sm) var(--space-md);
  text-align: left;
  background-color: var(--surface-card);
  border: var(--border-thick) solid var(--border);
  box-shadow: var(--shadow-md);
}

/* A skill meter: 20 segments, gold lit on dark unlit inside an ink frame, like a game's health bar. Lit and
   unlit differ by more than hue (6.01:1), so the value reads without color vision. */
.meter {
  display: grid;
  grid-template-columns: repeat(20, minmax(0, 1fr));
  gap: var(--border-thin);
  padding: var(--border-thin);
  background-color: var(--border);
}

.meter-segment {
  height: 1.4rem;
  background-color: var(--meter-unlit);
}

.meter-segment.is-lit {
  background-color: var(--meter-lit);
}

/* The Polaroid frame (Shared Layer PRD, FR 12), from the ideas board's round 2 (Carlo, 2026-10-18): a photo in a
   white frame with a deeper bottom margin, where its caption is handwritten, inside the thick ink border and the
   large hard shadow. A <figure>, so its <figcaption> is the photo's caption, and the photo keeps its own alt
   text, which says what the caption doesn't. The figure names itself by its caption through aria-labelledby:
   Chromium's accessibility tree doesn't take a figure's name from its figcaption (Edge, 2026-10-19), so without it
   the figure has none there. The shape is chosen with the photo, square or wide (16:9), and
   the place sets only the width, up to 360px square or 440px wide. It tilts 2.5 degrees, or the other way with
   --tilt-left, which a neighbor takes, as the board's did; the tilt is still, so nothing on it moves. The pages
   wrap the photo in a <picture> for its WebP files (tools/photos.py writes each photo at the frame's width and
   twice it); the frame's rules style the img, so they hold with or without one.
     <figure class="polaroid polaroid--wide" aria-labelledby="welcome-photo-caption">
       <img class="polaroid-photo" src="/assets/images/photos/carlo-and-robot-chrissy-406.jpg" width="406"
            height="228" alt="Carlo and Robot Chrissy collaborating">
       <figcaption class="polaroid-caption" id="welcome-photo-caption">Pair programming with Robot Chrissy</figcaption>
     </figure> */
.polaroid {
  width: 100%;
  max-width: 36rem;
  padding: var(--space-sm) var(--space-sm) 0;
  background-color: var(--surface-card);
  border: var(--border-thick) solid var(--border);
  box-shadow: var(--shadow-lg);
  transform: rotate(2.5deg);
}

.polaroid--wide {
  max-width: 44rem;
}

.polaroid--tilt-left {
  transform: rotate(-3deg);
}

/* The photo fills the frame's width at the frame's shape, cropped about its center */
.polaroid-photo {
  display: block;
  width: 100%;
  height: auto;
  object-fit: cover;
  border: var(--border-thin) solid var(--border);
}

.polaroid--square .polaroid-photo {
  aspect-ratio: 1;
}

.polaroid--wide .polaroid-photo {
  aspect-ratio: 16 / 9;
}

/* The caption in Caveat, the only text set in it (site.css's --font-caption), centered in a bottom margin at
   least 64px tall, two balanced lines at most */
.polaroid-caption {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: var(--space-3xl);
  padding: var(--space-xs) var(--space-xs) var(--space-sm);
  font-family: var(--font-caption);
  font-size: var(--type-caption);
  font-weight: 600;
  line-height: 1.1;
  text-align: center;
  text-wrap: balance;
}

/* The swing-out menu (FR 21-24; Shared Layer PRD, FR 13, 16), carried from the direction board, on a
   <details class="menu"> whose <summary> is the Menu button (.btn--primary), fixed at the window's top left, over
   the open menu. Without navigation.js, or before it runs, the browser opens and closes the <details> itself and
   the panel shows in place, flat and opaque. Once the script has taken over (.is-scripted), the panel waits
   folded back and drops from the window's top edge on a hinge, as Sugarfire Pie's panels do, swings past flat and
   settles, in 0.75s (slower than the board's 0.45s: Carlo, 8.6), and its Close tab, which only the script shows,
   swings out sideways after it, its swing and its delay slowed by the same factor. Only transform and opacity
   animate (FR 21, 27). Closed, the <details> keeps the panel out of the Tab order, the accessibility tree and hit
   testing; the script closes it at once and keeps the panel painted, and inert, while it fades (.is-closing). */
.menu-button {
  position: fixed;
  top: var(--space-md);
  left: var(--space-md);
  z-index: var(--z-overlay-control);
  list-style: none; /* Firefox's marker, on a summary shown as a list item */
}

/* Safari's marker */
.menu-button::-webkit-details-marker {
  display: none;
}

/* The window the panel drops into: its perspective makes the swing three-dimensional. Clicks pass through it
   to the page; only the panel takes them. */
.menu-stage {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  perspective: 100rem;
  pointer-events: none;
}

/* The list scrolls inside the panel on a screen shorter than the menu (WCAG 1.4.10), not the panel itself,
   which would cut off the Close tab beside it */
.menu-panel {
  position: absolute;
  top: 0;
  left: var(--space-md);
  display: flex;
  flex-direction: column;
  width: min(38rem, calc(100% - 10.4rem)); /* the Close tab and a gap beside it */
  max-height: 100%;
  padding-top: var(--space-3xl); /* under the Menu button */
  border: var(--border-thick) solid var(--border);
  box-shadow: var(--shadow-lg);
  pointer-events: auto;
  transform-origin: 50% 0;
}

/* Folded back while closed only once the script has taken over: without it, a visitor would open a panel they
   can't see */
.menu.is-scripted .menu-panel {
  opacity: 0;
  transform: rotateX(-90deg);
  transition: transform 0.75s cubic-bezier(0.57, 1.13, 0.72, 1.46), opacity 0.2s linear;
}

.menu.is-scripted .menu-panel.is-open {
  opacity: 1;
  transform: rotateX(0deg);
  transition: transform 0.75s cubic-bezier(0.57, 1.13, 0.72, 1.46), opacity 0.12s linear;
}

/* Styled once with no transition as it opens (navigation.js): Firefox styles a panel it skipped inside the closed
   <details> only then, from a transform of none, and ran a transition from flat to folded that the opening turned
   straight back, so the first open popped flat (the Pages task list's 5.8). The Close tab with it. */
.menu.is-scripted .menu-panel.is-still,
.menu.is-scripted .menu-panel.is-still .menu-close {
  transition: none;
}

/* Closing, the <details> is closed already, so the Menu button reports it, and its content stays painted until the
   panel's fade ends: content-visibility is how Chromium, Firefox and WebKit hide a closed <details>'s content
   (probed 2026-10-19); a browser without ::details-content drops this rule and hides the panel at once */
.menu.is-closing::details-content {
  content-visibility: visible;
}

/* Padding inside the scrolling list, so it doesn't clip an item's focus ring. The gap keeps a link's ring, which
   reaches 6px outside it, off its neighbor: on the current page's gold plate a gold ring's edge vanished (task 8.8) */
.menu-list {
  display: grid;
  gap: var(--space-xs);
  min-height: 0;
  margin: 0;
  padding: var(--space-xs) var(--space-lg) var(--space-lg);
  overflow-y: auto;
  overscroll-behavior: contain;
  list-style: none;
}

/* Every item fits on one line in a 320px window. With enlarged text, a word too long for the list breaks
   rather than scrolling the list sideways: anywhere, not break-word, because the text is a flex item, whose
   smallest width only anywhere lowers. Hover underlines as well as recoloring, a change of shape as links in
   text have. */
.menu-link {
  display: flex;
  align-items: center;
  min-height: 4.4rem;
  color: var(--link-inverse);
  font-family: var(--font-display);
  font-size: var(--type-h3);
  line-height: 1.08;
  text-decoration: none;
  overflow-wrap: anywhere;
  border-bottom: var(--border-thin) solid var(--rule-on-inverse);
}

.menu-link:hover,
.menu-link.is-hover {
  color: var(--link-inverse-hover);
  text-decoration-line: underline;
  text-decoration-thickness: 0.1em;
  text-underline-offset: 0.3em;
}

/* The current-page mark (Shared Layer PRD, FR 15; Carlo's pick, 2026-10-19): the link to the page shown, in the menu
   and the footer, both on the dark surface, set in ink on a gold plate, the label plates' look, reaching a step past
   its text on each side. It differs from every other link by its fill, a hovered one's too, so it reads in
   grayscale: 11.2:1 for its text, and for the plate against the dark surface. Screen readers hear aria-current, which
   tools/pages.mjs writes: "page", or "true" on Wonderland's link on a post page, its section. The :hover rule
   outranks site.css's hover on the dark surface, so the text never turns gold on the gold. */
.surface-inverse a[aria-current],
.surface-inverse a[aria-current]:hover {
  margin-inline: calc(var(--space-xs) * -1);
  padding-inline: var(--space-xs);
  color: var(--text);
  text-decoration-line: none;
  background-color: var(--surface-plate-accent);
}

/* The Reduce motion switch's row at the panel's foot (Shared Layer PRD, FR 17; Carlo's pick, 2026-10-20, task 8.1):
   under a rule, outside the scrolling list, so on a screen shorter than the menu it stays in view while the links
   scroll above it, and the list holds the six links alone. The switch is the footer's (.motion-switch, below);
   the row is hidden until footer.js shows it. */
.menu-motion {
  flex-shrink: 0;
  padding: var(--space-sm) var(--space-lg) var(--space-lg);
  border-top: var(--border-thin) solid var(--rule-on-inverse);
}

/* The Close tab hangs beside the panel, over the page. It is hidden until the script shows it, since only the
   script can close the menu from it. */
.menu-close {
  position: absolute;
  top: calc(var(--border-thick) * -1);
  left: 100%;
  min-width: 7.2rem;
  min-height: 6.4rem;
  padding: 0 var(--space-sm);
  color: var(--text);
  font-family: var(--font-reading);
  font-size: var(--type-body);
  font-weight: 700;
  line-height: 1.2;
  background-color: var(--surface-menu-close);
  border: var(--border-thick) solid var(--border);
  cursor: pointer;
  transform: rotateY(-90deg);
  transform-origin: 0 50%;
  transition: transform 0.67s cubic-bezier(0.57, 1.13, 0.72, 1.46);
}

.menu-panel.is-open .menu-close {
  transform: rotateY(0deg);
  transition-delay: 0.5s;
}

.menu-close:hover,
.menu-close.is-hover {
  background-color: var(--surface-menu-close-hover);
}

/* The menu's two controls that float over the page draw their focus rings inside the border, with 3px of their own
   fill between, so each is the same over whatever scrolls under them and never meets the window's edge (Carlo,
   2026-10-13, from the board https://claude.ai/artifact/8fGDghThWYrUcKYzHvtPi3). The links take the base's ring,
   FR 20's, outside them and gold on the panel's dark surface. */
.menu-button:focus-visible,
.menu-button.is-focus,
.menu-close:focus-visible,
.menu-close.is-focus {
  outline: var(--border-thick) solid var(--focus);
  outline-offset: calc(var(--border-thick) * -3);
}

/* Reduced motion (FR 22; Shared Layer PRD, Q9): nothing turns, and site.css's stop rule leaves nothing to fade,
   so the menu opens and closes at once, in place. Under the Reduce motion switch's class, which the head's motion
   script sets, and below for the system setting without JavaScript. */
html.motion-reduce .menu.is-scripted .menu-panel,
html.motion-reduce .menu.is-scripted .menu-panel.is-open,
html.motion-reduce .menu-close,
html.motion-reduce .menu-panel.is-open .menu-close {
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .menu.is-scripted .menu-panel,
  .menu.is-scripted .menu-panel.is-open,
  .menu-close,
  .menu-panel.is-open .menu-close {
    transform: none;
  }
}

/* The footer (FR 18; Shared Layer PRD, FR 19), on every page but Desktop, written from
   content/templates/footer.html: a strip of pixel scenery, then the dark surface with the tagline, the three groups
   of links, the Reduce motion switch's row and the copyright, centered: the tagline on two balanced lines, the
   groups a centered row whose lists keep their left edge, and on a phone, where the groups stack, every line
   centered (Carlo, from the footer board, 2026-10-21: the Pages task list's 2.13). It shows without JavaScript; only
   the switch's row waits for footer.js. Its type, its links' underline and its focus ring are site.css's base, and
   its colors the base's dark surface (.surface-inverse, on .footer-inner), as on every page since the Pages PRD
   rebuilt them all. */
.footer-strip {
  position: relative;
  height: 9.6rem;
  overflow: hidden;
  background-color: var(--surface-scenery);
}

.footer-ground {
  position: absolute;
  right: 0;
  bottom: 0;
  left: 0;
}

/* The sprites stand on the ground: its 16px tile at x2 */
.footer-sprite {
  position: absolute;
  bottom: calc(16px * 2);
}

/* The man from Discover Me and the White Rabbit side by side, the Cheshire Cat apart from them (7.9) */
.footer-sprite--man {
  left: 12%;
}

.footer-sprite--rabbit {
  left: calc(12% + 6.4rem);
}

.footer-sprite--cat {
  right: 14%;
}

.footer-inner {
  display: grid;
  gap: var(--space-lg);
  padding: var(--space-xl) var(--space-lg) var(--space-lg);
}

/* A measure of 40ch holds it to two balanced lines on a wide screen; narrower, it wraps to the window. Its margins
   give it room of its own over the inner box's padding and gap (Carlo, at Privacy's review: the Pages task list's
   2.15) */
.footer-tagline {
  justify-self: center;
  max-width: 40ch;
  margin-block: var(--space-md);
  color: var(--text-inverse-accent);
  font-family: var(--font-display);
  font-size: var(--type-h2);
  line-height: 1.08;
  text-align: center;
  text-wrap: balance;
}

/* The groups side by side in the middle, each as wide as its longest line, wrapping onto a second row where a
   window is too narrow for three */
.footer-nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-lg) var(--space-3xl);
}

/* On a phone the groups stack, and every line centers: the titles, the links and the switch, which is inline */
@media (max-width: 480px) {
  .footer-nav {
    flex-direction: column;
    text-align: center;
  }
}

.footer-group-title {
  margin-bottom: var(--space-xs);
  font-family: var(--font-display);
  font-size: var(--type-h3);
  line-height: 1.08;
}

/* The gap keeps a link's focus ring, which reaches 6px outside it, off its neighbor: on the current page's gold
   plate a gold ring's edge vanished (task 8.2) */
.footer-links {
  display: grid;
  gap: var(--space-xs);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Each link at least 36px tall, over the 24px floor for anything clickable. Its underline is the base link's, and
   site.css's dark-surface rules give its color, gold on hover. */
.footer-links a {
  display: inline-flex;
  align-items: center;
  min-height: 3.6rem;
}

.footer-copyright {
  padding-top: var(--space-sm);
  font-size: var(--type-small);
  text-align: center;
  border-top: var(--border-thin) solid var(--rule-on-inverse);
}

/* The Reduce motion switch (Shared Layer PRD, FR 17, 19; Carlo's pick, 2026-10-20, task 8.1): the Foundation's B1,
   a track and a thumb whose side shows the state, square as the retro look is, on the dark surface, on its own row
   in the footer's Legal group, above the copyright, and at the menu panel's foot (.menu-motion, above). A
   <button role="switch"> whose aria-checked footer.js keeps, the two switches always in step; each row is hidden
   until the script shows it, since without the script the switch couldn't work. At least 44px
   tall, as every button. Off, a white outline with the thumb at the left; on, filled gold with an ink thumb at the
   right, so the two differ in grayscale too (tools/contrast.mjs). The thumb jumps rather than slides: turning the
   switch on stops motion at once. Its focus ring is the base's, gold on the dark surface both rows sit on. */
.footer-motion {
  padding-top: var(--space-sm);
}

.motion-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--space-sm);
  min-height: 4.4rem;
  padding: 0;
  color: var(--text-inverse);
  font-family: var(--font-reading);
  font-size: var(--type-body);
  line-height: 1.2;
  text-align: start;
  background: none;
  border: 0;
  cursor: pointer;
}

.motion-switch-track {
  position: relative;
  flex-shrink: 0;
  width: 5.2rem;
  height: 2.8rem;
  border: var(--border-thick) solid var(--text-inverse);
}

/* The thumb sits 3px inside the track's 3px outline */
.motion-switch-thumb {
  position: absolute;
  top: var(--border-thick);
  left: var(--border-thick);
  width: var(--space-md);
  height: var(--space-md);
  background-color: var(--text-inverse);
}

.motion-switch[aria-checked="true"] .motion-switch-track {
  background-color: var(--surface-button);
  border-color: var(--surface-button);
}

.motion-switch[aria-checked="true"] .motion-switch-thumb {
  right: var(--border-thick);
  left: auto;
  background-color: var(--surface-inverse);
}

/* Windows' forced colors drop backgrounds, which would erase the thumb and with it the switch's state */
@media (forced-colors: active) {
  .motion-switch-thumb,
  .motion-switch[aria-checked="true"] .motion-switch-thumb {
    forced-color-adjust: none;
    background-color: CanvasText;
  }
}
