/* === tokens.css === */
/* thru-ds design tokens.
   Palette lives in OKLCH: L is perceptual lightness (0..1), C is chroma,
   H is the hue angle. Neutrals share one cool hue (210) so the gray ramp
   stays consistent; the red, warm, teal, and blue semantics each hold a
   single hue across their steps. All values sit inside the sRGB gamut.
   Light is the default. Dark applies when the html element has
   data-theme="dark", or by system preference when no explicit theme is set. */
:root {
  --font-sans:
    "Inter Tight", ui-sans-serif, system-ui, -apple-system, sans-serif;
  --font-mono:
    "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    monospace;
  /* display reuses the sans (Inter Tight carries weight 800), tightened for the hero register */
  --font-display:
    "Inter Tight", ui-sans-serif, system-ui, -apple-system, sans-serif;

  /* type scale. base is the body size; the dashboard runs dense, so the
     small end is close-packed and the large end climbs for headings. */
  --text-2xs: 10.5px;
  --text-xs: 11.5px;
  --text-sm: 13px;
  --text-ui: 14px;
  --text-base: 15px;
  --text-lg: 18px;
  --text-xl: 22px;
  --text-2xl: 27px;
  --text-3xl: 34px;
  --text-4xl: 44px;

  /* line-height: headings tight, body open. unitless so it scales with size. */
  --leading-tight: 1.1;
  --leading-snug: 1.2;
  --leading-normal: 1.5;
  --leading-relaxed: 1.6;

  /* letter-spacing: large type wants negative tracking, small uppercase labels positive. */
  --tracking-tighter: -0.03em;
  --tracking-tight: -0.02em;
  --tracking-snug: -0.01em;
  --tracking-normal: 0;
  --tracking-wide: 0.08em;
  --tracking-wider: 0.12em;

  /* reading measure for prose blocks (roughly 65 to 70 characters) */
  --measure: 68ch;

  /* golden-ratio spacing scale (phi ~ 1.618) */
  --space-1: 4px;
  --space-2: 6px;
  --space-3: 10px;
  --space-4: 16px;
  --space-5: 26px;
  --space-6: 42px;
  --space-7: 68px;
  --space-8: 110px;

  --radius-sm: 2px;
  --radius: 3px;
  --radius-lg: 4px;
  --border-w: 1px;

  --content-max: 1180px;
  /* the desktop header bar; anything that sticks below it offsets by this */
  --header-h: 60px;
  --top-strip: oklch(0.17 0.01 210);

  /* light theme */
  --chrome: oklch(0.925 0.01 210);
  --chrome-border: oklch(0.895 0.014 210);
  --bg: oklch(0.985 0.004 210);
  --surface: oklch(1 0 0);
  --surface-2: oklch(0.975 0.005 210);
  --ink: oklch(0.32 0.018 210);
  --ink-2: oklch(0.47 0.028 210);
  --muted: oklch(0.548 0.024 210);
  --border: oklch(0.9 0.013 210);
  --border-strong: oklch(0.8 0.02 210);

  /* brand blue, taken from the logo lens: deep #1173d4 for links and
     accents in light mode, glowing #48c7ff territory in dark mode. danger
     keeps its own red so warnings never read as brand. */
  --accent: oklch(0.54 0.14 250);
  --accent-2: oklch(0.68 0.12 242);
  --accent-3: oklch(0.82 0.08 235);
  --accent-4: oklch(0.93 0.035 235);
  --on-accent: oklch(1 0 0);

  --warm: oklch(0.79 0.152 65);
  --warm-2: oklch(0.86 0.1 65);
  --warm-3: oklch(0.925 0.05 65);
  --warm-4: oklch(0.52 0.093 55);
  /* A warm fill that can carry type: white on it is 7.4:1. Plain --warm is
     the color of a hot column, and at that lightness the only type that
     passes on it is a dark shade of its own hue, which measures fine and
     reads as mud. Deep enough for white, still unmistakably the columns'
     orange. One value for both themes, since it is dark in both. */
  --warm-deep: oklch(0.46 0.115 55);
  --on-warm-deep: oklch(1 0 0);

  --danger: oklch(0.52 0.19 25);
  --danger-bg: oklch(0.935 0.035 25);
  --info: oklch(0.55 0.13 235);

  --btn-bg: oklch(0.245 0.014 210);
  --btn-fg: oklch(1 0 0);

  color-scheme: light;
}

:root[data-theme="dark"] {
  --chrome: oklch(0.195 0.01 210);
  --chrome-border: oklch(0.32 0.016 210);
  --bg: oklch(0.215 0.01 210);
  --surface: oklch(0.255 0.013 210);
  --surface-2: oklch(0.285 0.014 210);
  --ink: oklch(0.955 0.006 210);
  --ink-2: oklch(0.8 0.022 210);
  --muted: oklch(0.66 0.026 210);
  --border: oklch(0.325 0.016 210);
  --border-strong: oklch(0.43 0.02 210);

  --accent: oklch(0.75 0.125 230);
  --accent-2: oklch(0.68 0.12 238);
  --accent-3: oklch(0.55 0.14 248);
  --accent-4: oklch(0.32 0.06 240);
  --on-accent: oklch(0.18 0.03 240);

  --warm: oklch(0.8 0.15 65);
  --warm-2: oklch(0.73 0.12 65);
  --warm-3: oklch(0.31 0.055 65);
  --warm-4: oklch(0.78 0.088 55);

  --danger: oklch(0.7 0.165 25);
  --danger-bg: oklch(0.29 0.05 25);
  --info: oklch(0.72 0.1 235);

  --btn-bg: oklch(0.955 0.006 210);
  --btn-fg: oklch(0.195 0.01 210);

  color-scheme: dark;
}

/* respect system preference until the user picks a theme explicitly.
   kept in sync with :root[data-theme="dark"] above. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --chrome: oklch(0.195 0.01 210);
    --chrome-border: oklch(0.32 0.016 210);
    --bg: oklch(0.215 0.01 210);
    --surface: oklch(0.255 0.013 210);
    --surface-2: oklch(0.285 0.014 210);
    --ink: oklch(0.955 0.006 210);
    --ink-2: oklch(0.8 0.022 210);
    --muted: oklch(0.66 0.026 210);
    --border: oklch(0.325 0.016 210);
    --border-strong: oklch(0.43 0.02 210);

    --accent: oklch(0.75 0.125 230);
    --accent-2: oklch(0.68 0.12 238);
    --accent-3: oklch(0.55 0.14 248);
    --accent-4: oklch(0.32 0.06 240);
    --on-accent: oklch(0.18 0.03 240);

    --warm: oklch(0.8 0.15 65);
    --warm-2: oklch(0.73 0.12 65);
    --warm-3: oklch(0.31 0.055 65);
    --warm-4: oklch(0.78 0.088 55);

    --danger: oklch(0.7 0.165 25);
    --danger-bg: oklch(0.29 0.05 25);
    --info: oklch(0.72 0.1 235);

    --btn-bg: oklch(0.955 0.006 210);
    --btn-fg: oklch(0.195 0.01 210);

    color-scheme: dark;
  }
}

/* === base.css === */
/* thru-ds base: reset, typography, and the light/dark transition. */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--chrome);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  /* render weights as loaded; never let the browser fake a bold or italic */
  font-synthesis: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}

/* Nothing brings its own spacing to a layout. A container spaces its children
   with gap, or a rule asks for a margin on purpose. Browser defaults are what
   stack on top of a gap and leave one pair of things further apart than the
   pair beside it, which is a hard bug to see and an easy one to ship. Prose,
   where a paragraph really does need air under it, sets its own below. */
p,
ul,
ol,
dl,
figure,
blockquote {
  margin: 0;
}

/* heading hierarchy: sizes descend by level, tracking tightens as type grows,
   line-height stays snug, and lines balance so a heading never leaves one
   word stranded. */
h1,
h2,
h3,
h4 {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 600;
  line-height: var(--leading-snug);
  color: var(--ink);
  text-wrap: balance;
}
h1 {
  font-family: var(--font-display);
  font-size: var(--text-3xl);
  font-weight: 700;
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tighter);
}
h2 {
  font-size: var(--text-2xl);
  letter-spacing: var(--tracking-tight);
}
h3 {
  font-size: var(--text-xl);
  letter-spacing: var(--tracking-tight);
}
h4 {
  font-size: var(--text-lg);
  letter-spacing: var(--tracking-snug);
}

a {
  color: var(--accent);
  text-decoration: none;
}
a:hover {
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.16em;
  text-decoration-skip-ink: auto;
}

code,
kbd,
samp {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

::selection {
  background: var(--accent-4);
  color: var(--ink);
}

/* smooth theme transition on the elements that carry themed color */
html,
body,
.ds-header,
.ds-panel,
.ds-statcard,
.ds-badge,
.ds-btn,
.ds-toggle,
.ds-table thead th,
.ds-table tbody td {
  transition:
    background-color 0.28s ease,
    border-color 0.28s ease,
    color 0.28s ease;
}

/* View Transitions crossfade when the toggle uses it */
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.28s;
}

@media (prefers-reduced-motion: reduce) {
  html,
  body,
  .ds-header,
  .ds-panel,
  .ds-statcard,
  .ds-badge,
  .ds-btn,
  .ds-toggle {
    transition: none;
  }
  ::view-transition-old(root),
  ::view-transition-new(root) {
    animation: none;
  }
}

/* === components.css === */
/* thru-ds component styles. Class prefix: ds- */

/* shell + centered full-bleed frame */
.ds-shell {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--chrome);
}
.ds-topstrip {
  height: 4px;
  background: var(--top-strip);
}
.ds-container {
  /* the gutter is a variable so a child can bleed back out to the page edge
     by cancelling exactly as much padding as the container applied */
  --gutter: var(--space-5);
  width: 100%;
  max-width: var(--content-max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.ds-main {
  flex: 1;
  padding-block: var(--space-6);
}

/* header */
.ds-header {
  background: var(--chrome);
  border-bottom: var(--border-w) solid var(--chrome-border);
}
.ds-header__inner {
  display: flex;
  align-items: stretch;
  gap: var(--space-4);
  height: var(--header-h);
}
.ds-header__logo {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-weight: 700;
  color: var(--ink);
  /* the wordmark is one thing; it never breaks across two lines */
  white-space: nowrap;
}
/* the links carry their own hit area and sit full height, so the current
   page can be marked with a rule along the header's bottom edge */
.ds-header__nav {
  display: flex;
  align-items: stretch;
  gap: var(--space-1);
  min-width: 0;
  margin-right: auto;
}
.ds-header__nav a {
  position: relative;
  display: inline-flex;
  align-items: center;
  padding-inline: var(--space-2);
  color: var(--ink-2);
  font-size: var(--text-ui);
  white-space: nowrap;
  transition: color 0.15s ease;
}
.ds-header__nav a::after {
  content: "";
  position: absolute;
  left: var(--space-2);
  right: var(--space-2);
  bottom: -1px;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.22s cubic-bezier(0.2, 0, 0, 1);
}
.ds-header__nav a:hover {
  color: var(--ink);
  text-decoration: none;
}
.ds-header__nav a.active {
  color: var(--ink);
}
.ds-header__nav a.active::after {
  transform: scaleX(1);
}
@media (prefers-reduced-motion: reduce) {
  .ds-header__nav a::after {
    transition: none;
  }
}
.ds-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-left: auto;
}

/* theme toggle */
.ds-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
.ds-toggle:hover {
  border-color: var(--accent);
  color: var(--accent);
}

/* panel / card, with optional red top accent and folded right end */
.ds-panel {
  position: relative;
  padding: var(--space-5);
  background: var(--surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
}
.ds-panel--flush {
  padding: 0;
}
/* red top accent: a straight bar the full width of the panel, edge to edge.
   no folded corner and no rounding, it just runs all the way across. */
.ds-panel--accent::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--accent);
}

