/* WordsGetCustomers -- design system
   built-by: hand-authored-ok | data: design_handoff_wgc_site tokens (README.md)
   Editorial, light, flat. Zero radius, no shadows, hairline rules.

   ONE definition per component. A page supplies structure and words: it never sets a
   color, a size, a padding, a gap, an alignment or a width of its own. */

/* ---------- tokens ---------- */
:root {
  /* surfaces */
  --paper:       #F7F4EE;
  --card:        #FCFAF6;
  --chip:        #EDE7DA;
  --rule:        #DDD6C9;
  --rule-strong: #C9C0AE;   /* the one hairline that has to read as a control edge */
  --dark:        #3A1420;   /* burgundy. The dark ground is a COLOR, not an absence of one */
  --dark-row:    #421824;   /* the lightest row tint that still clears 7 : 1 for amber and gold */
  --dark-rule:   #73303F;   /* every hairline inside a dark block. 1.6 : 1 off --dark-row */

  /* ink. Every value below is measured against the surface it is allowed to sit on:
     body tier clears 7 : 1, label tier never goes lighter than --ink-faint on paper. */
  --ink:         #1C1A17;   /* 15.8 : 1 on paper */
  --ink-soft:    #4A443A;   /*  8.8 : 1 on paper */
  --ink-muted:   #57503F;   /*  7.3 : 1 on paper */
  --ink-faint:   #6B6459;   /*  5.3 : 1 on paper. Label tier floor, nothing lighter */
  --dark-body:   #DCCEC7;   /*  9.9 : 1 on --dark-row,  10.5 : 1 on --dark */
  --dark-muted:  #C2B3AC;   /*  7.5 : 1 on --dark-row,   8.0 : 1 on --dark */

  /* accents */
  --blue:        #2F4B8F;   /*  7.6 : 1 on paper */
  --gold:        #E5A93F;   /* THE highlight. The wordmark's Get block, the active nav underline,
                               and every primary button. Carries ink text at 8.4 : 1, never paper
                               text, which is 2.0 : 1 on it and unreadable. */
  --amber:       #E5A555;   /*  7.1 : 1 on --dark-row. Dark blocks only */
  --amber-deep:  #82500C;   /*  6.2 : 1 on paper. Amber on a light surface */
  --err:         #8A3326;   /*  7.4 : 1 on paper */
  --err-dark:    #E8A08B;   /*  7.1 : 1 on --dark-row */

  /* text roles. Every text color on the site resolves through one of these. A surface
     that reverses out remaps the five roles once, so no class can be left behind
     wearing a light-mode color on a dark background. */
  --t-strong: var(--ink);
  --t-body:   var(--ink-soft);
  --t-small:  var(--ink-muted);
  --t-faint:  var(--ink-faint);
  --t-accent: var(--blue);
  --t-amber:  var(--amber-deep);
  --t-err:    var(--err);
  --t-hover:  var(--blue);

  /* space. One scale: no value appears that is not on this list. */
  --gutter:     clamp(20px, 4vw, 40px);
  --rhythm:     clamp(32px, 4.4vw, 76px);
  --rhythm-sm:  clamp(24px, 3vw, 44px);
  --gap-lg:     clamp(24px, 3.2vw, 44px);
  --pad-box:    clamp(22px, 2.6vw, 34px);
  --pad-box-sm: clamp(18px, 2.1vw, 26px);
  --row-pad:    13px 17px;
  --key-col:    56px;   /* the numeral / label gutter, every list on the site */
  --key-gap:    20px;

  --measure:        62ch;
  --measure-narrow: 48ch;
  --mono: ui-monospace, Menlo, Consolas, monospace;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/* .nav-shell is sticky and 85px tall, so a hash jump buries the top of its target
   under the nav. Every anchor on the site needs this, not just the sample course. */
html { scroll-padding-top: 100px; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: Archivo, system-ui, -apple-system, sans-serif;
  font-weight: 400;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}
/* height:auto is load-bearing: the width/height attributes on an <img> map to a real used
   height, so once max-width shrinks the width the height stays at the attribute value and the
   picture stretches vertically. Classes that want a fixed height (.avatar, .portrait) out-rank
   this on specificity. */
img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
/* Hover resolves through the role variable, so a link inside a dark block hovers to
   paper instead of to blue. Buttons carry their own hover and out-rank this. */
a:hover { color: var(--t-hover); }
h1, h2, h3, h4, p, ul, ol, figure { margin: 0; }
ul, ol { padding: 0; list-style: none; }
button { font: inherit; color: inherit; border: 0; background: none; cursor: pointer; }
:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* Zero radius is a brand decision. Nothing rounds. */
* { border-radius: 0 !important; }

/* ---------- layout ---------- */
.wrap { max-width: 1080px; margin: 0 auto; padding-left: var(--gutter); padding-right: var(--gutter); }
/* Two section paddings, and every top-level section carries exactly one of them. */
.section { padding-top: var(--rhythm); padding-bottom: var(--rhythm); }
.section--tight { padding-top: var(--rhythm-sm); padding-bottom: var(--rhythm-sm); }
.ruled-top { border-top: 1px solid var(--rule); }
/* THE stacked text group: a section header, a card column, a value trio cell, a FAQ item,
   a column inside a dark block. One gap, one class, light side and dark side alike. */
.group { display: flex; flex-direction: column; gap: 12px; }
/* The hero content column is the one place with its own rhythm: a 60px display headline
   needs more air under it than a card title does. */
.hero-col { display: flex; flex-direction: column; gap: 20px; }
.row { display: flex; flex-wrap: wrap; align-items: center; gap: 16px; }
/* Alignment modifiers, shared by flex rows and grids: a grid treats flex-start and
   flex-end as start and end, so one pair of classes covers both. */
.align-start { align-items: flex-start; }
.align-end { align-items: flex-end; }

/* ---------- type ---------- */
/* The vw coefficient is tuned so the longest headline still fits a two-column hero column
   at every width between the stack point and the cap. Raising it reintroduces overflow. */
/* ONE hero size for the whole site. There is no --xl or --compact variant on purpose:
   a per-page size is how the five heroes drifted apart in the first place.
   NO overflow-wrap here. break-word hyphenates nothing but still breaks after the last
   letter, which strands a lone period on its own line. Column width is what keeps a
   headline inside its column (.cols-hero stacks early), not letter breaking. */
.h1 {
  font-weight: 300;
  font-size: clamp(34px, 4.2vw, 60px);
  line-height: 1.0;
  letter-spacing: -0.03em;
  color: var(--t-strong);
}
.h2 {
  font-weight: 300;
  font-size: clamp(34px, 4.6vw, 54px);
  line-height: 1.03;
  letter-spacing: -0.028em;
  color: var(--t-strong);
}
.h3 {
  font-weight: 400;
  font-size: clamp(24px, 2.6vw, 34px);
  line-height: 1.08;
  letter-spacing: -0.028em;
  color: var(--t-strong);
}
.card-title { font-weight: 500; font-size: clamp(21px, 1.9vw, 24px); letter-spacing: -0.02em; line-height: 1.2; color: var(--t-strong); }
.eyebrow {
  font-weight: 600;
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.145em;
  color: var(--t-accent);
}
.mono {
  font-family: var(--mono);
  font-size: 11px;
  letter-spacing: 0.095em;
  color: var(--t-faint);
  text-transform: uppercase;
}
.mono--sm { font-size: 10px; letter-spacing: 0.08em; }
.lede { font-size: clamp(17px, 1.6vw, 21px); line-height: 1.5; color: var(--t-small); font-weight: 400; }
.body { font-size: clamp(15.5px, 1.2vw, 17px); line-height: 1.58; color: var(--t-body); }
.body--sm, .rows-val { font-size: 15px; line-height: 1.55; color: var(--t-small); }
.caption { font-size: 13.5px; line-height: 1.5; color: var(--t-faint); }
.stat-num { font-weight: 300; font-size: clamp(42px, 5vw, 60px); line-height: 1; letter-spacing: -0.03em; color: var(--t-accent); }
.nav-links, .footer-col { font-size: 14.5px; }
.measure { max-width: var(--measure); }
.measure--narrow { max-width: var(--measure-narrow); }
/* One accent class for every accented mark on the site: step numerals, list bullets,
   arrows, inline links. It was three classes plus an inline color. */
.accent { color: var(--t-accent); }
.accent-amber { color: var(--t-amber); }
.link-u { color: var(--t-accent); display: inline-block; border-bottom: 1px solid currentColor; padding-bottom: 1px; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-weight: 600; font-size: 15.5px; line-height: 1.1;
  min-height: 44px; padding: 16px 30px;
  border: 1px solid transparent; text-align: center;
  transition: background-color .15s ease, color .15s ease, border-color .15s ease;
}
/* Gold is the brand's own device: the wordmark sets ink on a gold block, and the primary
   action repeats it. Hover deepens to amber and flips to paper, 6.2 : 1. */
.btn-primary { background: var(--gold); color: var(--ink); }
.btn-primary:hover { background: var(--amber-deep); color: var(--paper); }
/* The compact variant, for the nav and for a form submit. 44px is the hit-target floor
   and it governs here, the padding does not. */
.btn-compact { background: var(--gold); color: var(--ink); padding: 10px 18px; }
.btn-compact:hover { background: var(--amber-deep); color: var(--paper); }
.btn-secondary { background: transparent; color: var(--t-strong); border-color: var(--rule-strong); }
.dark .btn-secondary { border-color: var(--dark-rule); }
.btn-secondary:hover { border-color: var(--t-strong); color: var(--t-strong); }
.btn-onDark { background: var(--paper); color: var(--ink); }
.btn-onDark:hover { background: var(--amber); color: var(--dark); }
/* The pay page fails closed by setting aria-disabled when a payment link is missing.
   Without this the dead button renders identical to the live one beside it. */
.btn[aria-disabled="true"] { background: var(--chip); color: var(--ink-faint); cursor: not-allowed; }
/* A button, or an underlined link, is a flex item inside a flex column, so it stretches
   to full width unless told not to. A full-bleed button reads as a banner; a full-bleed
   underline reads as a stray rule. */
.group > .btn, .group > .link-u, .hero-col > .btn { align-self: flex-start; }

/* ---------- hairline grids ---------- */
.grid {
  display: grid;
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}
.grid > * { background: var(--paper); }
.grid-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.grid-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
.grid-4 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr)); }
.cell { padding: var(--pad-box); display: flex; flex-direction: column; gap: 12px; }
/* The head of a card: a label or title on the left, a counterpart on the right, sharing
   one baseline. Used by every card, every step card and every module button. */