/* stat card */
.ds-statcard {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: var(--space-4) var(--space-5);
  background: var(--surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
}
.ds-statcard__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ds-statcard__value {
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: 500;
  letter-spacing: var(--tracking-snug);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.ds-statcard__sub {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

/* stat grid: the canonical 4-up row of StatCards. base only, no outer margin
   and no breakpoint, so each app sets its own surrounding spacing and its own
   collapse point. the gap matches the dense dashboard rhythm. */
.ds-statgrid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
}
/* four abreast needs room for mono values; two by two below that */
@media (max-width: 640px) {
  .ds-statgrid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

/* badge */
.ds-badge {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border: var(--border-w) solid transparent;
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: 1.5;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ds-badge--neutral {
  background: var(--surface-2);
  color: var(--ink-2);
  border-color: var(--border);
}

/* button */
.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: 9px var(--space-4);
  border: var(--border-w) solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: var(--text-ui);
  font-weight: 500;
  line-height: 1;
  cursor: pointer;
}
/* A control is not prose. Half the buttons on the site are anchors, since
   what they do is go somewhere, and an anchor still matches the link rule:
   hovering "Put it on an avatar" drew a line under the label inside its own
   filled rectangle. The rectangle is the affordance and the colour change is
   the hover, so a label never wants underlining here. */
.ds-btn,
.ds-btn:hover {
  text-decoration: none;
}
.ds-btn--primary {
  background: var(--btn-bg);
  color: var(--btn-fg);
}
.ds-btn--primary:hover {
  background: var(--accent);
  color: var(--on-accent);
}
.ds-btn--secondary {
  background: var(--surface);
  color: var(--ink);
  border-color: var(--border-strong);
}
.ds-btn--secondary:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.ds-btn--ghost {
  background: transparent;
  color: var(--ink-2);
}
.ds-btn--ghost:hover {
  color: var(--accent);
}
.ds-btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ribbon: red folded-corner banner */
.ds-ribbon {
  position: relative;
  display: inline-block;
  padding: 7px var(--space-5) 7px var(--space-4);
  background: var(--accent);
  color: var(--on-accent);
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: var(--text-sm);
  clip-path: polygon(0 0, 100% 0, calc(100% - 12px) 100%, 0 100%);
}

/* table */
.ds-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.ds-table thead th {
  padding: var(--space-3);
  border-bottom: var(--border-w) solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-align: left;
  color: var(--muted);
}
/* The accent bar is absolutely positioned over the panel's top 3px, so a
   header row sitting flush against it loses that much of its top padding
   and the labels ride high. Give the 3px back. */
.ds-panel--accent .ds-table thead th {
  padding-top: calc(var(--space-3) + 3px);
}
.ds-table tbody td {
  padding: var(--space-3);
  border-bottom: var(--border-w) solid var(--border);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.ds-table tbody tr:last-child td {
  border-bottom: none;
}
.ds-table tbody tr:hover td {
  background: var(--surface-2);
}
.ds-num {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* typography helpers */

/* long-form copy: capped measure, open leading, no stranded last word */
.ds-prose {
  max-width: var(--measure);
  line-height: var(--leading-relaxed);
  text-wrap: pretty;
}
/* Running text is the one place a block does space itself, since there is no
   container between one paragraph and the next to hold a gap. */
.ds-prose > * + * {
  margin-top: var(--space-4);
}

/* the bar follows the page down on desktop, where it costs 60px of a tall
   window. Narrow screens keep it at the top of the document: there the header
   is two rows, and parking a sixth of a phone screen under chrome is a worse
   trade than scrolling back up. */
@media (min-width: 1024px) {
  .ds-header {
    position: sticky;
    top: 0;
    z-index: 30;
  }
}

/* ---- compact shell + header -----------------------------------------------
   Below 1024px the dense single-row header stops fitting: eight nav links plus
   a wordmark and a call to action need more room than a tablet has. The logo
   and the actions keep the top row (actions pinned right, free to wrap their
   own children), and the nav drops to a full-width row that scrolls sideways
   instead of widening the page. Every header child can now either wrap or
   scroll, so none is ever wider than the viewport on its own. */
@media (max-width: 1023px) {
  .ds-header__inner {
    flex-wrap: wrap;
    height: auto;
    gap: var(--space-3);
    /* no bottom padding: the nav row sits on the header's own border, which
       is what turns the active rule into a tab underline */
    padding-block: var(--space-3) 0;
  }
  .ds-header__logo {
    flex: none;
    align-items: center;
    min-height: 40px;
  }
  /* actions stay to the right of the top row; min-width:0 plus wrap means a wide
     status pill or a CTA folds to a second line rather than forcing the page. */
  .ds-header__actions {
    min-width: 0;
    flex-wrap: wrap;
    justify-content: flex-end;
    row-gap: var(--space-2);
  }
  /* nav becomes a full-width second row and scrolls horizontally. order:3 puts
     it after the logo and the actions; the basis is the line plus both gutters,
     cancelled by the negative margins, so the rail runs edge to edge while its
     first and last links still line up with the page gutter. */
  .ds-header__nav {
    order: 3;
    flex: none;
    flex-basis: calc(100% + var(--gutter) * 2);
    min-width: 0;
    margin-inline: calc(var(--gutter) * -1);
    /* the links carry their own padding, so the rail stops short of the
       gutter by exactly that much and the first label lines up with the
       wordmark above it */
    padding-inline: calc(var(--gutter) - var(--space-3));
    gap: var(--space-2);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
    /* whichever side still has links behind it fades out, so a cut-off row
       reads as scrollable rather than as the end of the list. client.js sets
       the classes; with none of them the mask is a no-op. */
    --fade-start: 0px;
    --fade-end: 0px;
    -webkit-mask-image: linear-gradient(
      to right,
      transparent,
      #000 var(--fade-start),
      #000 calc(100% - var(--fade-end)),
      transparent
    );
    mask-image: linear-gradient(
      to right,
      transparent,
      #000 var(--fade-start),
      #000 calc(100% - var(--fade-end)),
      transparent
    );
  }
  .ds-header__nav.is-overflowing:not(.at-start) {
    --fade-start: var(--space-5);
  }
  .ds-header__nav.is-overflowing:not(.at-end) {
    --fade-end: var(--space-5);
  }
  .ds-header__nav::-webkit-scrollbar {
    display: none;
  }
  /* a thumb-sized tab, and the underline anchors to the header border */
  .ds-header__nav a {
    min-height: 44px;
    padding-inline: var(--space-3);
    scroll-margin-inline: var(--gutter);
  }
  .ds-header__nav a::after {
    left: var(--space-3);
    right: var(--space-3);
  }

  /* touch target: grow the theme toggle to a comfortable tap size.
     the dense 34px desktop control is untouched (guarded by the breakpoint). */
  .ds-toggle {
    width: 40px;
    height: 40px;
  }
}

@media (max-width: 720px) {
  /* trim the side gutter so phones get more usable width */
  .ds-container {
    --gutter: var(--space-4);
  }

  /* panels trim their padding on phones. a panel that sits in a grid track (the
     about page lead, the how it runs row) would otherwise floor the track at its
     content min-content and push the page wide; the smaller gutter plus the
     shrinkable tracks each app sets let the panel fit the viewport. flush panels
     keep their zero padding so edge-to-edge tables stay flush. the accent
     top-bar spans the full width independent of padding, so it is unaffected. */
  .ds-panel:not(.ds-panel--flush) {
    padding: var(--space-4);
  }
  /* statcards pack tighter so a 2-up row of figures fits a narrow screen */
  .ds-statcard {
    padding: var(--space-3) var(--space-4);
  }
  .ds-statcard__value {
    font-size: var(--text-xl);
  }
}

/* === wompworld/00-preamble.css === */
/* Wompworld app layer on top of the thru design system.
   Class prefix: ww- . Mobile-first: base rules are the phone layout and
   min-width queries add columns as room appears.

   This layer is a folder, concatenated in name order by
   scripts/build-css.mjs. Nothing preprocesses it, so the number on the
   front of each file is the cascade: an equally specific rule in 08 beats
   one in 06. Files are grouped by the page or the piece of furniture they
   dress. A couple of topics are deliberately split across two files (the
   map, the about page) because their rules were interleaved with others
   and pulling them together would change which rule wins. Adding a
   section only means adding a file, since the build reads the directory
   rather than a list. */

/* === wompworld/01-feed.css === */
/* ---- feed grid ---- */
.ww-feed {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
/* A card's nowrap meta line must not set the track's minimum width; the
   ellipsis needs the track to win, so drop the automatic minimum. */
.ww-feed > * {
  min-width: 0;
}
@media (min-width: 640px) {
  .ww-feed {
    grid-template-columns: repeat(2, 1fr);
    gap: var(--space-3);
  }
}
@media (min-width: 960px) {
  .ww-feed {
    grid-template-columns: repeat(3, 1fr);
  }
}

/* ---- womp card ---- */
.ww-card-link,
.ww-card-link:hover {
  color: inherit;
  text-decoration: none;
}
/* Coming back from a permalink lands on the card it was opened from, held
   clear of the header and marked for a moment so the eye finds it. */
.ww-feed > .ww-anim {
  scroll-margin-top: calc(var(--header-h) + var(--space-5));
}
.ww-anim:target {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}
.ww-card {
  /* overflow stays visible so author hover cards can escape; the image
     wrapper below clips its own corners instead */
  height: 100%;
  display: flex;
  flex-direction: column;
  transition:
    border-color 150ms cubic-bezier(0.2, 0, 0, 1),
    box-shadow 150ms cubic-bezier(0.2, 0, 0, 1),
    transform 150ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-card-link:hover .ww-card {
  border-color: var(--border-strong);
  box-shadow:
    0 1px 2px -1px oklch(0 0 0 / 0.09),
    0 10px 24px -8px oklch(0 0 0 / 0.15);
}
.ww-card-link:focus-visible {
  outline: none;
}
.ww-card-link:focus-visible .ww-card {
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-4);
}
:root[data-theme="dark"] .ww-card-link:hover .ww-card {
  box-shadow:
    0 1px 2px -1px oklch(0 0 0 / 0.45),
    0 14px 30px -10px oklch(0 0 0 / 0.55);
}

.ww-card__imgwrap {
  position: relative;
  aspect-ratio: 4 / 3;
  background: var(--surface-2);
  overflow: hidden;
  border-radius: var(--radius) var(--radius) 0 0;
}
.ww-card__imgwrap img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ww-card__body {
  display: flex;
  flex-direction: column;
  /* Caption and byline are two things to read, not one, so they take the
     step between things in a block rather than the one inside a group. */
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-4);
  flex: 1;
}
.ww-card__content {
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--ink);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
/* ---- the byline: who, where, when ----
   The same line under a card and over a post, in the same order at the
   same sizes, because the point of it is that somebody skimming forty
   womps never has to look for anything twice. Face and name are fixed, the
   place gives way first (a name is who and a time is how fresh, and both
   are shorter than "47 Via Fiori Oscuri"), and the time holds the right
   edge whatever happens between them. */
.ww-byline {
  display: flex;
  align-items: center;
  /* Wrapping rather than a breakpoint: a card is narrow on a phone and
     narrow again in a three-column grid on a desktop, and the line should
     answer the space it actually has. Too tight and the place drops to a
     second line, taking the time with it; the name never leaves the top. */
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-3);
  min-width: 0;
  /* Pinned to the foot of a card: most womps carry no caption at all, and
     without this a row of three would sit its three bylines at three
     different heights. */
  margin-top: auto;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted);
}
.ww-byline .ww-author {
  min-width: 0;
  flex: 0 1 auto;
}
.ww-byline__place {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  min-width: 0;
  /* Any longer and a parcel name starts eating the name beside it. */
  max-width: 20ch;
  color: var(--ink-2);
  /* a little invisible padding grows the target without airing out the card */
  padding-block: 2px;
}
.ww-byline__placename {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-byline__place:hover {
  color: var(--accent);
  text-decoration: none;
}
.ww-byline__else {
  flex: none;
}
.ww-byline__time {
  flex: none;
  margin-left: auto;
  padding-block: 2px;
  white-space: nowrap;
  color: var(--muted);
}
.ww-byline__time:hover {
  color: var(--accent);
  text-decoration: none;
}
/* Over a run of pictures there is a column to work with, so the name is
   set at reading size and the face grows to meet it. */
.ww-byline--lg .ww-author__label {
  font-family: var(--font-sans);
  font-size: var(--text-base);
  font-weight: 600;
  letter-spacing: var(--tracking-snug);
  color: var(--ink);
}
.ww-byline--lg .ww-byline__place {
  max-width: 24ch;
}

/* staggered enter for the first screen of cards, matching the thru portal */
@media (prefers-reduced-motion: no-preference) {
  .ww-feed > .ww-anim {
    opacity: 0;
    animation: ww-rise 460ms cubic-bezier(0.2, 0, 0, 1) forwards;
  }
  .ww-feed > .ww-anim:nth-child(1) {
    animation-delay: 40ms;
  }
  .ww-feed > .ww-anim:nth-child(2) {
    animation-delay: 80ms;
  }
  .ww-feed > .ww-anim:nth-child(3) {
    animation-delay: 120ms;
  }
  .ww-feed > .ww-anim:nth-child(4) {
    animation-delay: 160ms;
  }
  .ww-feed > .ww-anim:nth-child(5) {
    animation-delay: 200ms;
  }
  .ww-feed > .ww-anim:nth-child(6) {
    animation-delay: 240ms;
  }
  .ww-card-link:hover .ww-card {
    transform: translateY(-2px);
  }
  .ww-card-link:active .ww-card {
    transform: translateY(-1px);
    transition-duration: 90ms;
  }
}
@keyframes ww-rise {
  from {
    opacity: 0;
    transform: translateY(10px);
    filter: blur(4px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
    filter: blur(0);
  }
}

/* ---- layout rhythm ---- */
.ww-section {
  margin-bottom: var(--space-5);
}

/* mono, uppercase section label used between stacked boards */
.ww-seclabel {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 var(--space-3);
}

/* === wompworld/02-pagehead.css === */
/* ---- page headers (section title + count) ---- */
.ww-pagehead {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.ww-pagehead .ds-badge {
  position: relative;
  top: -2px;
}
.ww-pagehead__sub {
  width: 100%;
  font-size: var(--text-sm);
  /* ink-2 on the chrome background; muted fails WCAG AA there */
  color: var(--ink-2);
}
/* Inside the header the gap owns the spacing, so the paragraph's own margin
   does not stack on top of it: that is what used to leave the line under a
   title closer to the title than the block below it was to the line. The
   same class is used on its own elsewhere (a profile bio, a leaderboard
   timestamp), where the margin is the only spacing there is, so it stays. */
.ww-pagehead > .ww-pagehead__sub {
  margin: 0;
}
/* The one line under the wordmark. It has to sit a step above the small
   print, because the "Top this week" row right below it is the same
   element: same size and colour would read as two utility lines rather
   than a sentence and then the names. No max-width here, since the header
   is a flex row and a clamped width lets the line ride up beside the
   title instead of sitting under it. */
.ww-pagehead__lede {
  font-size: var(--text-base);
  color: var(--ink);
}
.ww-pagehead__sub a {
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.16em;
}
.ww-pagehead__sub a:hover {
  color: var(--accent);
}
/* A link inside the lede takes the sentence's own ink. The small print
   sets its links in ink-2, which under the darker lede text would read as
   the one dimmed word in the line. The underline carries it instead. */
.ww-pagehead__lede a {
  color: inherit;
}

/* ---- womp of the week strip ---- */
.ww-wotw {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin-bottom: var(--space-5);
  overflow: hidden;
}
.ww-wotw__imgwrap {
  aspect-ratio: 4 / 3;
  background: var(--surface-2);
}
.ww-wotw__imgwrap img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ww-wotw__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  padding: var(--space-4);
}
.ww-wotw__ribbonrow {
  margin: 0 calc(-1 * var(--space-4)) 0;
}
.ww-wotw__mid,
.ww-wotw__foot {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.ww-wotw__foot {
  gap: var(--space-3);
}
/* A caption can run to a paragraph, and a paragraph in this rail pushes
   the reason it won off the bottom of the picture. */
.ww-wotw__quote {
  display: -webkit-box;
  -webkit-line-clamp: 4;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.ww-wotw__why {
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--ink-2);
}
/* Why this one: the two numbers that picked it, in the mono the rest of
   the site counts in. */
.ww-wotw__stats {
  display: flex;
  gap: var(--space-6);
}
.ww-wotw__stat {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.ww-wotw__statval {
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: 500;
  line-height: 1;
  letter-spacing: var(--tracking-snug);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.ww-wotw__statlabel {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-wotw__go {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-2);
}
.ww-wotw__go:hover {
  color: var(--accent);
  text-decoration: none;
}
@media (min-width: 720px) {
  .ww-wotw {
    grid-template-columns: minmax(0, 1.618fr) minmax(300px, 1fr);
  }
  .ww-wotw__imgwrap {
    aspect-ratio: auto;
    min-height: 100%;
    /* A 4:3 womp at this width opened the front page with a picture and
       nothing else under it, and left the rail beside it a column of
       empty surface. The hero stops at a band and crops to it. */
    max-height: 420px;
  }
  .ww-wotw__body {
    padding: var(--space-5);
  }
  .ww-wotw__ribbonrow {
    margin: 0 calc(-1 * var(--space-5)) 0;
  }
  /* The rail is as tall as the photograph, so its content sits in the
     middle of that height and the way out sits at the foot of it. */
  .ww-wotw__mid {
    flex: 1;
    justify-content: center;
  }
}

/* === wompworld/03-womp.css === */
/* ---- permalink detail ---- */
.ww-detail__imgwrap {
  background: var(--surface-2);
  display: flex;
  justify-content: center;
}
/* The plate behind a womp while it loads: the field the photo will land in,
   with a camera at its centre. It sits on the image element rather than the
   wrapper, so the photo covers it exactly when it paints. The grey is picked
   to read on surface-2 in both themes. */
/* The cap on a tall photo is a cap on its WIDTH, worked out from its own
   proportions and handed over as --tall by the handler that knows them.
   Capping the height instead squashes the picture: the width attribute is
   still holding the box at its full size, and only one of the two gives. */
.ww-detail__imgwrap img {
  display: block;
  max-width: min(100%, var(--tall, 100%));
  height: auto;
  background:
    var(--surface-2)
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='%238a969c' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' opacity='.5'%3E%3Cpath d='M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z'/%3E%3Ccircle cx='12' cy='13' r='3'/%3E%3C/svg%3E")
    center / 44px no-repeat;
}
/* A womp whose size is not on file yet: hold the commonest shape so the
   page does not lurch when the photo lands. The next view has the real
   numbers (the detail handler measures the file in the background) and
   this rule stops applying. */
.ww-detail__imgwrap img:not([width]) {
  width: 640px;
  aspect-ratio: auto 4 / 3;
  object-fit: contain;
}
.ww-detail__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-4);
}
@media (min-width: 720px) {
  .ww-detail__body {
    padding: var(--space-5);
  }
}
.ww-detail__content {
  /* the column already spaces its children; the browser's own paragraph
     margin was stacking on top of that and leaving a hole under the photo */
  margin: 0;
  font-size: var(--text-lg);
  line-height: var(--leading-relaxed);
  overflow-wrap: anywhere;
}
.ww-detail__meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: var(--space-3);
}
.ww-detail__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  padding-top: var(--space-2);
}
/* comfortable 44px tap targets for the action row */
.ww-detail__actions .ds-btn {
  min-height: 44px;
}

.ww-kv {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ww-kv__k {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-kv__v {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-kv__v a {
  color: var(--ink);
}
.ww-kv__v a:hover {
  color: var(--accent);
  text-decoration: none;
}

/* ---- back to the grid ---- */
.ww-lanebar {
  display: flex;
  align-items: center;
  margin-bottom: var(--space-3);
}
.ww-back {
  /* pull the ghost button's own padding back so its label lines up with
     the panel edge under it, not with a button-shaped indent */
  margin-left: calc(-1 * var(--space-3));
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- the pictures either side, hanging off the frame ---- */
.ww-detail__stage {
  position: relative;
  overflow: hidden;
}
.ww-rail {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 96px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface-2);
  color: var(--ink);
  text-decoration: none;
  /* the sliver that stays in the frame, the rest waits outside it */
  --peek: 30px;
  transition:
    transform 240ms cubic-bezier(0.2, 0, 0, 1),
    box-shadow 240ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-rail--newer {
  left: 0;
  transform: translateX(calc(-100% + var(--peek)));
}
.ww-rail--older {
  right: 0;
  transform: translateX(calc(100% - var(--peek)));
}
.ww-rail:hover,
.ww-rail:focus-visible {
  transform: translateX(0);
  outline: none;
  box-shadow: 0 0 0 1px var(--border-strong);
}
.ww-rail__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* dimmed at rest so the picture on show keeps the eye, full strength
     once the rail is open and it is the thing being offered */
  opacity: 0.45;
  filter: saturate(0.6);
  transition:
    opacity 240ms cubic-bezier(0.2, 0, 0, 1),
    filter 240ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-rail:hover .ww-rail__img,
.ww-rail:focus-visible .ww-rail__img {
  opacity: 1;
  filter: none;
}
/* the chevron rides in the sliver, so it is the part that never leaves */
.ww-rail__edge {
  position: absolute;
  top: 50%;
  translate: 0 -50%;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  background: var(--surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  color: var(--ink);
}
.ww-rail--newer .ww-rail__edge {
  right: 2px;
}
.ww-rail--older .ww-rail__edge {
  left: 2px;
}
.ww-rail__label {
  position: absolute;
  bottom: var(--space-3);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  padding: 3px var(--space-2);
  background: var(--surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  opacity: 0;
  transition: opacity 200ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-rail:hover .ww-rail__label,
.ww-rail:focus-visible .ww-rail__label {
  opacity: 1;
}
@media (min-width: 720px) {
  .ww-rail {
    width: 148px;
    --peek: 40px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ww-rail,
  .ww-rail__img,
  .ww-rail__label {
    transition: none;
  }
}

/* ---- who took it ---- */
/* The label sits over a bordered, padded panel rather than bare text or a
   strip of tiles, so the default label-to-content gap reads as glued to
   the border. Its own margin steps back to let the section supply a
   block-level gap instead. */
.ww-section--photog {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.ww-section--photog > .ww-seclabel {
  margin-bottom: 0;
}
.ww-photog {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}
.ww-photog__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  min-width: 0;
}
/* With a costume to show, the figure takes a column of its own beside the
   writing. Below that it goes back on top, where a phone can see it. */
@media (min-width: 720px) {
  .ww-photog--look {
    display: grid;
    grid-template-columns: 208px minmax(0, 1fr);
    gap: var(--space-5);
    align-items: start;
  }
}
.ww-photog__look {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.ww-photog__stage {
  position: relative;
  aspect-ratio: 3 / 4;
  background: radial-gradient(
    circle at 50% 34%,
    var(--surface-2),
    var(--surface) 72%
  );
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}
.ww-photog__canvas {
  position: absolute;
  inset: 0;
}
.ww-photog__canvas canvas {
  display: block;
  width: 100%;
  height: 100%;
  /* the figure is the subject and the room around it is not, so a drag
     turns the view rather than scrolling the page off it */
  touch-action: none;
}
.ww-photog__wait {
  position: absolute;
  inset: auto 0 var(--space-3);
  margin: 0;
  text-align: center;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-photog__wait:empty {
  display: none;
}
.ww-photog__lookgo {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  line-height: var(--leading-normal);
  color: var(--muted);
  text-decoration: none;
}
.ww-photog__lookgo b {
  font-weight: 500;
  color: var(--ink);
}
.ww-photog__lookgo:hover {
  color: var(--accent);
}
.ww-photog__id {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  color: inherit;
  text-decoration: none;
}
.ww-photog__id > *:first-child {
  flex: none;
}
.ww-photog__idtext {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
.ww-photog__name {
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
  line-height: var(--leading-tight);
  overflow-wrap: anywhere;
}
.ww-photog__id:hover .ww-photog__name {
  color: var(--accent);
}
.ww-photog__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.ww-photog__bio {
  margin: 0;
  max-width: var(--measure);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--ink-2);
  overflow-wrap: anywhere;
}
.ww-photog__stats {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted);
}
.ww-photog__stats b {
  font-weight: 500;
  color: var(--ink);
}

/* ---- the row of more pictures ---- */
.ww-strip {
  display: flex;
  gap: var(--space-4);
  overflow-x: auto;
  scroll-snap-type: x proximity;
  /* the scroller bleeds to the page edges so the last tile reads as cut
     off rather than as the end of the row */
  margin-inline: calc(-1 * var(--gutter, 0px));
  padding-inline: var(--gutter, 0px);
  /* land a snapped tile on the content edge, not under the fade */
  scroll-padding-inline: var(--gutter, 0px);
  /* an x scroller clips on y too, so the hover lift below needs its own
     room. Pulled back out again by the margin, to keep the row where the
     label put it. */
  padding-block: 3px;
  margin-block: -3px;
  /* No bar. It was a line of chrome sitting under every picture whether
     or not anyone was scrolling, and the fade says the same thing: a tile
     dissolving into the page edge is a tile with more behind it. */
  scrollbar-width: none;
  mask-image: linear-gradient(
    to right,
    transparent 0,
    #000 var(--gutter, 0px),
    #000 calc(100% - var(--gutter, 0px)),
    transparent 100%
  );
}
.ww-strip::-webkit-scrollbar {
  display: none;
}
.ww-strip__item {
  flex: none;
  width: clamp(220px, 26vw, 300px);
  aspect-ratio: 4 / 3;
  scroll-snap-align: start;
  background: var(--surface-2);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  transition:
    border-color 150ms cubic-bezier(0.2, 0, 0, 1),
    transform 150ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-strip__item img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ww-strip__item:hover {
  border-color: var(--border-strong);
}
@media (prefers-reduced-motion: no-preference) {
  .ww-strip__item:hover {
    transform: translateY(-2px);
  }
}

/* === wompworld/04-entity.css === */
/* ---- profile / parcel page head ---- */
.ww-entity {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
/* The column gap spaces everything in here, so the header does not bring
   its page-level margin along on top of it: that left a name floating 55px
   above the bio describing it while the rows below sat 16px apart. */
.ww-entity > .ww-pagehead,
.ww-entity > .ww-pagehead__sub {
  margin: 0;
}
.ww-entity__stats {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-3);
}
@media (min-width: 720px) {
  .ww-entity__stats {
    grid-template-columns: repeat(4, 1fr);
  }
}

/* ---- earned badges row ---- */
.ww-badges {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}
.ww-badge {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px var(--space-3);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  white-space: nowrap;
}
.ww-badge svg {
  color: var(--accent);
  flex: none;
}

/* === wompworld/05-boards.css === */
/* ---- leaderboards ---- */
.ww-tabs {
  display: flex;
  gap: 2px;
  overflow-x: auto;
  scrollbar-width: none;
  margin-bottom: var(--space-4);
  border-bottom: var(--border-w) solid var(--border);
}
.ww-tabs::-webkit-scrollbar {
  display: none;
}
.ww-tab {
  position: relative;
  color: var(--muted);
  padding: 10px 12px 12px;
  font-size: var(--text-sm);
  font-weight: 500;
  white-space: nowrap;
  letter-spacing: var(--tracking-snug);
  transition: color 0.15s ease;
}
.ww-tab::after {
  content: "";
  position: absolute;
  left: 9px;
  right: 9px;
  bottom: -1px;
  height: 2px;
  background: var(--accent);
  border-radius: 2px 2px 0 0;
  transform: scaleX(0);
  transform-origin: center;
  transition: transform 0.22s cubic-bezier(0.2, 0, 0, 1);
}
.ww-tab:hover,
.ww-tab.active {
  color: var(--ink);
  text-decoration: none;
}
.ww-tab.active::after {
  transform: scaleX(1);
}

.ww-lb {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
}
/* The two boards in a pair rank different things, so a quiet week can put
   six photographers next to ten parcels. Stretching the panels keeps the
   leftover inside the shorter board, where it reads as a board with room
   left in it, rather than as a hole in the page beside a full one. */
@media (min-width: 860px) {
  .ww-lb {
    grid-template-columns: 1fr 1fr;
    align-items: stretch;
  }
}
.ww-board__note {
  padding: var(--space-3) var(--space-4);
  border-top: var(--border-w) solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted);
}
.ww-rank {
  color: var(--muted);
  width: 2.5em;
}

/* === wompworld/06-map.css === */
/* ---- womp map ---- */
.ww-map__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: var(--border-w) solid var(--border);
}
.ww-map__scroll {
  position: relative;
  overflow: hidden;
  background: radial-gradient(
    ellipse at 50% 40%,
    var(--surface) 0%,
    var(--surface-2) 100%
  );
  touch-action: none;
  -webkit-user-select: none;
  user-select: none;
}
.ww-map__inner {
  position: absolute;
  inset: 0;
}
.ww-map__inner canvas {
  display: block;
  position: absolute;
  left: 0;
  top: 0;
}
/* The chip that names a column. It follows the buttons, so it is dark in
   light mode and light in dark mode, and a hot column turns it orange to
   match the column it is standing on. */
.ww-map__tip {
  position: absolute;
  transform: translate(-50%, -100%);
  background: var(--btn-bg);
  color: var(--btn-fg);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  white-space: nowrap;
  pointer-events: none;
  z-index: 3;
  /* A long parcel name and its count can be wider than a phone. map.js keeps
     the chip inside the viewport, which it can only do if the chip fits in
     the first place, so the name gives way before the edge does. */
  max-width: calc(100% - 8px);
  overflow: hidden;
  text-overflow: ellipsis;
}
/* A column against the top of the viewport has no room above it, so the chip
   hangs under the rooftop instead. map.js decides which, since only it knows
   where the roof landed. */
.ww-map__tip--below {
  transform: translate(-50%, 0);
}
/* Deep orange rather than the column's own bright one, because type has to
   sit on this and white needs the fill to be dark. See --warm-deep. */
.ww-map__tip--warm {
  background: var(--warm-deep);
  color: var(--on-warm-deep);
}
/* A hairline ring, not a background swap, so it reads on top of either
   the plain or the warm chip: a click here goes to that parcel. */
.ww-map__tip--linked {
  outline: var(--border-w) solid var(--accent);
  outline-offset: 1px;
}

/* Table rows light up with a brand wash on hover so browsing the boards
   feels interactive; the inherited surface-2 hover was nearly invisible.
   Rows containing a link are whole-row click targets (client.js). */
.ds-table tbody tr:hover td {
  background: var(--accent-4);
}
.ds-table tbody tr:has(a[href]) {
  cursor: pointer;
}
.ds-table tbody tr {
  transition: background-color 120ms ease;
}
@media (prefers-reduced-motion: reduce) {
  .ds-table tbody tr {
    transition: none;
  }
}

/* profile images and land thumbnails. The face is the one round thing in a
   design of sharp corners, and it earns the exception: at 22 pixels beside
   a name, a circle reads as a person where a square reads as a tile. */
.ww-pfp {
  display: inline-flex;
  flex: none;
  border-radius: 50%;
  border: var(--border-w) solid var(--border-strong);
  overflow: hidden;
  object-fit: cover;
  background: var(--surface-2);
  vertical-align: middle;
}
.ww-pfp svg {
  display: block;
  width: 100%;
  height: 100%;
}
/* Table thumbnails carry the site: they are the only picture of a parcel,
   a photographer's latest frame, a wearable. Square 48s cropped the life
   out of womp screenshots, so the default is a 16:9 tile at the size a
   frame actually reads at, and only genuinely square art (wearable
   renders) opts into .ww-thumb--square. */
.ww-thumb {
  display: inline-block;
  width: 160px;
  height: 90px;
  border-radius: var(--radius-sm);
  border: var(--border-w) solid var(--border);
  overflow: hidden;
  object-fit: cover;
  background: var(--surface-2);
  vertical-align: middle;
  flex: none;
}
/* Name cell of an illustrated row: picture on the left, everything that
   names the row (link, badge, 3D button) stacked beside it. Inline flow
   left the text stranded on the picture's baseline once the tiles grew. */
.ww-rowitem {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}
.ww-rowitem__text {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  min-width: 0;
}
/* A second line under the name, for the thing that says whose row this is
   rather than what it measures. Full basis so it wraps onto its own line in
   a container that otherwise flows inline, and quiet enough that the name
   still leads. */
.ww-rowitem__sub {
  flex-basis: 100%;
  font-size: var(--text-2xs);
  color: var(--muted);
}
.ww-thumb--square {
  width: 90px;
}
/* Parcels nobody has womped fall back to the map render OpenSea cached,
   which is line art on paper white and glares in a dark table. Inverting
   flips it to pale streets on a dark plate, the read the map page already
   has; light mode takes the render as it comes. */
:root[data-theme="dark"] .ww-thumb--map {
  filter: invert(1) hue-rotate(180deg) contrast(0.85);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ww-thumb--map {
    filter: invert(1) hue-rotate(180deg) contrast(0.85);
  }
}
/* A row with no picture yet: the identicon stays a small centered mark
   inside the empty tile. Blown up to fill it, the pixel faces shout over
   the rows that do have a womp to show. */
span.ww-thumb {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}
@media (max-width: 720px) {
  .ww-thumb {
    width: 104px;
    height: 59px;
  }
  .ww-thumb--square {
    width: 59px;
  }
  .ww-rowitem {
    gap: var(--space-3);
  }
}
.ww-pagehead--profile {
  align-items: center;
}
.ww-pagehead--profile .ww-pfp {
  align-self: center;
}

/* === wompworld/07-author.css === */
/* ---- author chip with hover card ---- */
.ww-author {
  position: relative;
  display: inline-block;
  max-width: 100%;
}
/* With a face beside it the link becomes a little row, and the name is
   what gives way inside it: a cropped picture of somebody is still a
   picture of them, and half a name is nobody. */
.ww-author__link--face {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  max-width: 100%;
  color: var(--ink-2);
}
.ww-author__link--face:hover {
  color: var(--accent);
  text-decoration: none;
}
.ww-author__link--face .ww-author__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The address pill, which is also the button that copies it. A badge that
   does something says so with the icon and by lighting up under the
   pointer; without script it is still a badge reading a short address,
   which is what it was before. */
.ww-copy {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  cursor: pointer;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
}
.ww-copy:hover {
  border-color: var(--border-strong);
  color: var(--ink);
}
.ww-copy svg {
  opacity: 0.6;
}
.ww-copy:hover svg {
  opacity: 1;
}
.ww-copy[data-copying] {
  border-color: var(--accent);
  color: var(--accent);
}

.ww-author__card {
  position: absolute;
  bottom: calc(100% + 6px);
  left: 0;
  z-index: 20;
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 190px;
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  box-shadow:
    0 1px 2px -1px oklch(0 0 0 / 0.09),
    0 10px 24px -8px oklch(0 0 0 / 0.15);
  opacity: 0;
  visibility: hidden;
  transform: translateY(3px);
  transition:
    opacity 130ms ease,
    transform 130ms ease,
    visibility 130ms;
  pointer-events: none;
  white-space: normal;
}
.ww-author:hover .ww-author__card,
.ww-author:focus-within .ww-author__card {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}
/* Touch devices cannot hover; the hidden cards still occupy layout and
   were the source of sideways scroll on chip rows, so drop them there.
   Tapping the name goes to the profile, which shows everything anyway. */
@media (hover: none) {
  .ww-author__card {
    display: none;
  }
}
.ww-author__name {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-sans);
  font-size: var(--text-ui);
  font-weight: 600;
  color: var(--ink);
}
.ww-author__meta {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.ww-author__addr {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
  .ww-author__card {
    transition: none;
  }
}

/* === wompworld/08-tables.css === */
/* ---- tables, and the chips that ride in them ---- */

/* locate-on-map button in the map's parcel table */
.ww-locate {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-left: var(--space-2);
  padding: 4px;
  border: none;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  vertical-align: middle;
}
.ww-locate:hover {
  color: var(--accent);
}

/* tier chip: accent-tinted, pairs with ds-badge base class */
.ww-badge--tier {
  background: var(--accent-4);
  color: var(--accent);
  border-color: transparent;
}

/* linked chips (neighboring parcels, islands) */
.ww-chiplink {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 6px var(--space-3);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  white-space: nowrap;
}
.ww-chiplink:hover {
  border-color: var(--accent);
  color: var(--accent);
  text-decoration: none;
}
.ww-chiplink .ds-badge {
  margin-left: 2px;
}

/* map pan/zoom affordances */
.ww-map__scroll {
  cursor: grab;
}
.ww-map__scroll.dragging {
  cursor: grabbing;
}
.ww-map__hint {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  padding: var(--space-2) var(--space-4);
  border-top: var(--border-w) solid var(--border);
}

/* Column discipline for every table on the site. Numeric headers align
   right with their values (.ds-num alone loses to the base th rule on
   specificity, which left headers drifting away from their columns).
   Compact columns shrink to their content with the width:1% trick, so
   the name column absorbs the leftover width and prices, buyers, and
   dates sit together at the right edge instead of scattering. */
.ds-table .ww-col--fit {
  width: 1%;
  white-space: nowrap;
}
.ds-table th.ds-num {
  text-align: right;
}
.ds-table .ww-sales__when {
  width: 1%;
  white-space: nowrap;
  text-align: right;
}
.ds-table td.ww-sales__when {
  color: var(--muted);
}
.ww-thumb svg {
  display: block;
  width: 44px;
  height: 44px;
  border-radius: var(--radius-sm);
  opacity: 0.75;
}
@media (max-width: 720px) {
  .ww-thumb svg {
    width: 32px;
    height: 32px;
  }
}
/* A parcel that has no picture anywhere draws its own plot instead. That is
   art for the whole tile rather than a mark floating inside it, so it takes
   the full frame at full strength. */
.ww-thumb--plate svg,
.ww-thumb--plate > svg {
  width: 100%;
  height: 100%;
  border-radius: 0;
  opacity: 1;
}

/* A sale is four short things, and on a page-wide table the name sits at
   one edge with the price, the buyer and the date pinned to the other,
   half a screen of nothing between them. Past the width where that starts
   to hurt, the three of them take a share of the row instead of shrinking
   to their content, so a row reads as one line rather than as two ends. */
@media (min-width: 1000px) {
  .ww-saletable .ww-col--fit {
    width: 17%;
  }
  .ww-saletable .ww-sales__when {
    width: 14%;
  }
}

/* sales tables: the when and buyer columns yield on narrow screens so
   names and prices keep room to breathe */
@media (max-width: 560px) {
  .ds-table .ww-sales__when,
  .ds-table .ww-sales__buyer {
    display: none;
  }
}

/* Two boards side by side. The gap is the between-blocks step, the same one
   the stacked version gets from .ww-section, so pairing them changes where
   they sit and not how far apart they read. Below the breakpoint the grid
   collapses to one column and the pair goes back to stacking, which is why
   the children carry no width of their own. */
.ww-twoup {
  display: grid;
  gap: var(--space-5);
}
@media (min-width: 1120px) {
  .ww-twoup {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: start;
  }
  /* Half a page is not wide enough for four columns to sit apart the way
     the full-width rule above arranges them, so the buyer yields: it is the
     one column of the four a reader is not scanning for, and the name it
     would have shown is a click away on the item's own page. */
  .ww-twoup .ww-sales__buyer {
    display: none;
  }
  .ww-twoup .ww-saletable .ww-col--fit {
    width: 22%;
  }
  .ww-twoup .ww-saletable .ww-sales__when {
    width: 20%;
  }
}

/* === wompworld/09-chrome.css === */
/* ---- infinite scroll / load more ---- */
.ww-more {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-5) 0 var(--space-2);
}
.ww-more .ds-btn {
  min-height: 44px;
  min-width: min(180px, 100%);
}
/* The same row at the end of a detail page, where nothing follows it but
   the footer. The section above already left its own margin, so the full
   pad on top only made the button float in the middle of nowhere. It gets
   the between-blocks step, not the within-block one: it is a fresh action
   (any womp, not this photographer's), not a continuation of the strip
   above it. */
.ww-more--tail {
  padding: var(--space-5) 0 0;
}
/* ---- footer ---- */
.ww-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: space-between;
  align-items: center;
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: var(--border-w) solid var(--border);
  font-size: var(--text-xs);
  /* ink-2, not muted: this sits on the chrome background and muted text
     fails WCAG AA contrast there */
  color: var(--ink-2);
}
.ww-foot a {
  color: var(--ink-2);
  padding-block: 8px;
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.16em;
}
.ww-foot a:hover {
  color: var(--accent);
  text-decoration: none;
}
.ww-foot__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
}

/* === wompworld/10-viewer.css === */
/* ---- 3d viewer ---- */
.ww-viewer {
  overflow: hidden;
}
.ww-viewer__frame {
  position: relative;
  width: 100%;
  /* a fixed responsive height, not aspect-ratio: combined with max-height
     the browser recomputes a narrower width and leaves the panel edge bare */
  height: min(52vh, 440px);
  background: radial-gradient(
    ellipse at 50% 38%,
    var(--surface) 0%,
    var(--surface-2) 100%
  );
}
@media (max-width: 640px) {
  .ww-viewer__frame {
    height: auto;
    aspect-ratio: 4 / 3;
  }
}
/* The stand sizes a model to the frame's height, so a frame as wide as the
   page adds nothing but gradient either side of a hat. Where there is
   something to say about what is standing there, the room splits: stage on
   the left, what it is on the right. */
.ww-viewer__rail {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--space-3);
  padding: var(--space-5);
}
.ww-viewer__name {
  font-size: var(--text-xl);
  font-weight: 600;
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-snug);
  color: var(--ink);
  overflow-wrap: anywhere;
}
/* A label and the thing it names are one group, so they set their own
   tighter gap and the rail keeps the step between blocks. */
.ww-viewer__pair {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
}
.ww-viewer__who {
  font-size: var(--text-ui);
  color: var(--ink);
}
.ww-viewer__hint {
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--ink-2);
}
/* The one thing the rail is really asking for. It sits at the width of its
   own words rather than the width of the rail: stretched edge to edge it
   reads as a section footer, and the rail is a column of short lines with
   one button in the middle of them. */
.ww-viewer__try {
  align-self: start;
  padding: 11px var(--space-4);
}
.ww-viewer__go {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-2);
}
.ww-viewer__go:hover {
  color: var(--accent);
  text-decoration: none;
}
@media (min-width: 860px) {
  .ww-viewer--split {
    display: grid;
    grid-template-columns: minmax(0, 1.4fr) minmax(260px, 1fr);
  }
  .ww-viewer--split .ww-viewer__rail {
    border-left: var(--border-w) solid var(--border);
  }
}
.ww-viewer__stage {
  position: absolute;
  inset: 0;
}
.ww-viewer__stage canvas {
  display: block;
  touch-action: none;
}
.ww-viewer__note {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  pointer-events: none;
}

/* ---- the rack beside the stand ---- */

/* The stand and the rack it draws from are one thing, so the pair sets its
   own step and the section keeps its own. */
.ww-fitting {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Pictures only, and small: this is a tray of things to try rather than a
   third board of numbers to be read against the two below it. A scroller
   where the page is narrow, a shelf where there is room for the lot. */
.ww-rack {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 88px;
  gap: var(--space-2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding-bottom: var(--space-1);
  scrollbar-width: thin;
}
/* Fixed columns would hand a wide page six tiles as big as the cards on the
   shelf above, and 400px of rack under the stand stops being a tray. The
   tiles keep their size instead and the row takes as many as fit, so the lot
   sits in one line on a wide screen and wraps to two on a laptop. */
@media (min-width: 720px) {
  .ww-rack {
    grid-auto-flow: unset;
    grid-auto-columns: unset;
    grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
    overflow-x: visible;
  }
}
.ww-rack__item {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 0 0 var(--space-2);
  border: var(--border-w) solid var(--border);
  background: var(--surface);
  text-align: center;
  cursor: pointer;
  scroll-snap-align: start;
}
.ww-rack__art {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  background: var(--bg);
  border-bottom: var(--border-w) solid var(--border);
}
/* Thumb arrives dressed for a table row, at a fixed width with a border and
   a corner of its own. Here it is the whole top of the tile and the tile
   carries the border, so it gives all three back. */
.ww-rack__art .ww-thumb {
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 0;
  background: transparent;
  object-fit: contain;
}
.ww-rack__art .ww-thumb svg {
  width: 34px;
  height: 34px;
}
.ww-rack__art:has(svg) {
  display: grid;
  place-items: center;
}
/* One line, since the tile is 88px wide and the second line of a name that
   long is rarely the half that tells them apart. The whole name is in the
   title, and in the rail the moment it goes up. */
.ww-rack__name {
  display: block;
  padding: var(--space-2) var(--space-1) 0;
  font-size: var(--text-2xs);
  color: var(--ink-2);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-rack__item:hover,
.ww-rack__item.is-on {
  border-color: var(--accent);
}
.ww-rack__item.is-on .ww-rack__name {
  color: var(--accent);
}
.ww-rack__item:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-4);
}
@media (prefers-reduced-motion: no-preference) {
  .ww-rack__item {
    transition:
      border-color 160ms cubic-bezier(0.2, 0, 0, 1),
      transform 160ms cubic-bezier(0.2, 0, 0, 1);
  }
  .ww-rack__item:hover {
    transform: translateY(-2px);
  }
}

.ww-oslogo {
  width: 16px;
  height: 16px;
  border-radius: 50%;
  display: inline-block;
}

/* === wompworld/11-tiles.css === */
/* ---- home explore tiles ---- */
/* Big image tiles for each destination on the site. The caption sits on
   a fixed dark scrim, so the type is white in both themes and stays
   readable over any womp. Imageless tiles keep the viewer gradient. */
.ww-tiles {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 480px) {
  .ww-tiles {
    grid-template-columns: repeat(2, 1fr);
  }
}
@media (min-width: 860px) {
  .ww-tiles {
    grid-template-columns: repeat(3, 1fr);
  }
}
/* Nine tiles divide into three columns exactly and into two columns with
   one left over. A tile alone at the end of a row reads as a hole beside
   it, so give it the whole row instead, flattened so a full-width one
   does not tower over the rest. */
@media (min-width: 480px) and (max-width: 859px) {
  .ww-tile:nth-child(odd):last-child {
    grid-column: 1 / -1;
    aspect-ratio: 32 / 10;
  }
}
.ww-tile {
  position: relative;
  display: flex;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  background: radial-gradient(
    ellipse at 50% 35%,
    var(--surface) 0%,
    var(--surface-2) 100%
  );
  transition:
    border-color 150ms cubic-bezier(0.2, 0, 0, 1),
    box-shadow 150ms cubic-bezier(0.2, 0, 0, 1),
    transform 150ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-tile:hover {
  text-decoration: none;
  border-color: var(--border-strong);
  box-shadow:
    0 1px 2px -1px oklch(0 0 0 / 0.09),
    0 10px 24px -8px oklch(0 0 0 / 0.15);
}
:root[data-theme="dark"] .ww-tile:hover {
  box-shadow:
    0 1px 2px -1px oklch(0 0 0 / 0.45),
    0 14px 30px -10px oklch(0 0 0 / 0.55);
}
@media (prefers-reduced-motion: no-preference) {
  .ww-tile:hover {
    transform: translateY(-2px);
  }
  .ww-tile:active {
    transform: translateY(-1px);
    transition-duration: 90ms;
  }
}
.ww-tile:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-4);
}
.ww-tile__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* wearable renders are product shots; crop would behead them */
.ww-tile__img--contain {
  object-fit: contain;
  padding: var(--space-4) var(--space-4) 52px;
}
.ww-tile__scrim {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    oklch(0.16 0.02 240 / 0.84) 0%,
    oklch(0.16 0.02 240 / 0.38) 42%,
    oklch(0.16 0.02 240 / 0) 68%
  );
}
.ww-tile__body {
  position: relative;
  margin-top: auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-width: 0;
  padding: var(--space-3) var(--space-4);
}
.ww-tile__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 30px;
  height: 30px;
  border: 1px solid oklch(1 0 0 / 0.3);
  border-radius: var(--radius-sm);
  background: oklch(0.2 0.02 240 / 0.45);
  color: #fff;
}
.ww-tile__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ww-tile__title {
  color: #fff;
  font-weight: 600;
  font-size: var(--text-ui);
  letter-spacing: var(--tracking-snug);
}
.ww-tile__sub {
  color: oklch(1 0 0 / 0.82);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  font-variant-numeric: tabular-nums;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-tile__go {
  margin-left: auto;
  flex: none;
  color: oklch(1 0 0 / 0.7);
}
.ww-tile:hover .ww-tile__go {
  color: #fff;
}

/* section label row with a trailing link */
.ww-secrow {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}
.ww-secrow .ww-seclabel {
  margin-bottom: 0;
}
.ww-secrow__link {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--ink-2);
  text-decoration: underline;
  text-decoration-thickness: from-font;
  text-underline-offset: 0.16em;
}
.ww-secrow__link:hover {
  color: var(--accent);
  text-decoration: none;
}

/* === wompworld/12-about.css === */
/* ---- about page lead + explore grid ---- */
/* The lead is a two column row that has to end level on both sides, so the
   prose column carries the figures as its footer: the text sets the natural
   height, the figures take up whatever slack is left, and the womp beside it
   stretches to the same line. ds-prose sits on an inner div rather than the
   panel, otherwise the reading measure caps the panel itself and it stops
   short of its own column. */
.ww-about-lead {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 860px) {
  .ww-about-lead {
    grid-template-columns: minmax(0, 1.618fr) minmax(300px, 1fr);
    align-items: stretch;
  }
}
.ww-about-lead__text {
  display: flex;
  flex-direction: column;
  gap: var(--space-5);
}
.ww-about-lead__text .ds-prose > p:first-child {
  margin-top: 0;
}
.ww-about-lead__text .ds-prose > p:last-child {
  margin-bottom: 0;
}
/* without a featured womp the panel keeps the whole row */
.ww-about-lead__text:only-child {
  grid-column: 1 / -1;
}
.ww-about-lead__womp {
  display: flex;
}
.ww-about-lead__womp .ww-anim {
  display: flex;
  flex: 1;
  min-width: 0;
}
.ww-about-lead__womp .ww-card {
  flex: 1;
}
/* when the prose is the taller side, the photo takes the slack rather than
   the caption block. the image link keeps its 4:3 height as the starting
   size and grows from there; the image is object-fit cover, so it crops
   instead of stretching. */
.ww-about-lead__womp .ww-card > .ww-card-link {
  display: block;
  flex: 1 1 auto;
  min-height: 0;
}
/* width and height both resolved keeps the 4:3 ratio from being reapplied
   to the width once the link has grown taller than its starting size */
.ww-about-lead__womp .ww-card__imgwrap {
  width: 100%;
  height: 100%;
}
.ww-about-lead__womp .ww-card__body {
  flex: none;
}

/* the site's numbers, as the foot of the lead panel */
.ww-figures {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-3);
  margin: auto 0 0;
  padding-top: var(--space-4);
  border-top: var(--border-w) solid var(--border);
}
@media (max-width: 420px) {
  .ww-figures {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-4) var(--space-3);
  }
}
.ww-figures__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ww-figures__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-figures__value,
.ww-figures__sub {
  margin: 0;
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.ww-figures__value {
  font-size: var(--text-2xl);
  font-weight: 500;
  letter-spacing: var(--tracking-snug);
  color: var(--ink);
}
.ww-figures__sub {
  font-size: var(--text-xs);
  color: var(--ink-2);
}

/* ---- about page: how it runs + sources ---- */
.ww-about-runs {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
@media (min-width: 860px) {
  .ww-about-runs {
    /* even halves: at the container's full width each column lands just
       inside the reading measure, so the prose fills its panel */
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: stretch;
  }
}
.ww-about-runs__text .ds-prose > h2 {
  margin-bottom: var(--space-3);
}
.ww-about-runs__text .ds-prose > p:last-child {
  margin-bottom: 0;
}
.ww-about-runs__side {
  display: flex;
  flex-direction: column;
}
.ww-sources {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}
.ww-sources li + li {
  border-top: var(--border-w) solid var(--border);
}
.ww-sources__row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) 0;
  color: var(--ink);
}
.ww-sources__row:hover {
  text-decoration: none;
  color: var(--accent);
}
.ww-sources__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 30px;
  height: 30px;
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface-2);
  color: var(--accent);
}
.ww-sources__text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.ww-sources__name {
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: var(--tracking-snug);
}
.ww-sources__blurb {
  font-size: var(--text-sm);
  color: var(--ink-2);
}
.ww-sources__go {
  margin-left: auto;
  display: inline-flex;
  color: var(--muted);
}
.ww-sources__row:hover .ww-sources__go {
  color: var(--accent);
}
.ww-sources__note {
  margin: auto 0 0;
  padding-top: var(--space-4);
  font-size: var(--text-sm);
  color: var(--ink-2);
  line-height: var(--leading-normal);
}

.ww-explore {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-3);
}
/* six tiles, so two and three across both come out even; auto-fill left a
   ragged last row */
@media (min-width: 520px) {
  .ww-explore {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 900px) {
  .ww-explore {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
.ww-explore__card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-4);
  background: var(--surface);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  color: var(--ink);
  transition:
    border-color 150ms cubic-bezier(0.2, 0, 0, 1),
    box-shadow 150ms cubic-bezier(0.2, 0, 0, 1),
    transform 150ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-explore__card:hover {
  text-decoration: none;
  color: var(--ink);
  border-color: var(--border-strong);
  box-shadow:
    0 1px 2px -1px oklch(0 0 0 / 0.09),
    0 10px 24px -8px oklch(0 0 0 / 0.15);
}
@media (prefers-reduced-motion: no-preference) {
  .ww-explore__card:hover {
    transform: translateY(-2px);
  }
}
.ww-explore__card:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-4);
}
.ww-explore__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  color: var(--accent);
  background: var(--surface-2);
}
.ww-explore__title {
  font-weight: 600;
  font-size: var(--text-base);
  letter-spacing: var(--tracking-snug);
}
.ww-explore__blurb {
  font-size: var(--text-sm);
  color: var(--ink-2);
  line-height: var(--leading-normal);
}
.ww-explore__go {
  position: absolute;
  top: var(--space-4);
  right: var(--space-4);
  color: var(--muted);
}
.ww-explore__card:hover .ww-explore__go {
  color: var(--accent);
}