.cell-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }

/* plain grids, no hairline frame. One breakpoint set, shared with the hairline grids. */
.cols { display: grid; gap: var(--gap-lg); }
.cols-2 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 300px), 1fr)); }
.cols-3 { grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr)); }
/* Hero: stacks well before the display headline would collide with the card beside it. */
.cols-hero { grid-template-columns: repeat(auto-fit, minmax(min(100%, 440px), 1fr)); }
/* Text beside a fixed-size image. An equal-column grid gives the image half the width and
   leaves a dead zone under it; here the text absorbs everything the image does not need. */
.media-row { display: flex; flex-wrap: wrap; align-items: flex-start; gap: var(--gap-lg); }
.media-row > .media-body { flex: 1 1 440px; min-width: 0; }
.media-row > img { flex: 0 0 auto; }

/* ---------- section shape ---------- */
/* Header, then content. ONE gap between a section's header and its body. Pages were
   setting this five different ways, three of them inline margins on the heading. */
.sec-body { display: flex; flex-direction: column; gap: var(--gap-lg); }
/* The closing call to action. Every page ends in this block, byte for byte. */
.cta { display: flex; flex-wrap: wrap; align-items: flex-end; justify-content: space-between; gap: var(--gap-lg); }
.cta-text { display: flex; flex-direction: column; gap: 16px; max-width: var(--measure-narrow); }
.cta-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; }