/* === wompworld/13-empty.css === */
/* ---- empty state ---- */
.ww-empty {
  padding: var(--space-6) var(--space-4);
  text-align: center;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
}

/* === wompworld/14-header.css === */
/* ---- header chrome ---- */

/* header logo mark */
.ww-logo {
  color: inherit;
}
.ww-logo:hover {
  text-decoration: none;
}
.ww-logo__mark {
  display: inline-flex;
}
.ww-logo__mark svg {
  display: block;
}
.ww-logo span {
  letter-spacing: var(--tracking-tight);
}
/* Header follow CTA: the X glyph and the ask in one pill the height of
   the theme toggle. Accent tint rather than a solid fill so it reads as
   the one action in the chrome without shouting. */
.ww-follow {
  display: inline-flex;
  align-items: center;
  /* a word space, not a control gap: the glyph is the last word */
  gap: var(--space-1);
  height: 34px;
  padding-inline: var(--space-3);
  border: var(--border-w) solid var(--accent-3);
  border-radius: var(--radius);
  background: var(--accent-4);
  color: var(--accent);
  font-size: var(--text-ui);
  font-weight: 500;
  line-height: 1;
  white-space: nowrap;
}
.ww-follow:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--on-accent);
  text-decoration: none;
}
.ww-follow__x {
  flex: none;
}
/* the middle of the sentence is the first thing to go when the header
   gets tight, which is any window narrower than the full content frame */
@media (max-width: 1179px) {
  .ww-follow__long {
    display: none;
  }
}
@media (max-width: 1023px) {
  .ww-follow {
    height: 40px;
  }
}
/* on the narrowest phones the ask costs a whole header row, so the pill
   becomes the glyph alone and keeps its accent tint and its aria-label */
@media (max-width: 359px) {
  .ww-follow {
    width: 40px;
    padding-inline: 0;
    justify-content: center;
  }
  .ww-follow__text {
    display: none;
  }
}

/* Square icon control in the header chrome, sized to match the theme
   toggle beside it. The label lives in a tooltip so the row stays two
   glyphs wide next to the follow pill. */
.ww-iconbtn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
}
.ww-iconbtn:hover {
  border-color: var(--accent);
  color: var(--accent);
  text-decoration: none;
}
.ww-iconbtn:active svg {
  transform: scale(0.92);
}
.ww-iconbtn svg {
  transition: transform 0.18s cubic-bezier(0.2, 0, 0, 1);
}
/* tooltip: hairline card under the control, pointing at nothing, no
   arrow. It waits a beat so a passing cursor never triggers it. */
.ww-iconbtn[data-tip]::after {
  content: attr(data-tip);
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  padding: 4px var(--space-2);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  white-space: nowrap;
  opacity: 0;
  transform: translateY(-3px);
  pointer-events: none;
  transition:
    opacity 0.14s ease 0.35s,
    transform 0.14s ease 0.35s;
}
.ww-iconbtn[data-tip]:hover::after,
.ww-iconbtn[data-tip]:focus-visible::after {
  opacity: 1;
  transform: translateY(0);
}
/* a tooltip that needs a tap to appear is just a second thing to hit */
@media (hover: none) {
  .ww-iconbtn[data-tip]::after {
    display: none;
  }
}
@media (max-width: 1023px) {
  .ww-iconbtn {
    width: 40px;
    height: 40px;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ww-iconbtn svg,
  .ww-iconbtn[data-tip]::after {
    transition: none;
  }
}

/* === wompworld/15-map-viewport.css === */
/* ---- womp map viewport (map overhaul) ---- */
/* The map canvas is a fixed camera viewport once map.js adds is-live;
   without JavaScript the holder stays collapsed and the noscript note
   takes over. The zoom controls and the transient zoom readout float
   inside the viewport corners. */
.ww-map__scroll.is-live {
  height: min(64vh, 620px);
}
@media (max-width: 640px) {
  .ww-map__scroll.is-live {
    height: clamp(280px, 46vh, 430px);
  }
}
.ww-map__scroll:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.ww-map__ctl {
  position: absolute;
  right: var(--space-3);
  bottom: var(--space-3);
  display: flex;
  flex-direction: column;
  border: var(--border-w) solid var(--border-strong);
  background: var(--surface);
  z-index: 4;
}
.ww-map__ctlbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 0;
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: 1;
  cursor: pointer;
}
.ww-map__ctlbtn + .ww-map__ctlbtn {
  border-top: var(--border-w) solid var(--border);
}
.ww-map__ctlbtn:hover {
  color: var(--accent);
  background: var(--surface-2);
}
.ww-map__ctlbtn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.ww-map__ctlbtn--fit {
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}
@media (pointer: coarse) {
  .ww-map__ctlbtn {
    width: 44px;
    height: 44px;
  }
}
/* Two transient readouts over the canvas, told apart only by where they sit
   and what frames them: the zoom level in a corner, and the wheel nudge in
   the middle. Both fade in when map.js asks and go again on their own, and
   neither ever takes the pointer. */
.ww-map__level,
.ww-map__nudge {
  position: absolute;
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  opacity: 0;
  transition: opacity 180ms ease;
  pointer-events: none;
  z-index: 4;
}
.ww-map__level {
  left: var(--space-3);
  bottom: var(--space-3);
  padding: 4px 8px;
  border: var(--border-w) solid var(--border);
  font-variant-numeric: tabular-nums;
}
/* Said in the middle of the map, once, when a plain wheel has just been let
   through to the page. Anyone who meant to zoom reads it and knows how. */