/* ---------- key / value rows ----------
   ONE numbered or labelled row pattern for the whole site: a two-column GRID, never a
   flex row. A flex row wraps a long value underneath its key, which is what made row
   heights random inside a single list. A grid holds the key in a fixed gutter and lets
   the value wrap inside its own column, so a row's height is driven only by how many
   lines its own text takes. */
.rows { display: flex; flex-direction: column; gap: 2px; }
.rows-item {
  display: grid;
  grid-template-columns: var(--key-col) minmax(0, 1fr);
  column-gap: var(--key-gap);
  align-items: baseline;
  padding: var(--row-pad);
}
.rows-val { min-width: 0; }
/* Ruled variant: rows sit flush in their column, hairline between, container closes the
   last one. This replaced a hand-placed trailing <hr> on two pages. */
.rows--ruled { gap: 0; border-bottom: 1px solid var(--rule); }
.rows--ruled > .rows-item { padding: 13px 0; border-top: 1px solid var(--rule); }
/* Bullet variant: the key is one glyph, so the gutter narrows to match it. */
.rows--bullet { --key-col: 12px; --key-gap: 12px; }

/* ---------- boxes ---------- */
.chip-box { background: var(--chip); padding: var(--pad-box-sm); }
/* A label on the chip surface steps up one ink tier: chip is darker than paper, so the
   label tier would otherwise fall below its own floor. */