.ww-map__nudge {
  left: 50%;
  top: 50%;
  translate: -50% -50%;
  padding: var(--space-2) var(--space-3);
  border: var(--border-w) solid var(--border-strong);
  text-align: center;
  z-index: 5;
}
.ww-map__level.show,
.ww-map__nudge.show {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  .ww-map__level,
  .ww-map__nudge {
    transition: none;
  }
}

/* ---- the year scrubber and the view toggle ---- */
/* Both are built by map.js and only when the data carries what they read,
   so a row is absent rather than empty on a map that has none of it. They
   share one set of rules because they are one control twice over: a label
   and a strip of segmented chips. Buttons rather than a range input or a
   select: a handful of years is a list, and a list keeps the keyboard and
   the design system's hairlines without restyling a native control. */
.ww-map__years,
.ww-map__views {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-4);
  border-bottom: var(--border-w) solid var(--border);
  overflow-x: auto;
}
/* The play toggle and the year chips are one small mono control at heart;
   what differs is the frame around each and which of them can look pressed. */
.ww-map__play,
.ww-map__year,
.ww-map__view {
  padding: 4px 9px;
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  line-height: 1;
  cursor: pointer;
}
.ww-map__play {
  flex: none;
  border: var(--border-w) solid var(--border-strong);
  color: var(--ink-2);
  text-transform: uppercase;
}
.ww-map__yearlist,
.ww-map__viewlist {
  display: flex;
  flex: none;
  border: var(--border-w) solid var(--border);
}
/* The chips share the list's frame, so each carries only the hairline
   between it and the next. */
.ww-map__year,
.ww-map__view {
  border: 0;
  border-right: var(--border-w) solid var(--border);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}
.ww-map__year:last-child,
.ww-map__view:last-child {
  border-right: 0;
}
.ww-map__play:hover {
  color: var(--accent);
  background: var(--surface-2);
}
.ww-map__year:hover,
.ww-map__view:hover {
  color: var(--accent);
}
.ww-map__year[aria-pressed="true"],
.ww-map__view[aria-pressed="true"] {
  background: var(--accent-4);
  color: var(--ink);
}
.ww-map__play:focus-visible,
.ww-map__year:focus-visible,
.ww-map__view:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* === wompworld/16-featured-looks.css === */
/* ---- featured looks, above the stage ---- */
.ww-featured {
  margin-bottom: var(--space-4);
}
.ww-featured__head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-3);
}
.ww-featured__title {
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--ink);
}
.ww-featured__sub {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--ink-2);
}
/* A row that runs off the side rather than wrapping to a second line:
   the stage is what the page is for and it should stay in view. */
.ww-featured__row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 116px;
  gap: var(--space-2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding-bottom: var(--space-1);
  scrollbar-width: thin;
}
@media (min-width: 720px) {
  .ww-featured__row {
    /* wide enough for the whole set, so it stops being a scroller and
       starts being a shelf */
    grid-auto-flow: unset;
    grid-auto-columns: unset;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    overflow-x: visible;
  }
}
@media (min-width: 1024px) {
  .ww-featured__row {
    grid-template-columns: repeat(8, minmax(0, 1fr));
    gap: var(--space-3);
  }
}
.ww-featured__card {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 0 0 var(--space-2);
  border: var(--border-w) solid var(--border);
  background: var(--surface);
  text-align: center;
  cursor: pointer;
  scroll-snap-align: start;
}
.ww-featured__art {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  /* the figure is cut out against nothing, so it wants a floor of its own
     to stand on rather than the card's flat surface */
  background:
    radial-gradient(
      ellipse 60% 22% at 50% 88%,
      var(--accent-4) 0%,
      transparent 70%
    ),
    var(--bg);
  border-bottom: var(--border-w) solid var(--border);
}
.ww-featured__art img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.ww-featured__name {
  display: block;
  padding: var(--space-2) var(--space-1) 0;
  font-size: var(--text-xs);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-featured__note {
  display: block;
  padding: 0 var(--space-1);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  line-height: 1.25;
  color: var(--muted);
  text-wrap: balance;
}
.ww-featured__card:hover,
.ww-featured__card.is-on {
  border-color: var(--accent);
}
.ww-featured__card.is-on .ww-featured__name {
  color: var(--accent);
}
.ww-featured__card:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-4);
}
@media (prefers-reduced-motion: no-preference) {
  .ww-featured__card {
    transition:
      border-color 160ms cubic-bezier(0.2, 0, 0, 1),
      transform 160ms cubic-bezier(0.2, 0, 0, 1);
  }
  .ww-featured__card:hover {
    transform: translateY(-2px);
  }
}

/* === wompworld/17-dr-stage.css === */
/* ---- dressing room ---- */
.ww-dr {
  display: grid;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
@media (min-width: 960px) {
  .ww-dr {
    grid-template-columns: minmax(0, 1fr) 348px;
    align-items: start;
  }
}

.ww-dr__stagewrap {
  position: relative;
  /* a column so that on a narrow screen, where the bar and the hint drop
     out of their overlay, they still land above and below the stage
     rather than in the source order the overlay never cared about */
  display: flex;
  flex-direction: column;
  border: var(--border-w) solid var(--border);
  background: var(--surface);
  overflow: hidden;
}
.ww-dr__stage {
  /* svh keeps the stage off the mobile browser chrome, which otherwise
     pushes the panel below the fold on first paint */
  height: min(58svh, 560px);
  background: radial-gradient(
    ellipse at 50% 30%,
    var(--surface) 0%,
    var(--surface-2) 70%,
    var(--chrome) 100%
  );
}
@media (min-width: 960px) {
  .ww-dr__stage {
    height: min(74vh, 700px);
  }
}
/* Nothing stands in for the avatar while it loads, so the stage is empty
   until it lands and the figure fades up out of the empty room. */
.ww-dr__stage canvas {
  display: block;
  touch-action: none;
  opacity: 0;
  transition: opacity 420ms ease;
}
.ww-dr[data-dr-ready] .ww-dr__stage canvas {
  opacity: 1;
}
.ww-dr__status {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  pointer-events: none;
}
.ww-dr__status:empty {
  display: none;
}
/* A slow breath under the waiting line, so a long fetch reads as working
   rather than as stuck. */
@media (prefers-reduced-motion: no-preference) {
  .ww-dr__status[data-dr-wait] {
    animation: ww-dr-wait 1800ms ease-in-out infinite;
  }
}
@keyframes ww-dr-wait {
  0%,
  100% {
    opacity: 0.5;
  }
  50% {
    opacity: 1;
  }
}
@media (prefers-reduced-motion: reduce) {
  .ww-dr__stage canvas {
    transition: none;
  }
}

/* The bar floats over the top of the stage on a wide screen, where there
   is empty sky either side of the figure's head. On a narrow one it sits
   above the stage instead: the tools wrap onto a second row there, and a
   two-row overlay would cover the head it is floating over. */
.ww-dr__bar {
  order: -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-2) var(--space-3);
  background: color-mix(in oklch, var(--surface) 86%, transparent);
  border-bottom: var(--border-w) solid var(--border);
}
@media (min-width: 700px) {
  .ww-dr__bar {
    position: absolute;
    top: 0;
    left: 0;
    right: 0;
  }
}
.ww-dr__who {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-dr__tools {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  margin-left: auto;
}
/* One caption, then the buttons it covers, welded into a segment. Five
   loose buttons in a row read as five unrelated choices; this way the
   pair that changes the camera and the pair that hands you a file are
   two things to scan instead of four. */
.ww-dr__group {
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
.ww-dr__grouplabel {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-dr__tool {
  padding: 6px 10px;
  min-height: 32px;
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  cursor: pointer;
}
.ww-dr__tool:hover,
.ww-dr__tool.is-on {
  border-color: var(--accent);
  color: var(--accent);
  /* lit border over the neighbour's, not under it */
  position: relative;
  z-index: 1;
}
.ww-dr__tool[disabled] {
  color: var(--muted);
  cursor: progress;
}
/* Packing a file takes a second or two, and the button says so where it
   stands: dimmed, breathing, the same width as before. Swapping the word
   for a longer one used to shove its neighbours sideways, which moved the
   thing you had just pressed out from under the pointer. */
.ww-dr__tool[aria-busy="true"] {
  color: var(--muted);
}
@media (prefers-reduced-motion: no-preference) {
  .ww-dr__tool[aria-busy="true"] {
    animation: ww-dr-packing 1.1s ease-in-out infinite;
  }
}
@keyframes ww-dr-packing {
  50% {
    opacity: 0.45;
  }
}
/* An export that fails says so on the button that failed, for the couple of
   seconds share.js holds the class on it. Without a rule here the button
   just re-enabled and looked exactly like one that had worked, so a GLB the
   browser could not write went out as silence. Red, because this is the
   danger state the palette reserves it for, and staying the same size for
   the same reason the busy state does. */
.ww-dr__tool.is-failed {
  border-color: var(--danger);
  background: var(--danger-bg);
  color: var(--danger);
  position: relative;
  z-index: 1;
}
.ww-dr__tool--go {
  border-color: var(--accent);
  color: var(--accent);
}
.ww-dr__tool--go:hover {
  background: var(--accent);
  color: var(--on-accent);
}
.ww-dr__seg {
  display: flex;
}
.ww-dr__seg .ww-dr__tool + .ww-dr__tool {
  /* the pair shares one hairline rather than stacking two */
  margin-left: calc(var(--border-w) * -1);
}
.ww-dr__seg .ww-dr__tool:not(:first-child) {
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}
.ww-dr__seg .ww-dr__tool:not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}
.ww-dr__stagewrap .ww-map__hint {
  /* the paragraph's own margin would leave a band of dead panel below it */
  margin: 0;
}
@media (min-width: 700px) {
  .ww-dr__stagewrap .ww-map__hint {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
  }
}
/* The stage looks like a picture until you find out it turns, so the
   line that says so is worth reading from across the room. */
.ww-dr__hint {
  font-size: var(--text-sm);
  color: var(--ink-2);
  padding: var(--space-3) var(--space-4);
  background: color-mix(in oklch, var(--surface) 86%, transparent);
}
.ww-dr__hintkey {
  color: var(--accent);
}
.ww-dr__hintdot {
  color: var(--border-strong);
}
/* Shift belongs to a keyboard, and the line is long enough already on a
   phone, which has neither the key nor the room for it. */
.ww-dr__hintwide {
  display: none;
}
@media (min-width: 960px) {
  .ww-dr__hintwide {
    display: inline;
  }
}

/* === wompworld/18-dr-panel.css === */
/* ---- the side panel ---- */
.ww-dr__panel {
  display: flex;
  flex-direction: column;
  border: var(--border-w) solid var(--border);
  background: var(--surface);
  min-height: 0;
}
@media (min-width: 960px) {
  .ww-dr__panel {
    position: sticky;
    top: var(--space-4);
    max-height: min(74vh, 700px);
  }
}
/* clear of the sticky header, which only sticks at this width */
@media (min-width: 1024px) {
  .ww-dr__panel {
    top: calc(var(--header-h) + var(--space-4));
    max-height: min(74vh, 700px);
  }
}
.ww-dr__tabs {
  display: flex;
  border-bottom: var(--border-w) solid var(--border);
}
.ww-dr__tab {
  flex: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-2);
  min-height: 44px;
  border: 0;
  border-bottom: 2px solid transparent;
  background: transparent;
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  cursor: pointer;
}
.ww-dr__tab.is-on {
  color: var(--ink);
  border-bottom-color: var(--accent);
}
.ww-dr__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3);
  overflow-y: auto;
  min-height: 0;
}
.ww-dr__body[hidden] {
  display: none;
}
.ww-dr__note,
.ww-dr__empty {
  margin: 0;
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
}
.ww-dr__search {
  width: 100%;
  padding: 9px var(--space-3);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink);
  /* 16px or larger, otherwise iOS zooms the page on focus */
  font-size: 16px;
}
.ww-dr__search:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