.chip-box .mono { color: var(--ink-soft); }
.card-box { background: var(--card); border: 1px solid var(--rule); padding: var(--pad-box); }

/* ---------- the dark block ----------
   ONE component. Everything a dark block contains is decided here: background, row
   background, every text role, the numeral gutter, the divider, the highlighted row, the
   button, and the link at the bottom. A page inside a dark block writes structure and
   words only. Remapping the five text roles is what makes that true: any class that
   resolves its color through a role is correct on dark without a per-class override.
   A switched-on module button is the same reversed surface and shares the remap. */
.dark, .mod[aria-pressed="true"] {
  background: var(--dark);
  color: var(--paper);
  --t-strong: var(--paper);
  --t-body:   var(--dark-body);
  --t-small:  var(--dark-body);
  --t-faint:  var(--dark-muted);
  --t-accent: var(--amber);
  --t-amber:  var(--amber);
  --t-err:    var(--err-dark);
  --t-hover:  var(--paper);
}
/* An inline dark card sits inside a .wrap and has no padding source of its own. */
.dark--card { padding: var(--rhythm-sm); }
/* Light display weights erode reversed out of dark. */
.dark .h2, .dark .h3, .dark .card-title { font-weight: 400; }
.dark .grid { background: var(--dark-rule); border-color: var(--dark-rule); }
.dark .grid > * { background: var(--dark); }
/* A row inside a dark block. The transparent left border is load-bearing: it keeps every
   row's text on the same left edge as the highlighted one, which carries a real border. */
.dark .rows-item { background: var(--dark-row); border-left: 3px solid transparent; padding-left: 14px; }
.dark .rows-item--hl { border-left-color: var(--amber); }
.dark .rows--ruled { border-bottom-color: var(--dark-rule); }
.dark .rows--ruled > .rows-item { border-top-color: var(--dark-rule); }

/* ---------- hero bands ---------- */
.hero { padding-top: clamp(40px, 5.4vw, 84px); padding-bottom: clamp(36px, 4.8vw, 72px); border-bottom: 1px solid var(--rule); }
/* Per-page wayfinding tints. The only literal colors outside :root on purpose: they are
   band definitions, one per page, and are never reused anywhere else. */
.hero--courses  { background: repeating-linear-gradient(115deg, #E7ECF3 0 16px, #ECF0F6 16px 32px); }
.hero--writing  { background: repeating-linear-gradient(115deg, #F1ECE1 0 16px, #F6F2E9 16px 32px); }
.hero--sites    { background: repeating-linear-gradient(115deg, #E8EFEA 0 16px, #EDF3EF 16px 32px); }
.hero--about    { background: repeating-linear-gradient(115deg, #EDEBE6 0 16px, #F2F0EC 16px 32px); }
/* Home: plain striped band, same device as the other four pages. An illustration slot lives
   in images/hero-desk.svg if one is ever wanted back; add it as a middle background layer
   under a lighter veil. A real photograph would want the veil near 0.80. */
.hero--home     { background: repeating-linear-gradient(115deg, #E9E4D9 0 16px, #EEEAE0 16px 32px); }

/* ---------- nav ---------- */
.nav-shell { position: sticky; top: 0; z-index: 50; background: var(--paper); border-bottom: 1px solid var(--rule); }
.nav-inner { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; row-gap: 14px; gap: var(--gutter); padding-top: 20px; padding-bottom: 20px; }
.nav-links { display: flex; align-items: center; flex-wrap: wrap; gap: 16px 22px; }
.nav-links a { font-weight: 500; color: var(--t-body); display: inline-flex; align-items: center; min-height: 44px; position: relative; }
/* The 44px hit target makes the link box much taller than its text, so a border-bottom
   lands way below the word. The underline is drawn relative to the text instead. */
.nav-links a.is-active { font-weight: 600; color: var(--ink); }
.nav-links a.is-active::after {
  content: ""; position: absolute; left: 0; right: 0;
  top: calc(50% + 0.62em); height: 2px; background: var(--gold);
}
/* The nav link color rule would otherwise put paper text on the gold button. */
.nav-links a.btn-compact { color: var(--ink); }
.nav-links a.btn-compact:hover { color: var(--paper); }

/* ---------- wordmark ---------- */
.wordmark { font-weight: 600; font-size: 20px; letter-spacing: -0.03em; display: inline-flex; align-items: flex-start; gap: 1px; color: var(--ink); }
.wordmark:hover { color: var(--ink); }
.wordmark .wm-get { background: var(--gold); padding: 0 5px; }
.wordmark .wm-cust { padding-left: 1px; }
.wordmark .wm-tm { font-size: 9px; font-weight: 500; line-height: 1.1; padding-left: 1px; }
.wordmark--sm { font-size: 18px; }
.wordmark--sm .wm-tm { font-size: 8px; }

/* ---------- footer ---------- */
.footer { background: var(--paper); border-top: 1px solid var(--rule); }
.footer-inner { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--gutter); flex-wrap: wrap; padding-top: var(--rhythm-sm); padding-bottom: var(--rhythm-sm); }
.footer-cols { display: flex; gap: var(--gap-lg); flex-wrap: wrap; }
.footer-col { display: flex; flex-direction: column; gap: 9px; }
.footer-col a { color: var(--t-body); }
.footer-col .mono { margin-bottom: 3px; }
/* A link that carries an icon beside its label. */
.link-icon { display: inline-flex; align-items: center; gap: 7px; }

/* ---------- writing / newsletter post list (mirrors SMA's posts.json mechanism:
   fetch /posts.json client-side, render newest first, hide the section when empty) ---------- */
.post-list { display: flex; flex-direction: column; }
.post-row { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 12px; padding: 13px 0; border-top: 1px solid var(--rule); }
.post-list .post-row:first-child { border-top: none; }
.post-row a { color: var(--t-strong); font-weight: 500; }
.post-row a:hover { color: var(--t-hover); }
.post-date { color: var(--t-faint); font-size: 13.5px; white-space: nowrap; flex-shrink: 0; }
.post-kind { font-family: var(--mono); font-size: 10px; letter-spacing: 0.095em; text-transform: uppercase; color: var(--t-accent); margin-right: 8px; }

/* ---------- forms ---------- */
.field {
  width: 100%; padding: 14px 15px; min-height: 48px;
  border: 1px solid var(--rule); background: var(--paper); color: var(--t-strong);
  /* 16px, not the body size: anything smaller makes iOS Safari zoom on focus. */
  font: 400 16px/1.3 Archivo, system-ui, sans-serif;
}
.field::placeholder { color: var(--t-faint); }
.field:focus { outline: none; border-color: var(--blue); }
.dark .field { background: var(--dark-row); border-color: var(--dark-rule); }
.form { display: flex; flex-direction: column; gap: 12px; }
.form-row { display: flex; flex-wrap: wrap; gap: 10px; }
.form-row .field { flex: 1 1 220px; width: auto; }
.form-msg { min-height: 20px; }
.form-msg.is-ok { color: var(--t-accent); }
.form-msg.is-err { color: var(--t-err); }

/* ---------- module toggle grid (Websites) ---------- */
/* Sizes and colors come from .body, .caption and .mono in the markup; this component only
   owns the surface, the layout and the pressed state. */
.mod { text-align: left; width: 100%; padding: var(--pad-box-sm); display: flex; flex-direction: column; gap: 8px; min-height: 44px; background: var(--paper); color: var(--t-body); transition: background-color .12s ease, color .12s ease; }
.mod .mod-name { font-weight: 500; color: var(--t-strong); }
/* .mod-state carries no styling on purpose: it is the hook the toggle script writes
   ON / OFF into. Its type comes from .mono .mono--sm like every other micro label. */

/* ---------- modal ----------
   One dialog, whose contents are cloned from the page's own opt-in card at open time.
   It owns the shell only: the card inside brings its own surface and padding. */
.modal {
  /* Sized and centred off the VIEWPORT, not a percentage. A dialog in the top layer has no
     reliable percentage containing block: 100% resolved to 0 here and collapsed it to 2px.
     position/inset/margin are set explicitly rather than inherited from UA :modal styles. */
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(520px, calc(100vw - 2 * var(--gutter)));
  height: max-content;
  max-height: calc(100vh - 2 * var(--gutter));
  border: 1px solid var(--rule);
  background: var(--paper);
  padding: 0;
  overflow: auto;
}
.modal::backdrop { background: rgba(26, 24, 21, 0.62); }
/* The cloned card already carries .card-box, so its border would double the dialog's. */
.modal .card-box { border: 0; }
.modal-x {
  position: absolute; top: 6px; right: 10px; z-index: 1;
  font-size: 26px; line-height: 1; padding: 6px 10px;
  min-width: 44px; min-height: 44px;
  color: var(--t-faint);
}
.modal-x:hover { color: var(--t-strong); }

/* ---------- misc ---------- */
.avatar { width: 56px; height: 56px; object-fit: cover; object-position: center; flex: 0 0 auto; }
/* Capped: at 1:1 in a half-width column this renders around 500px square and takes over
   the page. The bio is the point; the photo supports it. */
/* height:auto is load-bearing. The img carries width="800" height="800" attributes, which
   the UA maps to a real used height, and aspect-ratio is ignored unless height is auto.
   Without it this renders as a 160 x 800 strip, not a square. */
.portrait { width: 100%; max-width: 260px; height: auto; aspect-ratio: 1 / 1; object-fit: cover; object-position: center; border: 1px solid var(--rule); }
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 12px; top: 12px; z-index: 100; background: var(--ink); color: var(--paper); padding: 10px 16px; }

/* ---------- carousel (Websites, D.U.D.E.S. hub) ----------
   Base state needs no javascript: the viewport scrolls horizontally, each slide snaps to
   full width, and every frame is reachable by scroll or swipe. JS adds .is-js to the root,
   which is the only thing that switches the viewport to one-frame-at-a-time and reveals the
   prev/next/dot controls; those controls live in the markup the whole time so a screen
   reader always has real, focusable buttons rather than ones injected after load. */
.carousel { display: flex; flex-direction: column; gap: 10px; }
.carousel-viewport { border: 1px solid var(--rule); overflow-x: auto; scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; }
.is-js .carousel-viewport { overflow: hidden; }
.carousel-track { display: flex; margin: 0; padding: 0; }
.carousel-slide { flex: 0 0 100%; min-width: 0; scroll-snap-align: start; }
/* The [hidden] attribute is what actually swaps frames: with only the current slide left
   in flow, it is the sole flex child and fills the viewport at its own aspect ratio. */
.carousel-slide[hidden] { display: none; }
.carousel-figure { margin: 0; }
/* The caption sits inside the viewport's border, so it needs the same side inset a row gets;
   padding-top alone left it hard against the left edge. */
.carousel-caption { padding: var(--row-pad); }
.carousel-controls { display: none; align-items: center; justify-content: space-between; gap: 12px; }
.is-js .carousel-controls { display: flex; }
.carousel-btn { width: 44px; height: 44px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; border: 1px solid var(--rule-strong); background: var(--paper); color: var(--t-strong); font-size: 18px; }
.carousel-btn:hover { border-color: var(--t-strong); color: var(--t-strong); }
.carousel-dots { display: flex; align-items: center; gap: 2px; }
/* 44px hit target with a small visual dot centred inside it, same pattern as the nav
   underline: the click area and the mark it draws are not the same box. */
.carousel-dot { width: 44px; height: 44px; flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; background: none; }
.carousel-dot::before { content: ""; width: 9px; height: 9px; border: 1px solid var(--rule-strong); background: var(--paper); }
/* Ink, not gold: gold is 2.0 : 1 on the card and would not read as an indicator, and not
   blue either, which now sits beside a gold button and reads as a leftover. */
.carousel-dot[aria-current="true"]::before { background: var(--t-strong); border-color: var(--t-strong); }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}