/* ---- the closet grid ---- */
.ww-dr__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(88px, 1fr));
  gap: var(--space-2);
}
.ww-dr__item {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 0 0 5px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--ink-2);
  text-align: left;
  cursor: pointer;
}
.ww-dr__item:hover {
  border-color: var(--accent);
  color: var(--accent);
}
.ww-dr__itemart {
  display: block;
  aspect-ratio: 1;
  background: var(--surface-2);
  overflow: hidden;
}
.ww-dr__itemart img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
.ww-dr__itemname {
  padding-inline: 5px;
  font-size: var(--text-2xs);
  line-height: 1.25;
  /* two lines, so a long wearable name never stretches its tile */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* ---- what is on right now ---- */
.ww-dr__worn {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2);
  border: var(--border-w) solid var(--border);
  background: var(--bg);
}
.ww-dr__worn + .ww-dr__worn {
  margin-top: var(--space-2);
}
.ww-dr__worn.is-open {
  border-color: var(--accent);
}
.ww-dr__wornhead {
  border: 0;
  background: transparent;
  padding: 4px;
  min-height: 36px;
  text-align: left;
  cursor: pointer;
  min-width: 0;
}
.ww-dr__worntext {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ww-dr__wornname {
  color: var(--ink);
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-dr__wornslot {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}
/* Where the piece came from: one line, cut off rather than wrapped, since
   a collection called something long would otherwise push the sliders
   under it down the panel. */
.ww-dr__wornfrom {
  font-size: var(--text-2xs);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-dr__wornnum {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.ww-dr__drop {
  padding: 6px 9px;
  min-height: 34px;
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  cursor: pointer;
}
.ww-dr__drop:hover {
  border-color: var(--danger);
  color: var(--danger);
}

/* ---- per-item nudges ---- */
.ww-dr__ctls {
  grid-column: 1 / -1;
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding-top: var(--space-2);
  border-top: var(--border-w) solid var(--border);
}
.ww-dr__ctl {
  display: grid;
  grid-template-columns: 88px 1fr 42px;
  align-items: center;
  gap: var(--space-2);
}
.ww-dr__ctlk {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-dr__ctlv {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  text-align: right;
}
.ww-dr__ctl input[type="range"] {
  width: 100%;
  min-height: 28px;
  accent-color: var(--accent);
}
.ww-dr__ctl--slot {
  grid-template-columns: 88px 1fr;
}
.ww-dr__slot {
  width: 100%;
  padding: 6px var(--space-2);
  border: var(--border-w) solid var(--border-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--text-sm);
}
/* The numbers, folded away. Shut, it is one quiet line under the size
   slider; open, it is the six sliders the handles on the figure would
   otherwise be racing. */
.ww-dr__fine > summary {
  padding: var(--space-1) 0;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
}
.ww-dr__fine > summary:hover,
.ww-dr__fine[open] > summary {
  color: var(--ink-2);
}
.ww-dr__finectls {
  display: flex;
  flex-direction: column;
  gap: 5px;
  padding-top: 5px;
}
.ww-dr__ctlrow {
  display: flex;
  gap: var(--space-2);
  margin-top: var(--space-1);
}
.ww-dr__ctlrow .ds-btn {
  flex: 1;
  padding-block: 7px;
  font-size: var(--text-sm);
}

/* ---- citizens to copy ---- */
.ww-dr__people {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.ww-dr__person {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2);
  min-height: 48px;
  border: var(--border-w) solid var(--border);
  background: var(--bg);
  text-align: left;
  cursor: pointer;
}
.ww-dr__person:hover {
  border-color: var(--accent);
}
.ww-dr__persontext {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}
.ww-dr__personname {
  color: var(--ink);
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-dr__personsub {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- footnotes under the stage ----
   A bordered prose panel here read as a slab of text stranded in an empty
   column, since ds-prose caps the measure and the box shrinks with it. Three
   short notes on a hairline rule sit at the same weight as the rest of the
   page and span its full width. */
.ww-drnotes {
  display: grid;
  gap: var(--space-4);
  margin: 0 0 var(--space-5);
  padding-top: var(--space-4);
  border-top: var(--border-w) solid var(--border);
}
@media (min-width: 720px) {
  .ww-drnotes {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: var(--space-5);
  }
}
.ww-drnotes dt {
  margin-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-drnotes dd {
  margin: 0;
  max-width: 42ch;
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  color: var(--ink-2);
  text-wrap: pretty;
}

/* === wompworld/19-promo.css === */
/* ---- home page push into the dressing room ---- */
.ww-promo {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-4);
  background: linear-gradient(
    135deg,
    color-mix(in oklch, var(--accent-4) 55%, var(--surface)) 0%,
    var(--surface) 62%
  );
}
/* Side by side only once there is room for both. The copy column is capped
   at what reads well and the rack gets the rest, but the rack needs about
   400px before four hangers are worth looking at, so anything narrower
   keeps the stacked layout where the rack has the full width. */
@media (min-width: 940px) {
  .ww-promo {
    grid-template-columns: minmax(300px, 460px) minmax(400px, 1fr);
    align-items: center;
    padding: var(--space-5);
    gap: var(--space-5);
  }
}
.ww-promo__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3);
  max-width: 52ch;
}
.ww-promo__tag {
  display: inline-block;
  /* nudged off the baseline so it centres against the big heading */
  vertical-align: 0.22em;
  margin-left: var(--space-1);
  padding: 3px 7px;
  border: var(--border-w) solid var(--accent);
  color: var(--accent);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
}
.ww-promo__title {
  margin: 0;
  font-size: var(--text-2xl);
  line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight);
}
.ww-promo__sub {
  margin: 0;
  color: var(--ink-2);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
}
.ww-promo__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

/* the rack: the priciest stock, tilted like clothes on hangers. Equal
   columns rather than fixed tiles, so the pictures grow into whatever
   width the band has going spare. Each hanger is a link to the item. */
.ww-promo__rack {
  display: grid;
  /* two up on a phone: four squares across 375px leaves the pictures too
     small to read and the price too long to fit */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-2);
  width: 100%;
}
@media (min-width: 560px) {
  .ww-promo__rack {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
@media (min-width: 720px) {
  .ww-promo__rack {
    gap: var(--space-3);
  }
}
.ww-promo__hanger {
  display: block;
  width: 100%;
  border: var(--border-w) solid var(--border);
  background: var(--surface);
  color: var(--ink-2);
  text-decoration: none;
  overflow: hidden;
}
.ww-promo__hanger-img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
}
.ww-promo__hanger img {
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* what it last went for, which is why it is on the rack at all. A price cut
   off mid-figure tells nobody anything, so a narrow hanger wraps the
   currency onto its own line instead of trailing off in an ellipsis. */
.ww-promo__hanger-price {
  display: block;
  padding: 4px var(--space-1);
  border-top: var(--border-w) solid var(--border);
  font-size: var(--text-2xs);
  line-height: 1.2;
  text-align: center;
  text-wrap: balance;
}
.ww-promo__hanger:hover {
  border-color: var(--accent);
  color: var(--ink-1);
  text-decoration: none;
}
.ww-promo__hanger:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 2px var(--accent-4);
}
@media (prefers-reduced-motion: no-preference) {
  .ww-promo__hanger {
    transition:
      transform 220ms ease,
      border-color 140ms ease,
      color 140ms ease;
  }
  .ww-promo__hanger:nth-child(odd) {
    transform: rotate(-2.5deg);
  }
  .ww-promo__hanger:nth-child(even) {
    transform: rotate(2deg) translateY(3px);
  }
  /* the rack straightens when the band is hovered, and the one under the
     cursor steps forward out of the row */
  .ww-promo:hover .ww-promo__hanger {
    transform: none;
  }
  .ww-promo:hover .ww-promo__hanger:hover {
    transform: translateY(-3px);
  }
  .ww-promo:hover .ww-promo__hanger:active {
    transform: translateY(-1px);
    transition-duration: 90ms;
  }
}

/* === wompworld/20-feed-page.css === */
/* ---- the feed page: activity band, view switch, timeline ---- */

/* ---- pulse band ---- */
.ww-pulse {
  display: grid;
  gap: var(--space-4);
  margin-bottom: var(--space-4);
}
.ww-pulse__figs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3) var(--space-4);
}
@media (min-width: 720px) {
  .ww-pulse__figs {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}
.ww-pulse__fig {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}
.ww-pulse__figlabel {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-pulse__figvalue {
  font-family: var(--font-mono);
  font-size: var(--text-2xl);
  font-weight: 500;
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-tight);
  color: var(--ink);
}
.ww-pulse__figsub {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The chart is one bar a month over four years. Bars share the row equally
   and are allowed to get thin: the shape is the point, not any single
   month, and a month nobody can hit with a finger still has its own title
   for the ones who can. */
.ww-pulse__chartwrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}
.ww-pulse__chart {
  display: flex;
  align-items: flex-end;
  gap: 1px;
  height: 84px;
  padding-top: var(--space-2);
  border-bottom: var(--border-w) solid var(--border);
}
.ww-pulse__bar {
  /* A week with womps in it is a link to that week's page and a week
     without is a plain span, so this holds its shape as either. */
  display: block;
  flex: 1 1 0;
  min-width: 0;
  /* a quiet month is still a month: never let a bar vanish entirely */
  min-height: 2px;
  background: var(--accent-3);
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.ww-pulse__bar:hover {
  background: var(--accent);
}
.ww-pulse__bar--now {
  background: var(--accent);
}
/* Said to a screen reader and to nobody else: the year's shape above the
   chart, and which week each bar opens. */
.ww-pulse__sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.ww-pulse__axis {
  display: flex;
  gap: 1px;
}
.ww-pulse__tick {
  flex: 1 1 0;
  min-width: 0;
  position: relative;
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--muted);
  white-space: nowrap;
}
.ww-pulse__note {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--muted);
}

.ww-pulse__who {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  padding-top: var(--space-3);
  border-top: var(--border-w) solid var(--border);
}
.ww-pulse__wholabel {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-pulse__whoitem {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 3px var(--space-2) 3px 3px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  color: var(--ink-2);
  max-width: 100%;
}
.ww-pulse__whoitem:hover {
  border-color: var(--border-strong);
  color: var(--ink);
  text-decoration: none;
}
.ww-pulse__whoitem .ds-num {
  font-size: var(--text-xs);
  color: var(--muted);
}
.ww-pulse__whoname {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- view switch ---- */
.ww-views {
  display: flex;
  gap: 0;
  margin-bottom: var(--space-4);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  width: fit-content;
  overflow: hidden;
}
.ww-views__opt {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-views__opt + .ww-views__opt {
  border-left: var(--border-w) solid var(--border);
}
.ww-views__opt:hover {
  color: var(--ink);
  text-decoration: none;
}
.ww-views__opt.is-on {
  background: var(--surface);
  color: var(--ink);
}

/* ---- timeline ----
   One grid for the whole feed. Posts fill the columns in order and a month
   rule spans them when the date crosses over. Days used to get a band each,
   which suited a busy world and not this one: a fortnight of Voxels can be
   one womp a day, so that was a heading per picture, each holding a
   half-empty row open. */
.ww-timeline {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5) var(--space-4);
}
@media (min-width: 860px) {
  .ww-timeline {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  /* The newest post, and any post holding a run of pictures, earn the whole
     width. Everything else pairs up. */
  .ww-post--wide {
    grid-column: 1 / -1;
  }
}
.ww-month {
  grid-column: 1 / -1;
  margin: var(--space-2) 0 0;
  padding-bottom: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  font-weight: 500;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
  border-bottom: var(--border-w) solid var(--border);
}
/* The first rule sits directly under the view switch, where the extra top
   margin reads as a gap nobody asked for. */
.ww-timeline > .ww-month:first-child {
  margin-top: 0;
}

/* ---- one post: a photographer's run of pictures ---- */
.ww-post {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}
.ww-post__caption {
  margin: 0;
  max-width: var(--measure);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--ink);
  overflow-wrap: anywhere;
}

/* The mosaic. One picture gets the room a single good photo deserves; a
   run of them tiles into squares, because at that point the subject is the
   run and not any one frame. */
.ww-post__shots {
  display: grid;
  gap: 2px;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--border);
}
.ww-post__shots--1 {
  grid-template-columns: minmax(0, 1fr);
}
.ww-post__shots--2 {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
.ww-post__shots--3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.ww-post__shot {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  background: var(--surface-2);
  overflow: hidden;
}
/* A lone picture in a half-width post keeps a photograph's own shape. Only
   the full-width ones widen out, and they widen rather than get a height
   cap: a box with an aspect ratio answers a height limit by narrowing,
   which would leave half the cell empty. */
.ww-post__shots--1 .ww-post__shot {
  aspect-ratio: 3 / 2;
}
@media (min-width: 900px) {
  .ww-post--wide .ww-post__shots--1 .ww-post__shot {
    aspect-ratio: 16 / 9;
  }
}
.ww-post__shots--2 .ww-post__shot {
  aspect-ratio: 4 / 3;
}
.ww-post__shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 200ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-post__shot:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
/* Coming back from a permalink lands on the picture it was opened from,
   held clear of the sticky date above it. */
.ww-post__shot:target {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}
.ww-timeline .ww-post__shot {
  scroll-margin-top: calc(var(--header-h) + var(--space-6));
}
.ww-post__rest {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-2);
}
.ww-post__rest span {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}
.ww-post__rest:hover {
  color: var(--accent);
  text-decoration: none;
}

@media (prefers-reduced-motion: no-preference) {
  .ww-post__shot:hover img {
    transform: scale(1.03);
  }
}

/* === wompworld/21-days.css === */
/* ---- time as a place: day, week, month, and the archive index ---- */

/* Prev and next sit at the edges with the period above between them, so
   the eye finds "where am I" in the middle and "where else" at the ends.
   A missing neighbour keeps its slot rather than letting the middle drift
   sideways from one day to the next. */
.ww-periodnav {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding-bottom: var(--space-3);
  border-bottom: var(--border-w) solid var(--border);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
}
.ww-periodnav__step {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-width: 0;
  color: var(--ink-2);
  white-space: nowrap;
}
.ww-periodnav__step:hover {
  color: var(--accent);
  text-decoration: none;
}
.ww-periodnav__step.is-off {
  color: var(--muted);
  opacity: 0.55;
}
.ww-periodnav__up {
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-periodnav__up:hover {
  color: var(--ink);
  text-decoration: none;
}
/* On a phone the three of them do not fit across, and the one that has to
   give is the one in the middle: it goes above, centred, with prev and
   next holding the edges under it. */
@media (max-width: 560px) {
  .ww-periodnav {
    flex-wrap: wrap;
    gap: var(--space-2) var(--space-3);
  }
  .ww-periodnav__up {
    order: -1;
    flex-basis: 100%;
    text-align: center;
  }
}

/* A row of days, each one a picture with its date over it. The cells are
   deliberately small: a month of them is a shape you read at a glance,
   not thirty photographs competing. They get real air between them and a
   hairline each, because thirty photographs of anything at all, butted
   edge to edge, read as one busy rectangle rather than as thirty days. */
.ww-daycells {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(78px, 1fr));
  gap: var(--space-3);
  margin-bottom: var(--space-5);
}
.ww-daycell {
  position: relative;
  display: block;
  aspect-ratio: 1 / 1;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  overflow: hidden;
  color: var(--ink);
}
.ww-daycell:hover {
  border-color: var(--accent);
  text-decoration: none;
}
/* The day the reader is standing on, in a strip of the days around it. */
.ww-daycell--here {
  border-color: var(--accent);
  box-shadow: inset 0 0 0 var(--border-w) var(--accent);
}
.ww-daycell__pic {
  position: absolute;
  inset: 0;
}
.ww-daycell__pic img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* The date and the count are the content here; the picture is the
     texture under them, and full strength it swallowed both. */
  opacity: 0.68;
  transition: opacity 200ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-daycell:hover .ww-daycell__pic img {
  opacity: 0.92;
}
/* A picture is anything at all, so the type sits on its own darkening
   rather than trusting whatever happens to be behind it. Two small
   gradients, top and bottom, leave the middle of the photograph clear. */
.ww-daycell__pic::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgb(0 0 0 / 55%), transparent 42%),
    linear-gradient(to top, rgb(0 0 0 / 55%), transparent 42%);
}
.ww-daycell__date,
.ww-daycell__count {
  position: absolute;
  font-family: var(--font-mono);
  color: #fff;
}
.ww-daycell__date {
  top: var(--space-2);
  left: var(--space-2);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: var(--tracking-snug);
}
.ww-daycell__count {
  right: var(--space-2);
  bottom: var(--space-2);
  font-size: var(--text-2xs);
  /* The count is the second thing read, not the first. */
  opacity: 0.82;
}

/* The home page's run of years is five or six cells in a panel as wide as
   the page, and at the size a month wants they sat in the corner of it
   with a month's worth of empty columns beside them. Here the cells take
   the row they are given: equal shares of it, a little wider than tall,
   with the year big enough to read from the picture. */
.ww-daycells--fill {
  display: flex;
  gap: var(--space-3);
  margin-bottom: 0;
}
.ww-daycells--fill .ww-daycell {
  flex: 1 1 0;
  min-width: 0;
  aspect-ratio: 4 / 3;
}
.ww-daycells--fill .ww-daycell__date {
  font-size: var(--text-base);
}
.ww-daycells--fill .ww-daycell__count {
  font-size: var(--text-xs);
}
/* A phone has no row to fill, so they go back to being a small grid. */
@media (max-width: 719px) {
  .ww-daycells--fill {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
  .ww-daycells--fill .ww-daycell {
    aspect-ratio: 1 / 1;
  }
}

/* Label on the left, the days across the rest: the words that say what the
   row is take the space the row does not need. Used by the home page's run
   of years and by a day page's own week. */
.ww-daystrip {
  display: grid;
  gap: var(--space-4);
}
.ww-daystrip__head {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: start;
}
.ww-daystrip__head .ww-seclabel {
  margin-bottom: 0;
}
.ww-daystrip__note {
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--ink-2);
}
/* At the foot of a page the strip needs its own air above it: what it
   follows is the last photograph of a run, with nothing between. */
.ww-daystrip--foot {
  padding-top: var(--space-6);
}
@media (min-width: 720px) {
  .ww-daystrip {
    grid-template-columns: minmax(170px, 220px) minmax(0, 1fr);
    gap: var(--space-5);
    align-items: center;
  }
}

/* The archive index: a year at a time, a card a month. */
.ww-months {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  gap: var(--space-4);
}
.ww-month-card {
  position: relative;
  display: block;
  aspect-ratio: 4 / 3;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface-2);
  overflow: hidden;
}
.ww-month-card:hover {
  border-color: var(--accent);
  text-decoration: none;
}
.ww-month-card__pic {
  position: absolute;
  inset: 0;
}
.ww-month-card__pic img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.72;
  transition:
    transform 200ms cubic-bezier(0.2, 0, 0, 1),
    opacity 200ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-month-card:hover .ww-month-card__pic img {
  opacity: 0.92;
}
.ww-month-card__pic::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, rgb(0 0 0 / 55%), transparent 45%),
    linear-gradient(to top, rgb(0 0 0 / 55%), transparent 45%);
}
.ww-month-card__name,
.ww-month-card__count {
  position: absolute;
  left: var(--space-3);
  font-family: var(--font-mono);
  color: #fff;
}
.ww-month-card__name {
  top: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 600;
  letter-spacing: var(--tracking-snug);
}
.ww-month-card__count {
  bottom: var(--space-3);
  font-size: var(--text-2xs);
}

@media (prefers-reduced-motion: no-preference) {
  .ww-month-card:hover .ww-month-card__pic img {
    transform: scale(1.03);
  }
}

/* === wompworld/22-gatherings.css === */
/* ---- gatherings: the nights the world turned up somewhere ---- */

/* Tiles by default: a gathering is a night of photographs, so the
   photograph is the card and the facts sit under it. Two across on a
   tablet, three on a desktop, and every fifth one runs the full width so
   a page of them has a rhythm rather than a texture. */
.ww-gatherings {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}
@media (min-width: 660px) {
  .ww-gatherings {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1040px) {
  .ww-gatherings {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
.ww-gathering {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  min-width: 0;
  color: var(--ink);
}
.ww-gathering:hover {
  text-decoration: none;
}
.ww-gathering__pic {
  display: block;
  aspect-ratio: 4 / 3;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
  overflow: hidden;
}
.ww-gathering__pic img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 200ms cubic-bezier(0.2, 0, 0, 1);
}
.ww-gathering:hover .ww-gathering__pic {
  border-color: var(--accent);
}
.ww-gathering__body {
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}
.ww-gathering__place {
  font-size: var(--text-base);
  font-weight: 600;
  letter-spacing: var(--tracking-snug);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-gathering:hover .ww-gathering__place {
  color: var(--accent);
}
.ww-gathering__meta,
.ww-gathering__when {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-gathering__meta .ds-num {
  color: var(--ink-2);
}
/* Every so often one runs the full width, so a page of them has a rhythm
   rather than a texture. Which one is a CSS question rather than a markup
   one, because the answer changes with the column count: a full-width
   card can only start a row, so on three columns it has to come after
   every third neighbour and on two after every second. Get that wrong and
   the grid leaves a row with one card in it and two holes beside it,
   which is exactly what it did. */
@media (min-width: 660px) and (max-width: 1039px) {
  .ww-gatherings:not(.ww-gatherings--rows) > .ww-gathering:nth-child(3n + 1) {
    grid-column: 1 / -1;
  }
  .ww-gatherings:not(.ww-gatherings--rows)
    > .ww-gathering:nth-child(3n + 1)
    .ww-gathering__pic {
    aspect-ratio: 21 / 9;
  }
}
@media (min-width: 1040px) {
  .ww-gatherings:not(.ww-gatherings--rows) > .ww-gathering:nth-child(4n + 1) {
    grid-column: 1 / -1;
  }
  .ww-gatherings:not(.ww-gatherings--rows)
    > .ww-gathering:nth-child(4n + 1)
    .ww-gathering__pic {
    aspect-ratio: 21 / 9;
  }
}
/* The rows never widen: they are a list, and a list with one item three
   times the width of the others is not a list. Said as a :not() on the
   rules above rather than as an override here, because an override at
   lower specificity is not an override at all, which is what the first
   attempt at this discovered. */

/* The compact form: a line with a thumbnail on it. Used where a wall of
   photographs would be the second or third on the page. */
.ww-gatherings--rows {
  gap: var(--space-3);
}
@media (min-width: 660px) {
  .ww-gatherings--rows {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}
@media (min-width: 1040px) {
  .ww-gatherings--rows {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}
.ww-gathering--row {
  flex-direction: row;
  align-items: stretch;
  gap: var(--space-3);
  padding: var(--space-2);
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}
.ww-gathering--row:hover {
  border-color: var(--accent);
}
.ww-gathering--row .ww-gathering__pic {
  flex: none;
  width: 84px;
  border-radius: var(--radius-sm);
}
.ww-gathering--row .ww-gathering__body {
  justify-content: center;
  gap: 2px;
}
.ww-gathering--row .ww-gathering__place {
  font-size: var(--text-sm);
}
.ww-gathering--row .ww-gathering__meta,
.ww-gathering--row .ww-gathering__when {
  font-size: var(--text-2xs);
}

/* The last gathering, at the size of the night it was: its pictures as a
   mosaic with the sentence beside them. */
.ww-hero {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
  color: var(--ink);
}
@media (min-width: 860px) {
  .ww-hero {
    /* Nearer to even than it looks: the pictures still lead, but a third
       of the row holding four lines of type left a hole beside them. */
    grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
    align-items: center;
  }
}
.ww-hero:hover {
  text-decoration: none;
}
.ww-hero__shots {
  display: grid;
  gap: 2px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: 1fr;
  /* The block's shape is fixed and the pictures fill it. Left to their
     own aspect ratios, a tall first frame made the hero three screens
     high on a wide window. */
  aspect-ratio: 16 / 9;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--border);
}
.ww-hero:hover .ww-hero__shots {
  border-color: var(--accent);
}
/* One picture fills the frame; two split it down the middle; three give
   the first one the whole left half and stack the other two beside it, so
   the block never reads as a grid of stamps. */
.ww-hero__shots--1,
.ww-hero__shots--2 {
  grid-auto-rows: auto;
}
.ww-hero__shots--1 {
  grid-template-columns: minmax(0, 1fr);
}
.ww-hero__shots--3 .ww-hero__shot:first-child {
  grid-row: span 2;
}
.ww-hero__shot {
  display: block;
  min-height: 0;
  overflow: hidden;
  background: var(--surface-2);
}
.ww-hero__shot img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.ww-hero__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  min-width: 0;
}
.ww-hero__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-hero__place {
  font-size: var(--text-2xl);
  font-weight: 700;
  letter-spacing: var(--tracking-snug);
  line-height: var(--leading-tight);
  overflow-wrap: anywhere;
}
.ww-hero:hover .ww-hero__place {
  color: var(--accent);
}
.ww-hero__facts {
  font-size: var(--text-sm);
  line-height: var(--leading-normal);
  color: var(--ink-2);
}
.ww-hero__facts .ds-num {
  color: var(--ink);
}
/* The crowd, at a glance: the faces overlap into a huddle rather than
   lining up as a row of stamps, and the names finish the sentence. */
.ww-hero__faces {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
}
.ww-hero__faces .ww-pfp {
  border-color: var(--surface);
  border-width: 2px;
}
/* Each face after the first tucks under the one before it, and nothing
   else in the row does, which is why this is stated between siblings
   rather than as a margin every face carries. */
.ww-hero__faces .ww-pfp + .ww-pfp {
  margin-left: -12px;
}
.ww-hero__names {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}
.ww-hero__go {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--accent);
}

@media (prefers-reduced-motion: no-preference) {
  .ww-gathering:hover .ww-gathering__pic img {
    transform: scale(1.03);
  }
}

/* Who was there. Faces first, because a gathering is a crowd and a crowd
   is people; the counts sit behind them in mono. */
.ww-crowd {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-3);
  margin-bottom: var(--space-5);
}
.ww-crowd__label {
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--muted);
}
.ww-crowd__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-width: 100%;
  padding: 3px var(--space-2) 3px 3px;
  border: var(--border-w) solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  color: var(--ink-2);
}
.ww-crowd__item:hover {
  border-color: var(--border-strong);
  color: var(--ink);
  text-decoration: none;
}
.ww-crowd__item .ds-num {
  font-size: var(--text-xs);
  color: var(--muted);
}
.ww-crowd__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ww-crowd__rest {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted);
}

/* === wompworld/23-standouts.css === */
/* ---- standouts rail, the dearest recent wearable trades ---- */

/* The note beside a section label, where ww-secrow__link would be if the
   section had somewhere to send you. */
.ww-secrow__note {
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  color: var(--muted);
}

/* One row that runs off the side, and fills the width when it does not.
   The featured-looks shelf can be a grid because it always holds eight;
   this one holds however many cleared the floor, which on a given week is
   six or four, so a fixed card width leaves the row stopping short of the
   section it sits in. The card is a share of the row instead, with a floor
   it never goes under and a ceiling it never goes over: five or more fill
   the width, eight overflow at 152px and it is a scroller again, and a
   quiet week of three is three cards and a gap rather than three
   billboards. */
.ww-standouts {
  display: flex;
  gap: var(--space-2);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  padding-bottom: var(--space-1);
  scrollbar-width: thin;
}
@media (min-width: 720px) {
  .ww-standouts {
    gap: var(--space-3);
  }
}

.ww-standout {
  display: flex;
  flex-direction: column;
  flex: 1 0 132px;
  min-width: 0;
  max-width: 220px;
  padding-bottom: var(--space-2);
  border: var(--border-w) solid var(--border);
  background: var(--surface);
  color: inherit;
  text-decoration: none;
  scroll-snap-align: start;
}
.ww-standout__art {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  background: var(--bg);
  border-bottom: var(--border-w) solid var(--border);
}
/* Thumb is built for a table row, so it arrives 90px wide with a border and
   a corner radius of its own. Here it is the whole top of the card and the
   card already carries the border, so it gives all three back. */
.ww-standout__art .ww-thumb {
  width: 100%;
  height: 100%;
  border: 0;
  border-radius: 0;
  background: transparent;
  object-fit: contain;
}
/* An item with no picture falls back to the identicon, which is a mark and
   not artwork: it sits small and centred rather than stretching to fill. */
.ww-standout__art .ww-thumb svg {
  width: 46px;
  height: 46px;
}
.ww-standout__art:has(svg) {
  display: grid;
  place-items: center;
}
/* Price leads, since it is the reason the card is on the shelf at all. It
   takes the tabular figures from .ds-num without the class, which also
   right-aligns: correct in a table column, and in a card it strands the
   price against the far edge with a gap where it should have started. */
.ww-standout__price {
  display: block;
  padding: var(--space-2) var(--space-2) 0;
  font-size: var(--text-sm);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
/* Wearable names run long ("B.20 Limited Edition Digiphysical Hoodie (Upper
   Sleeve)"), and one ellipsed line of a card this narrow cuts most of them
   at the first word or two. Two lines is enough to tell one hoodie from
   another, and the fixed height keeps the row of cards level whether a name
   takes one line or three. */
.ww-standout__name {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  min-height: calc(2 * 1.3em);
  padding: 0 var(--space-2);
  font-size: var(--text-xs);
  line-height: 1.3;
  color: var(--ink-2);
  overflow: hidden;
}
.ww-standout__when {
  display: block;
  padding: 0 var(--space-2);
  font-family: var(--font-mono);
  font-size: var(--text-2xs);
  color: var(--muted);
}
.ww-standout:hover,
.ww-standout:focus-visible {
  border-color: var(--accent);
}
.ww-standout:focus-visible {
  outline: none;
  box-shadow: 0 0 0 2px var(--accent-4);
}
@media (min-width: 720px) {
  .ww-standout {
    flex-basis: 152px;
  }
}
@media (prefers-reduced-motion: no-preference) {
  .ww-standout {
    transition: border-color 160ms cubic-bezier(0.2, 0, 0, 1);
  }
}

/* === wompworld/24-lost.css === */
/* ---- the 404: a line, a way back, and one good photograph ---- */

.ww-lost {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-6) 0;
  text-align: center;
}
/* Long enough to be a sentence, short enough to be read as one. */
.ww-lost__line {
  max-width: 44ch;
  font-size: var(--text-lg);
  line-height: var(--leading-normal);
  letter-spacing: var(--tracking-snug);
  color: var(--ink);
}
/* The small print either side of the picture: the way back, and the line
   that hands the picture over. Same voice, so one rule. */
.ww-lost__nav,
.ww-lost__offer {
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  color: var(--muted);
}

/* The offer and the picture are one thing, so they are one child with a
   gap of their own rather than two children of the stack above pulling at
   its spacing. */
.ww-lost__pick {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  padding-top: var(--space-3);
}
/* One card, at the width it has in a feed column. Left to the container it
   would run the whole page and read as the point rather than as the way
   out of a dead end. */
.ww-lost__womp {
  width: 100%;
  max-width: 380px;
  text-align: left;
}
