/* ================================================================
   Fiddlemint — fiddlemint.com
   Static, self-contained: fonts, art, CSS and JS are all local, and the
   page makes no request to any other server.

   The palette (PLAN.md §2). Mint is the identity color, and mint is nearly
   unreadable as text on a light page, so the rule is: MINT IS A SURFACE AND
   A GRAPHIC COLOR; SPRUCE IS THE TEXT COLOR. Every pair below was computed
   2026-09-10 (WCAG relative luminance):

     ink #14332B on ground 12.9:1 · ink-soft 7.1:1 · muted 4.8:1
     mint-deep #0F7A60 on ground 5.0:1 · white on mint-ink #0B5C48 8.0:1
     ink on mint #7EE0C2 8.7:1 · ink on apricot #FFB067 7.6:1
     dark: text #E9F3EE on ground 15.0:1 · muted 8.6:1 · mint 10.8:1

   The one number that fell short is muted on mint-soft (4.4:1), so muted
   text never sits on a tinted panel — ink-soft (6.5:1) does. There is no
   `.tint .muted` anywhere; if you add one, fix the color too.

   The display face is OUTFIT (13 Sep 2026), the third try. Fraunces's f
   was "really weird"; Bricolage Grotesque's f has a hook so large it fuses
   with the next letter. Outfit has a plain f, and at 800 it is chunky and
   round like the mark. One axis (wght 100-900), so no font-variation-settings
   anywhere. Body text stays Nunito, with ligatures OFF (see html rule).

   Themes: the bare :root is the complete light palette. The media query
   redefines only tokens, guarded so an explicit light choice beats a dark
   OS, and the [data-theme="dark"] block repeats them so a toggle wins in
   both directions. No component ever names a hex.
   ================================================================ */

@font-face {
  font-family: "Outfit";
  src: url("../assets/fonts/outfit.woff2") format("woff2");
  font-weight: 100 900;
  font-display: swap;
}
@font-face {
  font-family: "Nunito";
  src: url("../assets/fonts/nunito.woff2") format("woff2");
  font-weight: 200 1000;
  font-display: swap;
}

:root {
  --ground: #F5FAF6;
  --paper: #FFFFFF;
  --ink: #14332B;
  --ink-soft: #3E5A52;
  --muted: #5B736C;
  --mint: #7EE0C2;
  --mint-soft: #D6F5EA;
  --mint-deep: #0F7A60;
  --mint-ink: #0B5C48;
  --apricot: #FFB067;
  --apricot-deep: #A8521A;
  --on-mint: #14332B;        /* text that sits on mint or apricot */
  --on-mint-ink: #FFFFFF;    /* text that sits on the solid button */
  --line: rgba(20, 51, 43, .14);
  --line-strong: rgba(20, 51, 43, .30);
  --bezel: #14332B;          /* the phone frame, dark in BOTH themes */
  --bezel-edge: #2B4A41;
  --shadow: 0 10px 30px rgba(20, 51, 43, .10);
  --shadow-lift: 0 16px 40px rgba(20, 51, 43, .16);

  --display: "Outfit", "Trebuchet MS", "Segoe UI", system-ui, sans-serif;
  --body: "Nunito", "Segoe UI", system-ui, -apple-system, sans-serif;
  /* the studio's own heading gradient; each app palette overrides it */
  --grad-a: #0F7A60; --grad-b: #0B6E55; --grad-c: #0B5C48;
  --wrap: 1120px;
  --r-card: 18px;
  color-scheme: light;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #0F1F1B;
    --paper: #172B26;
    --ink: #E9F3EE;
    --ink-soft: #C9DAD3;
    --muted: #A7BDB5;
    --mint-soft: #1E3A33;
    --mint-deep: #7EE0C2;
    --mint-ink: #9BEAD3;
    --apricot-deep: #FFB067;
    --on-mint-ink: #0F1F1B;
    --line: rgba(233, 243, 238, .14);
    --line-strong: rgba(233, 243, 238, .30);
    --bezel: #0A1512;
    --bezel-edge: #244037;
    --shadow: 0 10px 30px rgba(0, 0, 0, .38);
    --shadow-lift: 0 16px 40px rgba(0, 0, 0, .5);
    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --ground: #0F1F1B;
  --paper: #172B26;
  --ink: #E9F3EE;
  --ink-soft: #C9DAD3;
  --muted: #A7BDB5;
  --mint-soft: #1E3A33;
  --mint-deep: #7EE0C2;
  --mint-ink: #9BEAD3;
  --apricot-deep: #FFB067;
  --on-mint-ink: #0F1F1B;
  --line: rgba(233, 243, 238, .14);
  --line-strong: rgba(233, 243, 238, .30);
  --bezel: #0A1512;
  --bezel-edge: #244037;
  --shadow: 0 10px 30px rgba(0, 0, 0, .38);
  --shadow-lift: 0 16px 40px rgba(0, 0, 0, .5);
  color-scheme: dark;
}

* { box-sizing: border-box; }
html { scroll-behavior: smooth; }
/* NO LIGATURES, anywhere. Nunito and Bricolage both fuse "fi", "fl" and
   "ff" into single glyphs: the f's hook swallows the next letter and the i
   loses its dot, so "Fiddlemint", "offers" and "fireflies" all read as
   misprints. Anna: "the f is just too weird in all the text copy"
   (13 Sep 2026). This was the real cause, not the display face. */
html { font-variant-ligatures: none; font-feature-settings: "liga" 0, "clig" 0, "dlig" 0; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--body);
  font-size: 18px;
  font-weight: 450;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}
@media (max-width: 720px) { body { font-size: 17px; } }

img { max-width: 100%; height: auto; display: block; }
a { color: var(--mint-deep); text-decoration: underline; text-underline-offset: .15em; text-decoration-thickness: 1.5px; }
a:hover { color: var(--mint-ink); text-decoration-thickness: 2.5px; }
:focus-visible { outline: 3px solid var(--mint-deep); outline-offset: 3px; border-radius: 6px; }

h1, h2, h3 { font-family: var(--display); font-weight: 700; letter-spacing: -.01em; line-height: 1.12; margin: 0; text-wrap: balance; }
h1 {
  font-size: clamp(2.6rem, 6vw, 4.4rem); font-weight: 800;
  line-height: 1.03;
  letter-spacing: -.015em;
}
h2 {
  font-size: clamp(1.9rem, 3.6vw, 2.6rem);
  margin-bottom: .35em;
}
h3 { font-size: 1.25rem; }
/* a subhead straight under a page title needs air; the privacy page lost the
   standfirst that used to provide it */
h1 + h2 { margin-top: 34px; }
p { margin: 0 0 1em; max-width: 66ch; }

/* A row of the app's own icons with their names under them. Unlike the
   marquee this does not move and is not decorative: each label says what the
   icon means, so the row carries information rather than atmosphere. */
.icon-row {
  display: grid; grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 18px 14px; list-style: none; padding: 0; margin: 26px 0 0; max-width: none;
}
.icon-row li { text-align: center; }
.icon-row img { width: 100%; max-width: 96px; height: auto; margin: 0 auto 8px; }
.icon-row span { display: block; font-size: .92rem; color: var(--ink-soft); line-height: 1.3; }
@media (max-width: 760px) { .icon-row { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* --------------------------------------------------------------- FAQ
   Built on <details>/<summary>, so it needs no script, works with JS off,
   is keyboard operable and is announced as a disclosure by screen readers.
   The summary carries a 44px minimum height for WCAG 2.5.8, and the chevron
   is a rotated border rather than a glyph, because neither shipped font
   carries one and a fallback face mid-heading is exactly the bug that was
   fixed on this site once already. */
.faq { max-width: 760px; }
.faq details { border-top: 1px solid var(--line); }
.faq details:last-of-type { border-bottom: 1px solid var(--line); }
.faq summary {
  position: relative;
  display: block;               /* not flex: Safari drops the marker box */
  list-style: none;
  cursor: pointer;
  padding: 16px 46px 16px 2px;
  min-height: 44px;
  font-family: var(--display);
  font-weight: 700; font-size: 1.1rem; line-height: 1.35;
  color: var(--ink);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: ""; position: absolute; right: 12px; top: 50%;
  width: 11px; height: 11px; margin-top: -9px;
  border-right: 2.5px solid var(--mint-deep);
  border-bottom: 2.5px solid var(--mint-deep);
  transform: rotate(45deg);
}
.faq details[open] summary::after { margin-top: -3px; transform: rotate(-135deg); }
.faq summary:hover { color: var(--mint-ink); }
.faq .answer { padding: 0 46px 18px 2px; }
.faq .answer p { margin: 0 0 .8em; color: var(--ink-soft); }
.faq .answer p:last-child { margin-bottom: 0; }
@media (prefers-reduced-motion: no-preference) {
  .faq summary::after { transition: transform .18s ease, margin-top .18s ease; }
}

/* ----------------------------------------------- variegated headings
   Anna, 13 Sep 2026: "see how the headers have variegated color? somehow
   that got stripped away from the glitter jar webpage." It is the pattern
   Spinception and Glitter Jar's own pages both use: a gradient painted
   through the glyphs of PART of a heading, never the whole of it.

   Three rules keep it safe:
   1. It wraps a <span> inside a heading, never a run of body text. Usually
      that is part of the sentence, so the effect reads as emphasis; the
      short "Who it's for" headings take the whole line, at Anna's ask
      (13 Sep 2026).
   2. Every stop is contrast-checked against its own app's ground. All are
      at or above 4.2:1, and headings are large text, where the bar is 3:1.
   3. `background-clip: text` needs `color: transparent`, which Windows
      High Contrast would otherwise render as invisible text. The
      forced-colors block puts the real color back. */
.grad {
  background: linear-gradient(100deg, var(--grad-a) 8%, var(--grad-b) 45%, var(--grad-c) 90%);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
@media (forced-colors: active) {
  .grad { background: none; -webkit-text-fill-color: currentColor; color: currentColor; }
}
/* Anna, 13 Sep 2026: keep the variegated phrase on one line "when you can".
   Above phone width there is room, so the line breaks BEFORE the span and the
   gradient runs unbroken through it; `text-wrap: balance` on the heading can
   otherwise split it in two. Below 760px there is not room for any of these
   phrases, and a nowrap span there would push the page sideways, so the rule
   simply does not apply and they wrap normally. */
@media (min-width: 760px) {
  .grad { white-space: nowrap; }
  /* ...except when the span IS the whole heading, which must still wrap or a
     long title runs off the side. Used on Rug Wash, where Anna wanted no
     plain-ink half at the start of a heading: the colour runs the whole way
     (16 Sep 2026). */
  .grad.whole { white-space: normal; }
}
ul, ol { max-width: 66ch; }

.wrap { max-width: var(--wrap); margin: 0 auto; padding-inline: 24px; }
@media (max-width: 520px) { .wrap { padding-inline: 16px; } }
/* A narrow reading column that keeps the page's ONE left edge: the wrapper
   stays 1120 so it lines up with the hero above it, and the children are the
   thing that gets narrow. Centering the wrapper instead gave the app pages
   two different left margins. */
.wrap.narrow > * { max-width: 760px; }
.wrap.narrow > .table-scroll { max-width: 100%; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 200;
  background: var(--mint-ink); color: var(--on-mint-ink);
  padding: 12px 18px; border-radius: 0 0 10px 0; font-weight: 800; text-decoration: none;
}
.skip:focus { left: 0; }

.kicker {
  font-family: var(--body);
  font-size: .8rem; font-weight: 800; letter-spacing: .09em; text-transform: uppercase;
  color: var(--mint-deep); margin: 0 0 .7em;
  /* the soft halo the Spinception and Glitter Jar pages both give their
     kickers. It is a glow, not a color, so it carries no meaning and the
     text reads the same with it switched off; the light pages leave it
     off because a halo on cream just looks like a printing fault. */
  text-shadow: var(--kicker-glow, none);
}
.lead { font-size: 1.2rem; color: var(--ink-soft); max-width: 58ch; }

/* ---------------------------------------------------------------- nav */
.site-head { border-bottom: 1px solid transparent; background: var(--ground); position: sticky; top: 0; z-index: 60; }
.site-head.scrolled { border-bottom-color: var(--line); }
.head-in { display: flex; align-items: center; gap: 20px; min-height: 72px; position: relative; }
.brand { display: inline-flex; align-items: center; gap: 6px; text-decoration: none; color: var(--ink); padding: 8px 0; margin-right: auto; }
.brand img, .brand picture { width: 44px; height: 44px; }
.brand span {
  font-family: var(--display); font-size: 1.85rem; font-weight: 600;
  letter-spacing: -.01em;
}
@media (max-width: 400px) { .brand span { font-size: 1.6rem; } }
.brand:hover { color: var(--mint-ink); }
.nav-links { display: flex; gap: 4px; }
.nav-links a {
  color: var(--ink-soft); text-decoration: none; font-weight: 700; font-size: .98rem;
  padding: 10px 14px; border-radius: 10px; min-height: 44px; display: inline-flex; align-items: center;
}
.nav-links a:hover { color: var(--mint-ink); background: var(--mint-soft); }
.nav-links a[aria-current="page"] { color: var(--mint-ink); background: var(--mint-soft); }
.nav-toggle {
  display: none; background: var(--paper); border: 1px solid var(--line-strong); color: var(--ink);
  border-radius: 10px; min-width: 44px; min-height: 44px; padding: 8px 16px;
  font: 700 .98rem var(--body); cursor: pointer;
}
@media (max-width: 760px) {
  .nav-toggle { display: inline-flex; align-items: center; }
  .nav-links {
    display: none; position: absolute; top: 100%; left: 0; right: 0; flex-direction: column;
    background: var(--paper); border: 1px solid var(--line); border-radius: 0 0 14px 14px;
    padding: 8px; box-shadow: var(--shadow);
  }
  .site-head.open .nav-links { display: flex; }
  .nav-links a { min-height: 48px; }
}

/* ---------------------------------------------------------------- hero */
.hero { padding-block: 64px 40px; }
.hero-grid { display: grid; grid-template-columns: 1.05fr .95fr; gap: 56px; align-items: center; }
.hero p.lead { margin-top: .8em; }
.hero-art { justify-self: center; width: min(100%, 420px); }
.hero-art img { width: 100%; }
@media (max-width: 860px) {
  .hero { padding-block: 40px 24px; }
  .hero-grid { grid-template-columns: 1fr; gap: 32px; }
  .hero-art { width: min(72%, 320px); order: -1; }
}

/* The one page-load moment: the fronds settle, from a fully visible state.
   Nothing is ever parked invisible, so the first frame is the finished page. */
@media (prefers-reduced-motion: no-preference) {
  .hero-art img { animation: settle 900ms cubic-bezier(.22, .9, .3, 1.05) 1 both; transform-origin: 50% 88%; }
  @keyframes settle {
    from { transform: scale(.94) rotate(-2.5deg); }
    60%  { transform: scale(1.012) rotate(.6deg); }
    to   { transform: none; }
  }
}

.btn {
  display: inline-flex; align-items: center; gap: 10px;
  background: var(--mint-ink); color: var(--on-mint-ink);
  font-weight: 800; text-decoration: none;
  padding: 14px 24px; border-radius: 999px; min-height: 56px;
  border: 1px solid transparent;
}
.btn:hover { color: var(--on-mint-ink); filter: brightness(1.12); }
.btn.ghost { background: transparent; color: var(--mint-ink); border-color: var(--line-strong); }
.btn.ghost:hover { background: var(--mint-soft); filter: none; }
.btn-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 1.4em; }

/* ------------------------------------------------------------ sections */
.section { padding-block: 80px; }
.section.tint { background: var(--mint-soft); }
/* A whole page on the home page's mint band rather than the cream ground
   (Anna, 13 Sep 2026, for the privacy page). The header and footer take
   --ground too, so the page is one field of mint top to bottom.
   Measured on the light tint #D6F5EA: body ink 11.8:1, ink-soft 6.5:1,
   links 4.6:1. The one color that does NOT clear AA there is `muted`, at
   4.4:1, so a tinted page must not use it, and none currently does. In dark
   mode the same swap gives #1E3A33, where everything clears 6:1.
   A `.tint` section would vanish on such a page; there is none. */
:root[data-tint="mint"] { --ground: var(--mint-soft); }
/* ...but the header stays white above it, so the brand sits on the same
   ground it does on every other page and the mint reads as the page rather
   than as a change of chrome (Anna, 13 Sep 2026). */
:root[data-tint="mint"] .site-head { background: var(--paper); }
.section-head { max-width: 60ch; margin-bottom: 36px; }

/* ---------------------------------------------------------------- shelf */
.shelf { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 22px; align-items: stretch; }
/* four in the coming row: 4 across wide, 2x2 on a laptop, one column on a phone */
/* five in the coming row since Rug Wash (16 Sep 2026): 5 across wide, 3 on a
   laptop so five splits 3 + 2 rather than 2 + 2 + 1, one column on a phone */
.shelf.four { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 1000px) { .shelf.four { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 760px) { .shelf, .shelf.four { grid-template-columns: 1fr; } }
/* flex, not grid, so the link can be pushed to the bottom of every card and
   the three "About …" links sit on one line however long the blurbs are */
.app-card {
  position: relative;
  display: flex; flex-direction: column; gap: 10px;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 20px 22px 24px; box-shadow: var(--shadow);
  /* Centred, name and all (Anna, 17 Sep 2026). The chip and the "About ..."
     link are both width:max-content, so they need centring by margin rather
     than by text-align. */
  text-align: center;
}
.app-card .chip, .app-card .more { margin-inline: auto; }
.app-card .art { background: var(--mint-soft); border-radius: 14px; padding: 6%; margin-bottom: 4px; }
.app-card .art img { width: 100%; aspect-ratio: 1; object-fit: contain; }
.app-card h3 { margin: 0; }
.app-card p { margin: 0; color: var(--ink-soft); font-size: 1rem; }
.app-card .more { font-weight: 800; text-decoration: none; margin-top: auto; padding-top: 6px; width: max-content; }
.app-card .more::after { content: ""; position: absolute; inset: 0; border-radius: var(--r-card); }
.app-card .more span { text-decoration: underline; text-underline-offset: .15em; }
.app-card:hover { box-shadow: var(--shadow-lift); }
.app-card:has(.more:focus-visible) { outline: 3px solid var(--mint-deep); outline-offset: 3px; }
.app-card .more:focus-visible { outline: none; }
@media (prefers-reduced-motion: no-preference) {
  .app-card { transition: transform .18s ease, box-shadow .18s ease; }
  .app-card:hover { transform: translateY(-2px); }
}

.chip {
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--apricot); color: var(--on-mint);
  font-size: .78rem; font-weight: 800; letter-spacing: .05em; text-transform: uppercase;
  padding: 5px 12px; border-radius: 999px;
  /* width: max-content ALONE let "Free on iPhone and Android" run 67px past
     the card at 1024px, where the shelf is at its narrowest per column
     (Anna, 17 Sep 2026). max-content still sizes the pill to its text when
     there is room; max-width pins it to the card when there is not, and the
     text wraps inside the pill rather than out of it. */
  width: max-content; max-width: 100%; text-align: center; line-height: 1.25;
}

/* ---------------------------------------------------------------- about */
.values { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 28px 44px; }
@media (max-width: 720px) { .values { grid-template-columns: 1fr; } }
.value h3 { margin-bottom: .2em; }
.value p { color: var(--ink-soft); margin: 0; }

/* ------------------------------------------------------------ app pages */
/* RELATIVE, or the floating art never appears. .hero-float is absolute with
   inset:0, so it needs a positioned ancestor; without one it collapsed to
   0x0 and every app page's decorative art was in the DOM, loaded, and
   invisible. Found 16 Sep 2026, when Anna asked to change Barometer's. */
.app-hero { padding-block: 56px 32px; position: relative; }
.app-hero .lead { margin-top: .7em; }
.app-hero .chip { margin-top: .4em; }
.app-hero-grid { display: grid; grid-template-columns: 1.05fr .95fr; gap: 48px; align-items: center; }
.app-hero-art { justify-self: center; width: min(100%, 360px); background: var(--mint-soft); border-radius: 24px; padding: 8%; }
@media (max-width: 860px) { .app-hero-grid { grid-template-columns: 1fr; gap: 28px; } .app-hero-art { width: min(70%, 300px); order: -1; } }
.will { list-style: none; padding: 0; margin: 0; display: grid; gap: 14px; }
.will li { padding-left: 30px; position: relative; }
.will li::before {
  content: ""; position: absolute; left: 0; top: .55em;
  width: 12px; height: 12px; border-radius: 4px; background: var(--mint);
  border: 1px solid var(--mint-deep);
}
.will b { display: block; }
.will span { color: var(--ink-soft); }
.note {
  background: var(--paper); border: 1px solid var(--line); border-left: 4px solid var(--apricot);
  border-radius: 12px; padding: 14px 18px; max-width: 66ch;
}
.note p:last-child { margin-bottom: 0; }
.back { display: inline-flex; align-items: center; gap: 8px; font-weight: 800; text-decoration: none; }
.back span { text-decoration: underline; text-underline-offset: .15em; }

/* --------------------------------------------------------------- table */
.table-scroll { overflow-x: auto; }
table { border-collapse: collapse; width: 100%; font-size: .96rem; }
th, td { text-align: left; vertical-align: top; padding: 12px 14px; border-bottom: 1px solid var(--line); }
th { font-size: .8rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }

/* -------------------------------------------------------------- footer */
.site-foot { border-top: 1px solid var(--line); padding-block: 36px 48px; margin-top: 40px; color: var(--muted); font-size: .95rem; }
.foot-in { display: flex; flex-wrap: wrap; gap: 20px 40px; align-items: center; }
.foot-brand { display: inline-flex; align-items: center; gap: 6px; color: var(--ink); text-decoration: none; font-weight: 700; font-size: 1.1rem; }
.foot-brand img, .foot-brand picture { width: 34px; height: 34px; }
.foot-links { display: flex; flex-wrap: wrap; gap: 8px 22px; margin-left: auto;
  /* min-width:0 lets this shrink below its min-content width so the links
     wrap instead of running past the screen edge; a flex item defaults to
     min-width:auto, which refuses to go narrower than its widest child and
     pushed "Say and See" off the right of a 400px phone. */
  min-width: 0; }
/* On a phone there is no room to push the links right, and the brand plus the
   copyright plus the support address will not sit on one line at 320px — so
   the footer stops being a row and becomes a stack. */
@media (max-width: 640px) {
  .foot-in { flex-direction: column; align-items: flex-start; gap: 14px; }
  .foot-links { margin-left: 0; }
}
.site-foot a { color: var(--muted); }
.site-foot a:hover { color: var(--mint-ink); }

/* ----------------------------------------------------------------- 404 */
.lost { text-align: center; padding-block: 80px 40px; }
.lost img { width: min(220px, 50vw); margin: 0 auto 24px; }
.lost .btn-row { justify-content: center; }
.lost p { margin-inline: auto; }

@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

/* Windows High Contrast: keep the card edges real, and never rely on a
   background-image for anything that carries meaning. */
@media (forced-colors: active) {
  .app-card, .note, .nav-toggle { border: 1px solid CanvasText; }
  .chip { border: 1px solid CanvasText; }
}

/* ================================================================
   App pages — the real screenshots and the apps' own art
   ================================================================ */

/* A phone frame. The bezel is dark in BOTH themes (its own token), because a
   screenshot needs a dark surround to read as a device rather than a crop. */
.phone {
  width: 100%; max-width: 300px; margin-inline: auto;
  background: var(--bezel); border: 2px solid var(--bezel-edge);
  border-radius: 30px; padding: 9px; box-shadow: var(--shadow-lift);
}
.phone img { border-radius: 22px; width: 100%; }
.shot { margin: 0; }
.shot figcaption {
  text-align: center; color: var(--muted); font-size: .92rem;
  margin: 14px auto 0; max-width: 36ch;
}
.shot figcaption b { color: var(--ink); }

/* rows of screenshots — the count is the class, so nothing stretches */
.shots { display: grid; gap: 32px 26px; }
.shots.two { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 720px; margin-inline: auto; }
.shots.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.shots.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1000px) { .shots.four { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 860px)  { .shots.three { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .shots, .shots.two, .shots.three, .shots.four { grid-template-columns: 1fr; } }

/* text beside a screenshot, alternating down the page */
.side { display: grid; grid-template-columns: 1fr 1fr; gap: 56px; align-items: center; }
.side.flip > .shot { order: -1; }
@media (max-width: 860px) {
  .side, .side.flip { grid-template-columns: 1fr; gap: 32px; }
  .side.flip > .shot { order: 0; }
}

/* a strip of the app's own icons, on tinted tiles */
.art-strip { list-style: none; padding: 0; margin: 0; max-width: none;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 12px; }
.art-strip li { background: var(--mint-soft); border-radius: 14px; aspect-ratio: 1; display: grid; place-items: center; padding: 14%; }
.art-strip img { width: 100%; height: 100%; object-fit: contain; }
.art-note { color: var(--muted); font-size: .92rem; margin-top: 14px; }

/* Say and See's paintings sit on paper, the way they sit on its cream page */
.paints { list-style: none; padding: 0; margin: 0; max-width: none;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); gap: 14px; }
.paints li { background: var(--paper); border: 1px solid var(--line); border-radius: 14px; padding: 12px; text-align: center; }
.paints img { width: 100%; aspect-ratio: 1; object-fit: contain; }
.paints span { display: block; font-family: var(--display);
  font-weight: 600; font-size: .95rem; color: var(--ink-soft); margin-top: 6px; }

/* a numbered sequence — used only where the order is real */
.steps { list-style: none; counter-reset: step; padding: 0; margin: 0; max-width: none;
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 20px; }
.steps.four { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1000px) { .steps.four { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 860px) { .steps { grid-template-columns: 1fr; } }
@media (max-width: 620px) { .steps.four { grid-template-columns: 1fr; } }
.steps li { counter-increment: step; background: var(--paper); border: 1px solid var(--line);
  border-radius: var(--r-card); padding: 20px 22px; }
.steps li::before {
  content: counter(step); display: grid; place-items: center;
  width: 40px; height: 40px; border-radius: 12px;
  background: var(--mint); color: var(--on-mint);
  font-family: var(--display); font-weight: 600; font-size: 1.3rem;
  margin-bottom: 12px;
}
.steps b { display: block; margin-bottom: 2px; }
.steps span { color: var(--ink-soft); font-size: .98rem; }

/* the app's own wordmark at the top of its page */
.app-mark { max-width: 300px; margin-bottom: 18px; }
.app-icon { width: 72px; height: 72px; border-radius: 18px; margin-bottom: 16px; }
/* The app icon set INSIDE the h1, so the mark and the name read as one
   lockup and the page needs no separate wordmark graphic. It is sized in em
   so it tracks the heading's own clamp() rather than being pinned to a pixel
   size, and it sits on the text baseline rather than the line box, which is
   what stops it riding high next to a cap-height word. Decorative: the
   heading already says the name, so the alt is empty. */
h1 .title-icon {
  width: .9em; height: .9em; border-radius: .18em;
  display: inline-block; vertical-align: -.08em;
  margin-right: .28em; margin-bottom: 0;
}

/* a plain figure for the printed-report pages, which are paper not phone */
.paper-shot { background: var(--paper); border: 1px solid var(--line); border-radius: 14px;
  padding: 10px; box-shadow: var(--shadow); }
.paper-shot img { border-radius: 8px; width: 100%; }

/* an app's own launcher icon, given room to be looked at */
.icon-feature {
  display: grid; grid-template-columns: 160px 1fr; gap: 28px; align-items: center;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 24px 28px; margin-top: 48px;
}
.icon-feature img { width: 160px; height: 160px; border-radius: 22px; }
.icon-feature h3 { margin-bottom: .2em; }
.icon-feature p { margin: 0; color: var(--ink-soft); }
@media (max-width: 620px) { .icon-feature { grid-template-columns: 1fr; justify-items: center; text-align: center; } }

/* Muted text never sits on a tinted band (4.4:1 there, below AA). The header
   comment promised this and the app pages then broke it with .art-note and
   figcaptions inside .tint — caught by the 2026-09-10 audit. Ink-soft is
   6.5:1 on mint-soft. */
.tint .art-note, .tint .shot figcaption, .tint .muted { color: var(--ink-soft); }

/* Link arrows are inline SVG, not the → and ← characters: neither Outfit
   nor Nunito carries U+2190/U+2192, so the glyphs fell back to a system font
   and changed weight beside the label. Found by the 2026-09-10 audit. */
.arr { display: inline-block; vertical-align: -3px; margin-inline: 2px; }

/* WCAG 2.2 pass (2026-09-10 assessment).
   2.4.11 Focus Not Obscured: the sticky header is 72px tall, so an anchor
   jump (#apps) or a Tab landing on a link near the top could scroll it under
   the header. scroll-padding keeps the target below it.
   2.5.8 Target Size: footer links were ~20px tall inline text; 24px is the
   minimum, and 44 costs nothing. */
html { scroll-padding-top: 88px; }
.foot-links a { display: inline-flex; align-items: center; min-height: 44px; padding-inline: 4px; }

/* ================================================================
   A shipped app on the shelf (Glitter Jar, 13 Sep 2026)
   ================================================================ */
/* the chip for an app you can install today: mint, not apricot, so the
   two states read apart by lightness as well as word */
.chip.live { background: var(--mint); color: var(--on-mint); }
/* the shelf now has two rows: available, then coming. Same card, own row. */
/* The one shipped app gets its own row above the coming ones, centered. It
   used to be deliberately wider than them; Anna asked on 17 Sep 2026 for the
   same size, so the track is now exactly one column of the row below --
   container minus the two gaps, divided by three. The old calc was also
   stale: it was written against a five-across row and that row is three
   across now, so "a little bigger" had quietly become a good deal smaller.
   Below 760px both rows are a single full-width column anyway. */
.shelf.one { justify-content: center; grid-template-columns: minmax(0, calc((100% - 2 * 22px) / 3)); }
@media (max-width: 760px) { .shelf.one { grid-template-columns: minmax(0, 1fr); } }
.shelf-label { font-family: var(--body); font-size: .8rem; font-weight: 800; letter-spacing: .09em; text-transform: uppercase; color: var(--mint-deep); margin: 40px 0 14px; }
.shelf-label:first-of-type { margin-top: 0; }
/* the Google Play badge is drawn in SVG so it needs no image and no
   external request; a real <a> with the store URL, 56px tall */
.store-badge { display: inline-flex; align-items: center; gap: 12px; background: #08080a; color: #fff; border: 1.5px solid #5f5b64; border-radius: 12px; padding: 8px 18px 8px 14px; text-decoration: none; min-height: 56px; }
.store-badge:hover { color: #fff; border-color: #9a95a3; }
.store-badge .pre { display: block; font-size: .66rem; letter-spacing: .12em; text-transform: uppercase; color: #cfcbd6; line-height: 1.1; }
.store-badge .name { display: block; font-size: 1.25rem; font-weight: 700; line-height: 1.1; }
.store-badge svg { width: 28px; height: 30px; flex: none; }
/* the badges keep their own brand colors on every app palette: they are the
   stores' marks, not ours, so they never take a page token */
.store-badge, .store-badge:hover { background: #08080a; color: #fff; }
.store-badge .pre, .store-badge .name { color: inherit; }
/* the charms strip sits on the app's own night, so the enamel reads as it
   does in the jar; a dark surface in BOTH themes, like the phone bezel */
/* auto-fill was wrong here: it fills the column with as many 64px tracks as
   fit, so three charms sat in the left third of a wide, mostly empty bar.
   Three CAPPED tracks make one per charm, each no wider than 116px, and
   `width: fit-content` shrinks the box to exactly those three. The tracks
   are minmax(0, …) rather than auto-fit so a narrow column squeezes the row
   instead of wrapping it into an unbalanced 2 + 1. */
.charms { list-style: none; padding: 16px; margin: 0 auto; max-width: 100%; background: var(--bezel); border-radius: 18px;
  display: grid; grid-template-columns: repeat(3, minmax(0, 116px)); justify-content: center;
  width: fit-content; gap: 10px; }
.charms li { aspect-ratio: 1; display: grid; place-items: center; padding: 10%; }
.charms img { width: 100%; height: 100%; object-fit: contain; filter: drop-shadow(0 4px 8px rgba(0,0,0,.5)); }

/* ---------------------------------------------- Say and See: a turn
   The "dog" sequence. The tiles and the painting start hidden from the
   sequence's point of view but are ALWAYS in the DOM and always readable:
   `.is-waiting` only dims and lifts them, so with JS off, or with the
   animation never played, a visitor still sees d-o-g and the painting.
   Nothing here is the only way to get any information. */
.turn { display: grid; gap: 22px; justify-items: center; }
.turn-stage {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: center;
  gap: 18px 26px; width: 100%;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 30px 24px; box-shadow: var(--shadow);
}
.turn-tiles { display: flex; gap: 10px; margin: 0; padding: 0; list-style: none; max-width: none; }
.turn-tiles li {
  width: 74px; height: 88px; display: grid; place-items: center;
  background: var(--paper); border: 2px solid var(--line-strong); border-radius: 12px;
  box-shadow: 0 3px 0 var(--line-strong);
  font-family: var(--display); font-size: 2.6rem; font-weight: 700; line-height: 1;
  color: var(--ink);
}
.turn-pic { margin: 0; width: min(260px, 60vw); }
.turn-pic img { width: 100%; height: auto; }
.turn-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 14px 18px; justify-content: center; }
.turn-status { margin: 0; color: var(--ink-soft); font-weight: 700; min-height: 1.6em; }
.turn .art-note { text-align: center; max-width: 68ch; }
@media (max-width: 420px) {
  .turn-tiles li { width: 58px; height: 70px; font-size: 2rem; }
}

/* the played state: each piece is dimmed until its own moment arrives */
.turn.is-armed .turn-tiles li,
.turn.is-armed .turn-pic { opacity: .22; }
.turn.is-armed .turn-tiles li.lit,
.turn.is-armed .turn-pic.lit { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .turn-tiles li, .turn-pic { transition: opacity .18s ease, transform .18s ease; }
  .turn.is-armed .turn-tiles li { transform: translateY(-6px); }
  .turn.is-armed .turn-tiles li.lit { transform: translateY(0); }
  .turn.is-armed .turn-pic { transform: scale(.94); }
  .turn.is-armed .turn-pic.lit { transform: scale(1); }
}

/* ================================================================
   PER-APP PALETTES
   Anna, 13 Sep 2026: "I liked how the glitter jar page for groundmass was
   done entirely in glitter jar colors — can you change it back and make the
   other pages also match their original pages."

   So each app page wears its own app's world: the colors, the display face
   and the background texture from that app's own product page under
   /devapps. The studio pages (home, privacy, 404) keep the mint system.

   HOW IT WORKS. Every component in this file draws from tokens and never
   names a hex, so an app page only has to redefine the tokens. The page
   carries data-app on <html>; each block below is a COMPLETE palette.

   These blocks sit at the END of the file on purpose. `:root[data-app=...]`
   and `:root:not([data-theme="light"])` have the same specificity, so source
   order decides — an app palette must come after the dark-mode block, or a
   viewer whose phone is in dark mode would see mint tokens bleed through.

   Each app page is SINGLE-THEME, like the original it comes from: Glitter
   Jar, Barometer and Firefly Jar are dark whatever the OS says; Cross Count
   and Say and See are light. Every color is stated, so nothing is inherited
   from the host.

   Contrast, computed 13 Sep 2026 (WCAG relative luminance). Worst pair in
   each palette: glitterjar 4.69 (page text on the violet button) · barometer
   6.30 (muted on the surface) · crosscount 4.64 (ink on rose) · sayandsee
   4.66 (link blue on the tinted band) · fireflyjar 6.84 (muted on the
   field). All at or above 4.5:1.
   ================================================================ */

@font-face {                       /* Glitter Jar's own display face */
  font-family: "Fredoka";
  src: url("../assets/fonts/fredoka.woff2") format("woff2");
  font-weight: 300 700;
  font-display: swap;
}
@font-face {                       /* Say and See's own letter face */
  font-family: "Andika";
  src: url("../assets/fonts/andika.woff2") format("woff2");
  font-weight: 400;
  font-display: swap;
}
@font-face {
  font-family: "Andika";
  src: url("../assets/fonts/andika-bold.woff2") format("woff2");
  font-weight: 700;
  font-display: swap;
}

/* ---------------------------------------------- Glitter Jar: stardust night */
:root[data-app="glitterjar"] {
  --kicker-glow: 0 0 22px rgba(179, 136, 255, .45);
  --grad-a: #b388ff; --grad-b: #d4a2f5; --grad-c: #ff8fc7;
  --ground: #0a0d20; --paper: #161c3d; --mint-soft: #0d1129;
  --ink: #f3eeff; --ink-soft: #ddc9ff; --muted: #b3aada;
  --mint: #2ee6a8;                 /* the app's aurora mint: the "free" chip */
  --mint-deep: #b388ff;            /* links and kickers: the signature violet */
  --mint-ink: #7c4ddb;             /* solid buttons */
  --apricot: #ffd88a;              /* star gold */
  --apricot-deep: #ffd88a;
  --on-mint: #0a0d20; --on-mint-ink: #f3eeff;
  --line: rgba(179, 136, 255, .16); --line-strong: rgba(179, 136, 255, .42);
  --bezel: #05060f; --bezel-edge: #2a2358;
  --shadow: 0 18px 50px rgba(4, 4, 20, .55);
  --shadow-lift: 0 24px 60px rgba(4, 4, 20, .7);
  --display: "Fredoka", "Trebuchet MS", system-ui, sans-serif;
  color-scheme: dark;
}
/* the app's starfield, and one wide bloom of violet light */
:root[data-app="glitterjar"] body::before {
  content: ""; position: fixed; inset: -20vh -10vw; z-index: -2; pointer-events: none;
  background-image:
    radial-gradient(1.6px 1.6px at 18% 12%, rgba(255,255,255,.75), transparent 60%),
    radial-gradient(1.3px 1.3px at 72% 8%,  rgba(221,201,255,.70), transparent 60%),
    radial-gradient(1.8px 1.8px at 41% 33%, rgba(255,216,138,.60), transparent 60%),
    radial-gradient(1.2px 1.2px at 88% 41%, rgba(255,255,255,.55), transparent 60%),
    radial-gradient(1.5px 1.5px at 9% 58%,  rgba(255,143,199,.55), transparent 60%),
    radial-gradient(1.2px 1.2px at 61% 66%, rgba(255,255,255,.60), transparent 60%),
    radial-gradient(1.7px 1.7px at 31% 84%, rgba(221,201,255,.60), transparent 60%),
    radial-gradient(1.3px 1.3px at 79% 91%, rgba(255,255,255,.50), transparent 60%);
  background-size: 100% 100%;
}
:root[data-app="glitterjar"] body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(1200px 700px at 50% -12%, rgba(124, 77, 219, .30), transparent 62%),
    radial-gradient(900px 600px at 88% 22%, rgba(255, 143, 199, .09), transparent 60%);
}
@media (prefers-reduced-motion: no-preference) {
  :root[data-app="glitterjar"] body::before { animation: driftStars 90s linear infinite alternate; }
}
@keyframes driftStars {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-3vw, -4vh, 0); }
}

/* ------------------------------------------- Barometer: dark ground and lamp */
:root[data-app="barometer"] {
  --kicker-glow: 0 0 20px rgba(224, 196, 138, .32);
  --grad-a: #e0c48a; --grad-b: #efdcb4; --grad-c: #c9a86a;
  --ground: #101a16; --paper: #18241f; --mint-soft: #18241f;
  --ink: #e7ece8; --ink-soft: #c3ccc7; --muted: #97a69e;
  --mint: #e0c48a; --mint-deep: #e0c48a; --mint-ink: #e0c48a;
  --apricot: #e0c48a; --apricot-deep: #e0c48a;
  --on-mint: #101a16; --on-mint-ink: #101a16;
  --line: #3d5147; --line-strong: #52685c;
  --bezel: #0b110e; --bezel-edge: #2c3a33;
  --shadow: 0 18px 50px rgba(0, 0, 0, .5);
  --shadow-lift: 0 24px 60px rgba(0, 0, 0, .62);
  color-scheme: dark;
}
/* NO FELT ON THE PAGE. It used to tile the app's own felt.jpg under
   everything, and the result was felt on felt: every screenshot already
   carries the app's wallpaper, so the page repeated the same material behind
   it and the two competed (Anna, 16 Sep 2026). Worse, the tile rendered a
   saturated #0e3222, much greener than the #101a16 this palette declares, so
   the page was not even the colour it said it was. Flat ground now: the felt
   inside a screenshot reads as the app's material, and the page is the dark
   it was meant to be. */
:root[data-app="barometer"] body { background: var(--ground); }

/* ------------------------------------------- Cross Count: linen and rose */
:root[data-app="crosscount"] {
  --grad-a: #b23063; --grad-b: #8f2b52; --grad-c: #6f2742;
  --ground: #f5efe3; --paper: #fffdf8; --mint-soft: #ebe3d3;
  --ink: #2e2d4d; --ink-soft: #4a4966; --muted: #5f5e78;
  --mint: #e07a99;                 /* the wordmark's dusty rose */
  --mint-deep: #b23063;            /* links: the app's small-emphasis rose */
  --mint-ink: #6f2742;             /* solid buttons: the wordmark's plum */
  --apricot: #f2b9c9; --apricot-deep: #6f2742;
  --on-mint: #2e2d4d; --on-mint-ink: #fffdf8;
  --line: rgba(111, 39, 66, .14); --line-strong: rgba(111, 39, 66, .32);
  --bezel: #52192f; --bezel-edge: #7b3b55;
  --shadow: 0 14px 34px rgba(82, 25, 47, .12);
  --shadow-lift: 0 22px 48px rgba(82, 25, 47, .18);
  color-scheme: light;
}
/* woven cloth: two faint thread directions, and the app's own two blooms */
:root[data-app="crosscount"] body::before {
  content: ""; position: fixed; inset: 0; z-index: -2; pointer-events: none;
  background:
    repeating-linear-gradient(90deg, rgba(111,39,66,.040) 0 1px, transparent 1px 4px),
    repeating-linear-gradient(0deg,  rgba(111,39,66,.032) 0 1px, transparent 1px 4px);
}
:root[data-app="crosscount"] body::after {
  content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(1100px 640px at 4% -8%, rgba(255,215,186,.55), transparent 62%),
    radial-gradient(900px 600px at 100% 104%, rgba(214,224,210,.60), transparent 60%);
}

/* --------------------------------------- Say and See: the cream page, blue */
:root[data-app="sayandsee"] {
  --grad-a: #2F6AA6; --grad-b: #27598C; --grad-c: #1E4B7A;
  --ground: #FBF3DF; --paper: #FFFBF0; --mint-soft: #F2E9D6;
  --ink: #3B3225; --ink-soft: #5C5142; --muted: #6E6350;
  --mint: #F2B134;                 /* the honey accent */
  --mint-deep: #2F6AA6;            /* links: the picture-book blue */
  --mint-ink: #27598C;
  --apricot: #F2B134; --apricot-deep: #8A5A00;
  --on-mint: #3B3225; --on-mint-ink: #FFFBF0;
  --line: #E6D9B8; --line-strong: #D8C9A6;
  --bezel: #2b2622; --bezel-edge: #4a4239;
  --shadow: 0 2px 6px rgba(59, 50, 37, .08), 0 10px 30px rgba(59, 50, 37, .08);
  --shadow-lift: 0 18px 40px rgba(59, 50, 37, .16);
  --display: "Andika", "Nunito", system-ui, sans-serif;
  color-scheme: light;
}

/* ------------------------------------- Rug Wash: steel blue, teal, soap pink
   The app's own palette (rug/android_port/game/ui/ui_theme.gd), whose header
   explains the cool ground: every warm rug reads louder against it. Roles from
   the same file: primary is the teal, secondary is the soap pink sampled from
   the bar on the New rug button, gold is the restored-grade sparkle. Two of
   the app's greys fall short of 4.5:1 on its own ground (MUTED 4.4, FAINT 2.1),
   so the page uses a darker slate for secondary text; links take a teal one
   step deeper than the app's ACCENT (2.6:1 on the ground) so they clear AA.
   Measured 16 Sep 2026: ink 11.0:1, secondary text 6.5:1, links 4.9:1, ink on
   the pink chip 5.4:1, button text 4.8:1. Fredoka is already vendored. */
:root[data-app="rugwash"] {
  /* The heading gradient ends in the app's soap pink rather than the plum it
     used to (Anna, 16 Sep 2026). The app's own pink is #ef7aa6, which is only
     2.1:1 on this ground and unreadable as text, so the stop is that exact
     hue and saturation (337deg, 79%) taken down to 42% lightness: the same
     pink, dark enough to read. Measured 4.75:1, against a 3:1 bar for
     headings and the site's own 4.5:1 house rule.

     It STARTS on the app's own darker teal, ACCENT_DEEP #157c88 (Anna,
     16 Sep 2026), deepens a step, then turns pink — the same shape the other
     light pages use, where the ramp gets darker rather than lighter. The
     first stop is 3.9:1, which clears the 3:1 these headings are held to;
     the two it runs into are 4.9 and 4.8. */
  --grad-a: #157c88; --grad-b: #116a75; --grad-c: #bf1756;
  --ground: #dce5ee; --paper: #f5f9fc; --mint-soft: #e7eff6;
  --ink: #1f2d3b; --ink-soft: #3f5163; --muted: #3f5163;
  --mint: #ef7aa6;                 /* the soap pink: the "coming soon" chip */
  --mint-deep: #116a75;            /* links */
  --mint-ink: #157c88;             /* solid buttons */
  --apricot: #ef7aa6; --apricot-deep: #4d1230;
  --on-mint: #1f2d3b; --on-mint-ink: #f2fdff;
  --line: rgba(31, 45, 59, .14); --line-strong: rgba(31, 45, 59, .32);
  --bezel: #18222e; --bezel-edge: #2e4054;
  --shadow: 0 14px 34px rgba(24, 34, 46, .12);
  --shadow-lift: 0 22px 48px rgba(24, 34, 46, .18);
  --display: "Fredoka", "Trebuchet MS", system-ui, sans-serif;
  color-scheme: light;
}

/* ------------------------------------- Firefly Jar: the olive field, ember */
/* Anna, 13 Sep 2026: the olive ground "isn't right". The page no longer
   copies the app's near-black jar; it sits in the dusk the jar is carried
   out into. Deep blue-teal is the complement the chartreuse firefly and the
   amber ember both need, and it is unmistakably not the app's own color.
   Measured on this ground: body text 10.7:1, muted 7.4:1, the firefly green
   11.9:1, the ember 9.2:1. */
:root[data-app="fireflyjar"] {
  --kicker-glow: 0 0 22px rgba(200, 232, 91, .40);
  --grad-a: #C8E85B; --grad-b: #E2F08F; --grad-c: #FFB23D;
  --ground: #12212A; --paper: #0C171D; --mint-soft: #172A33;
  --ink: #D6D0BE; --ink-soft: #C6C0AE; --muted: #B5AE9C;
  --mint: #C8E85B;                 /* the early firefly */
  --mint-deep: #C8E85B;
  --mint-ink: #FFB23D;             /* buttons take the ember */
  --apricot: #FFB23D; --apricot-deep: #FFB23D;
  --on-mint: #0E100C; --on-mint-ink: #0E100C;
  --line: rgba(200, 232, 91, .16); --line-strong: rgba(200, 232, 91, .34);
  --bezel: #08131A; --bezel-edge: #23414F;
  --shadow: 0 18px 50px rgba(2, 8, 12, .55);
  --shadow-lift: 0 24px 60px rgba(2, 8, 12, .7);
  color-scheme: dark;
}

/* On a dark app page a pale mint tile would glow, so art tiles take the
   page's own panel color instead of the tint. */
:root[data-app="glitterjar"] .app-card .art,
:root[data-app="glitterjar"] .art-strip li,
:root[data-app="barometer"] .art-strip li,
:root[data-app="fireflyjar"] .art-strip li { background: var(--paper); }

/* The charm strip already sits on the bezel; on Glitter Jar's own night that
   would be a black box on near-black, so it takes the panel. */
:root[data-app="glitterjar"] .charms { background: var(--paper); }

/* On this palette --mint-ink is the SOLID-BUTTON violet: deep enough for pale
   text to sit ON it. As a text colour it fails -- 3.49:1 on the header's hover
   panel and 3.61:1 on the ground, against a 4.5:1 bar. The header, the brand
   and the footer links take the lighter signature violet instead, at 6.98:1
   and 7.22:1. Buttons are untouched; there it is still the background. */
:root[data-app="glitterjar"] .nav-links a:hover,
:root[data-app="glitterjar"] .nav-links a[aria-current="page"],
:root[data-app="glitterjar"] .brand:hover,
:root[data-app="glitterjar"] .site-foot a:hover { color: var(--mint-deep); }

/* ================================================================
   MOTION: the bobbing charms, the marquee, the stats, the hero video
   Anna, 13 Sep 2026: "the original glitter jar website had animations and
   bouncing charms and stuff — where did those go." They were never carried
   over; this is that liveliness, brought back from the app's own page.

   Two rules hold everywhere below:

   1. NOTHING IS EVER HIDDEN BY MOTION. A marquee is a strip of content in an
      overflow:hidden box, so a visitor who has asked for less motion would
      only see the first few charms. Under prefers-reduced-motion the track
      stops being a track and becomes an ordinary wrapping row, showing every
      charm. Content first; the animation is decoration on top.
   2. MOVING CONTENT CAN BE STOPPED (WCAG 2.2.2). The marquee scrolls for
      longer than five seconds, so it has a real Pause button, and it also
      pauses on hover and on keyboard focus inside it.
   ================================================================ */

/* -------------------------------------------------- floating hero charms */
.hero-float { position: absolute; inset: 0; pointer-events: none; }
.hero-float img {
  position: absolute; width: 58px; opacity: .8;
  filter: drop-shadow(0 8px 18px rgba(0, 0, 0, .55));
}
.hero-float img:nth-child(1) { top: 2%;  left: 2%; }
.hero-float img:nth-child(2) { top: 62%; left: -1%; width: 48px; }
.hero-float img:nth-child(3) { top: 14%; right: 2%; width: 64px; }
.hero-float img:nth-child(4) { top: 74%; right: 6%; }
.hero-float img:nth-child(5) { top: 38%; left: 6%; width: 44px; }
/* a sixth, for the pages that carry six */
.hero-float img:nth-child(6) { top: 88%; left: 22%; width: 50px; }
/* they sit in the hero's own margins; below that there is no room for them */
@media (max-width: 1280px) { .hero-float { display: none; } }

/* ------------------------------------------------------------- marquee */
.marquee { overflow: hidden; margin-top: 26px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 7%, #000 93%, transparent); }
/* the vertical padding has to clear the bob, or overflow:hidden shaves the
   tops and bottoms off the charms as they rise */
.marquee .track {
  display: flex; align-items: center; gap: 26px;
  /* max-width:none is load-bearing. The track is a <ul>, so the base
     `ul, ol { max-width: 66ch }` typography rule still applies and clamps the
     strip to about 660px. With 72 charms and 26px gaps that is far less than
     the row needs, so every item shrinks to ZERO WIDTH and the marquee renders
     as an empty band — which is exactly what shipped on 13 Sep 2026 until the
     li boxes were outlined and found to be 0px wide. */
  width: max-content; max-width: none;
  padding: 30px 12px; margin: 0; list-style: none;
}
.marquee .track img { width: 58px; height: 58px; object-fit: contain; flex: none; }
/* Rug Wash's strip is the rugs themselves, not icons: portrait, and the whole
   point is their colour, so they run at 150px tall and keep their own width
   (Anna, 16 Sep 2026: "include pretty colorful rugs") */
.marquee.tall .track img { width: auto; height: 150px; border-radius: 6px; box-shadow: var(--shadow); }
.marquee.tall .track { gap: 18px; }
.marquee-bar { display: flex; align-items: center; gap: 14px; margin-top: 6px; flex-wrap: wrap; }
.marquee-pause {
  background: var(--paper); color: var(--ink); border: 1px solid var(--line-strong);
  border-radius: 999px; min-height: 44px; padding: 8px 18px;
  font: 800 .92rem var(--body); cursor: pointer;
}
.marquee-pause:hover { border-color: var(--mint-deep); color: var(--mint-deep); }

@media (prefers-reduced-motion: no-preference) {
  .marquee .track { animation: slide 52s linear infinite; }
  .marquee .track img { animation: bob 7s ease-in-out infinite; }
  .marquee .track img:nth-child(2n) { animation-delay: -2.5s; }
  .marquee .track img:nth-child(3n) { animation-delay: -4.5s; }
  .hero-float img { animation: floaty 9s ease-in-out infinite; }
  .hero-float img:nth-child(2n) { animation-delay: -3s; }
  .hero-float img:nth-child(3n) { animation-delay: -5.5s; }
  /* hover, keyboard focus inside, and the button all stop it */
  .marquee:hover .track,
  .marquee:focus-within .track,
  .marquee.paused .track,
  .marquee.paused .track img { animation-play-state: paused; }
}
/* Asked for less motion: the strip becomes a plain wrapping row, so every
   charm is still on the page and nothing scrolls. */
@media (prefers-reduced-motion: reduce) {
  .marquee { overflow: visible; -webkit-mask-image: none; mask-image: none; }
  .marquee .track { flex-wrap: wrap; width: auto; justify-content: center; gap: 16px; padding: 16px 0; }
  .marquee-bar { display: none; }
}
@keyframes slide { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@keyframes bob {
  0%, 100% { transform: translateY(-9px) rotate(-4deg); }
  50%      { transform: translateY(9px)  rotate(5deg); }
}
@keyframes floaty {
  0%, 100% { transform: translateY(-10px) rotate(-5deg); }
  50%      { transform: translateY(10px)  rotate(6deg); }
}

/* --------------------------------------------------------------- stats */
.stats { list-style: none; padding: 0; margin: 30px 0 0; max-width: none;
  display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
@media (max-width: 720px) { .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.stats li { background: var(--paper); border: 1px solid var(--line); border-radius: 16px; padding: 18px 20px; }
.stats b {
  display: block; font-family: var(--display); font-weight: 700;
  font-size: 2.1rem; line-height: 1.05; color: var(--mint-deep);
  font-variant-numeric: tabular-nums;
}
.stats span { display: block; color: var(--ink-soft); font-size: .95rem; font-weight: 600; margin-top: 4px; }

/* ---------------------------------------------------------- hero video */
/* height:auto matters as much here as it does on an img: the width/height
   attributes are a ratio hint, and without this the element keeps its literal
   818px height while the width shrinks, letterboxing the poster. */
.phone video { border-radius: 22px; width: 100%; height: auto; display: block; }
.sound-row { display: flex; justify-content: center; margin-top: 14px; }
.sound-btn {
  display: inline-flex; align-items: center; gap: 9px;
  background: var(--paper); color: var(--ink); border: 1px solid var(--line-strong);
  border-radius: 999px; min-height: 44px; padding: 8px 18px;
  font: 800 .92rem var(--body); cursor: pointer;
}
.sound-btn:hover { border-color: var(--mint-deep); color: var(--mint-deep); }
.sound-btn svg { width: 18px; height: 18px; flex: none; }

/* a marquee whose items carry a word under the art (Say and See) */
.marquee.named .track { gap: 20px; }
.marquee.named .track li { flex: none; text-align: center; }
.marquee.named .track img { width: 96px; height: 96px; }
.marquee.named .track span {
  display: block; margin-top: 4px; font-family: var(--display);
  font-weight: 700; font-size: .95rem; color: var(--ink-soft);
}
@media (prefers-reduced-motion: no-preference) {
  .marquee.named .track li { animation: bob 7s ease-in-out infinite; }
  .marquee.named .track li:nth-child(2n) { animation-delay: -2.5s; }
  .marquee.named .track li:nth-child(3n) { animation-delay: -4.5s; }
  .marquee.named .track img { animation: none; }
  .marquee.paused .track li { animation-play-state: paused; }
}

/* wide key art, full width of the column */
.keyart { margin: 0; border-radius: var(--r-card); overflow: hidden; box-shadow: var(--shadow-lift); }
.keyart img { width: 100%; display: block; }
.keyart figcaption { color: var(--muted); font-size: .92rem; margin-top: 12px; text-align: center; }

/* the hero holds the floating art, so it is the positioning context */
.app-hero-grid, .hero-grid { position: relative; }

/* Every app page is that app's App Store Support URL, and Apple requires a
   Support URL to lead to real contact information (App Store Connect
   reference; guideline 1.5 wants "an easy way to contact you"). So the
   address sits in the footer of each app page, in the same place on every
   one — which is also WCAG 2.2's Consistent Help. */
.foot-support a { font-weight: 700; }

/* A second footer row for text that belongs to one app rather than to the
   studio -- who publishes Glitter Jar, and the two store trademarks. */
.foot-note { margin-top: 18px; }
.foot-note p { color: var(--muted); font-size: .84rem; line-height: 1.5; margin: 0 0 6px; max-width: 78ch; }
.foot-note p:last-child { margin-bottom: 0; }

/* ================================================================
   Cross Count, 15 Sep 2026: the hero lockup, the before-and-after
   sliders, the skein strip, the illustrated steps and the closing ask.
   All of it draws from the page's tokens; nothing names a hex.
   ================================================================ */

/* steps that carry the app's own art instead of a number */
.steps.art li::before { display: none; }
.steps.art img { width: 72px; height: 72px; margin-bottom: 12px; }

/* the picture that went in, tucked behind the phone like a print on a desk */
.hero-stack { position: relative; width: min(100%, 300px); margin-inline: auto; }
.hero-stack .phone { max-width: none; }
.hero-stack .polaroid {
  position: absolute; left: -30%; bottom: 8%; width: 46%;
  background: var(--paper); border: 1px solid var(--line);
  padding: 5% 5% 13%; box-shadow: var(--shadow-lift); rotate: -7deg;
}
.hero-stack .polaroid img { width: 100%; display: block; }
@media (max-width: 860px) { .hero-stack .polaroid { display: none; } }
@media (prefers-reduced-motion: no-preference) {
  .hero-stack .polaroid { animation: settle 900ms cubic-bezier(.22, .9, .3, 1.05) 1 both; transform-origin: 50% 100%; }
}

/* ---------------------------------------------------- before and after
   Two real images, the picture over the chart, the picture clipped at
   --pos. A native <input type=range> lies over the whole box at zero
   opacity: it is what makes the thing draggable with a finger or a mouse
   AND operable from the keyboard, and it is announced as a slider. With
   JS off the split simply stays at half, which still tells the story. */
.compares { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 30px 26px; align-items: start; }
@media (max-width: 860px) { .compares { grid-template-columns: 1fr; max-width: 420px; margin-inline: auto; } }
.compare { margin: 0; }
.photo-side { margin-top: 56px; }
.photo-side > .compare { width: 100%; max-width: 440px; justify-self: center; }
.compare-box {
  --pos: 50%;
  position: relative; overflow: hidden; border-radius: 16px;
  background: var(--paper); box-shadow: var(--shadow);
  user-select: none; -webkit-user-select: none; touch-action: pan-y;
}
.compare-box picture { position: absolute; inset: 0; }
.compare-box img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.compare-box img.before { clip-path: inset(0 calc(100% - var(--pos)) 0 0); }
.compare-handle {
  position: absolute; top: 0; bottom: 0; left: var(--pos); width: 3px; margin-left: -1.5px;
  background: var(--paper); box-shadow: 0 0 0 1px var(--line-strong); pointer-events: none;
}
.compare-handle::after {
  content: ""; position: absolute; top: 50%; left: 50%; width: 44px; height: 44px; margin: -22px 0 0 -22px;
  border-radius: 50%; background: var(--paper) center / 22px no-repeat;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%236f2742' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'><path d='M9 6l-6 6 6 6M15 6l6 6-6 6'/></svg>");
  border: 2px solid var(--mint-ink); box-shadow: var(--shadow);
}
.compare-tag {
  position: absolute; top: 10px; z-index: 1; pointer-events: none;
  font: 800 .72rem/1 var(--body); letter-spacing: .08em; text-transform: uppercase;
  padding: 7px 10px; border-radius: 999px; background: var(--paper); color: var(--ink);
  box-shadow: 0 0 0 1px var(--line);
}
.compare-tag.l { left: 10px; }
.compare-tag.r { right: 10px; }
.compare-range {
  position: absolute; inset: 0; z-index: 2; width: 100%; height: 100%; margin: 0;
  opacity: 0; cursor: ew-resize; -webkit-appearance: none; appearance: none; background: transparent;
}
.compare-box:has(.compare-range:focus-visible) { outline: 3px solid var(--mint-deep); outline-offset: 3px; }
.compare figcaption { text-align: center; color: var(--muted); font-size: .92rem; margin: 12px auto 0; max-width: 36ch; }
.compare figcaption b { color: var(--ink); }
.tint .compare figcaption { color: var(--ink-soft); }

/* -------------------------------------------------------- the skein strip
   Real DMC numbers with their published colors; each skein is a rounded
   slab of its own thread with a paper band across it, drawn in CSS. */
.skeins {
  list-style: none; padding: 0; margin: 48px 0 0; max-width: none;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(62px, 1fr)); gap: 14px 10px;
}
.skeins li { display: grid; justify-items: center; gap: 6px; }
.skeins li::before {
  content: ""; width: 100%; max-width: 56px; aspect-ratio: 1 / 1.7; border-radius: 12px;
  background:
    linear-gradient(0deg, transparent 40%, var(--paper) 40%, var(--paper) 58%, transparent 58%),
    linear-gradient(90deg, color-mix(in srgb, var(--c) 72%, black) 0%, var(--c) 28%, color-mix(in srgb, var(--c) 68%, white) 52%, var(--c) 78%, color-mix(in srgb, var(--c) 80%, black) 100%);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .10), var(--shadow);
}
.skeins span { font-family: var(--display); font-weight: 700; font-size: .86rem; color: var(--ink-soft); }
@supports not (color: color-mix(in srgb, red, blue)) {
  .skeins li::before { background:
    linear-gradient(0deg, transparent 40%, var(--paper) 40%, var(--paper) 58%, transparent 58%), var(--c); }
}

/* ------------------------------------------------------------- the ask */
.cta {
  display: flex; flex-wrap: wrap; align-items: center; gap: 18px 32px;
  background: var(--paper); border: 1px solid var(--line); border-radius: var(--r-card);
  padding: 24px 28px; margin: 28px 0; box-shadow: var(--shadow);
}
.cta > div { flex: 1 1 300px; }
.cta h3 { margin-bottom: .2em; }
.cta p { margin: 0; color: var(--ink-soft); }

/* ================================================================
   The Cross Count user guide (crosscount/guide/), 15 Sep 2026.
   A reference, read by lookup: a sticky contents column with a search box,
   and an article of topics. Screenshots are documentation here, so they
   sit in a plain frame rather than the marketing bezel. Every color is a
   page token; the page wears the Cross Count palette like its product page.
   ================================================================ */
.guide-wrap { display: grid; grid-template-columns: 272px minmax(0, 1fr); gap: 48px; align-items: start; padding-block: 36px 24px; }
.guide-side { position: sticky; top: 88px; max-height: calc(100vh - 104px); overflow: auto; padding-right: 4px; }
@media (max-width: 900px) {
  .guide-wrap { grid-template-columns: 1fr; gap: 24px; }
  .guide-side { position: static; max-height: none; overflow: visible; }
  .guide-toc[open] summary { margin-bottom: 8px; }
}
.guide-search input {
  width: 100%; min-height: 48px; padding: 10px 14px; border-radius: 12px;
  border: 1px solid var(--line-strong); background: var(--paper); color: var(--ink);
  font: 500 1rem var(--body);
}
.guide-search input::placeholder { color: var(--muted); }
.guide-count { font-size: .88rem; color: var(--ink-soft); margin: 8px 2px 0; min-height: 1.4em; }
.guide-toc { margin-top: 18px; }
.guide-toc summary {
  cursor: pointer; list-style: none; font-family: var(--display); font-weight: 700; font-size: 1.05rem;
  padding: 8px 2px; min-height: 44px; display: flex; align-items: center; gap: 8px;
}
.guide-toc summary::-webkit-details-marker { display: none; }
.guide-toc summary::before {
  content: ""; width: 9px; height: 9px; border-right: 2.5px solid var(--mint-deep); border-bottom: 2.5px solid var(--mint-deep);
  transform: rotate(-45deg); margin-right: 4px;
}
.guide-toc[open] summary::before { transform: rotate(45deg); }
.guide-toc ol { list-style: none; padding: 0; margin: 0; max-width: none; }
.guide-toc ol ol { padding-left: 14px; margin: 2px 0 8px; border-left: 2px solid var(--line); }
.guide-toc li { margin: 0; }
.guide-toc a { display: block; text-decoration: none; color: var(--ink-soft); font-size: .95rem; padding: 6px 8px; border-radius: 8px; min-height: 32px; }
.guide-toc > nav > ol > li > a { font-weight: 700; color: var(--ink); margin-top: 4px; }
.guide-toc a:hover { background: var(--mint-soft); color: var(--mint-ink); }
.guide-toc a.current { background: var(--mint-soft); color: var(--mint-ink); }

.guide-main { max-width: 820px; min-width: 0; }
.guide-head { padding-bottom: 8px; border-bottom: 2px solid var(--line-strong); margin-bottom: 8px; }
.guide-head h1 { font-size: clamp(2.2rem, 5vw, 3.4rem); }
.guide-meta { color: var(--muted); font-size: .95rem; }
.topic-group { padding-top: 40px; }
.topic-group > h2 { padding-bottom: 10px; border-bottom: 1px solid var(--line-strong); margin-bottom: 8px; }
.group-lead { color: var(--ink-soft); font-size: 1.08rem; }
.topic { padding: 22px 0 10px; border-top: 1px solid var(--line); scroll-margin-top: 96px; }
.topic:first-of-type { border-top: 0; }
.topic h3 { font-size: 1.45rem; margin-bottom: .3em; }
.topic .where { font-size: .92rem; color: var(--mint-deep); font-weight: 700; margin: 0 0 .9em; }
.topic ul, .topic ol { padding-left: 22px; }
.topic li { margin: .35em 0; }
.topic li b { font-weight: 800; }
/* THE NAME OF A CONTROL IS IN THE APP'S ROSE (Anna, 15 Sep 2026: "more pink
   text to highlight the names of buttons"). Every <b> inside a topic is a
   label as it appears on screen, so the color says "look for these words".
   mint-deep is the palette's link rose, 4.6:1 on the linen; a linked name
   keeps the underline so the two are still told apart. */
.topic p b, .topic li b, .topic dd b, .topic td b { color: var(--mint-deep); font-weight: 800; }
.topic a b { color: inherit; }
.also { font-size: .92rem; color: var(--ink-soft); margin-top: 1em; }
/* Basic beside Advanced: the same screen in each mode, tagged, and a table
   of every feature with a Yes in the app's rose or a dash. */
.mode-pairs { display: grid; gap: 26px; margin: 18px 0; }
.mode-pair { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 22px; max-width: 640px; }
.mode-pair .gshot { margin: 0; position: relative; }
.mode-pair .gshot img, .mode-pair .gshot figcaption { max-width: none; }
.mode-tag {
  display: inline-block; margin-bottom: 8px;
  font: 800 .72rem/1 var(--body); letter-spacing: .09em; text-transform: uppercase;
  padding: 6px 10px; border-radius: 999px;
}
.mode-tag.basic { background: var(--mint-soft); color: var(--ink); border: 1px solid var(--line-strong); }
.mode-tag.adv { background: var(--apricot); color: var(--apricot-deep); }
@media (max-width: 480px) { .mode-pair { grid-template-columns: 1fr; max-width: 300px; } }
.topic h4 { font-family: var(--display); font-size: 1.15rem; margin: 28px 0 8px; }
.howto { background: var(--paper); border: 1px solid var(--line); border-left: 4px solid var(--mint-deep); border-radius: 12px; padding: 14px 18px 6px; margin: 0 0 18px; }
.howto h4 { margin: 0 0 6px; }
.howto ol { margin: 0 0 6px; }
.howto .gshots.two { max-width: 520px; margin: 12px 0 8px; }
.gshot figcaption b { color: var(--mint-deep); }
.modes-table th[scope="col"]:not(:first-child), .modes-table td.yes, .modes-table td.no { text-align: center; white-space: nowrap; }
.modes-table td.yes { color: var(--mint-deep); font-weight: 800; }
.modes-table td.no { color: var(--muted); }
.modes-table tr.group th { padding-top: 18px; font-size: .92rem; letter-spacing: .04em; text-transform: none; color: var(--ink); border-bottom: 2px solid var(--line-strong); }
.modes-table td a { font-size: .9rem; white-space: nowrap; }
.also a { font-weight: 700; }
.adv {
  display: inline-block; vertical-align: .12em; margin-left: .35em;
  font: 800 .66rem/1 var(--body); letter-spacing: .08em; text-transform: uppercase;
  color: var(--apricot-deep); background: var(--apricot); padding: 4px 8px; border-radius: 999px;
}
.topic .note, .topic .tip, .topic .warn {
  border: 1px solid var(--line); border-left-width: 4px; border-radius: 10px;
  background: var(--paper); padding: 12px 16px; margin: 1em 0; max-width: 66ch;
}
.topic .note { border-left-color: var(--mint-deep); }
.topic .tip { border-left-color: var(--apricot); }

/* screenshots: documentation, so a plain frame on the page's own paper */
.gshot { margin: 18px 0; }
.gshot picture, .gshot img { display: block; }
.gshot img { width: 100%; max-width: 300px; border: 1px solid var(--line-strong); border-radius: 14px; box-shadow: var(--shadow); background: var(--paper); }
.gshot figcaption { font-size: .9rem; color: var(--ink-soft); margin-top: 8px; max-width: 300px; }
.gshots { display: grid; gap: 18px 22px; margin: 18px 0; }
.gshots.two { grid-template-columns: repeat(2, minmax(0, 1fr)); max-width: 640px; }
.gshots.three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.gshots .gshot { margin: 0; }
.gshots .gshot img, .gshots .gshot figcaption { max-width: none; }
@media (max-width: 720px) { .gshots.three { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .gshots.two, .gshots.three { grid-template-columns: 1fr; max-width: 300px; } }

.trouble dt, .gloss dt { font-weight: 800; margin-top: 1em; }
.trouble dd, .gloss dd { margin: .25em 0 0; color: var(--ink-soft); max-width: 66ch; }
.guide-foot { margin-top: 48px; }

/* search: sections that do not match are hidden; the matching words are marked */
.guide.searching .topic[data-hit="no"] { display: none; }
.guide.searching .topic-group[data-hit="no"] { display: none; }
.guide mark { background: var(--apricot); color: var(--on-mint); border-radius: 3px; padding: 0 .1em; }

/* ---------------------------------------------- the "tell me when it's out" form
   One input and one button. It posts to the site's own endpoint (see
   backend/notify/), and if that endpoint is not configured yet the script
   falls back to opening the visitor's mail app, so the form is never a dead
   end. 44px minimum target height on both controls (WCAG 2.5.8). */
.signup { max-width: 34rem; margin: 0 auto; }
.signup-row { display: flex; gap: 12px; flex-wrap: wrap; }
.signup input {
  flex: 1 1 15rem; min-width: 0; min-height: 48px;
  padding: 12px 16px; font: inherit; font-size: 1rem;
  color: var(--ink); background: var(--paper);
  border: 2px solid var(--line-strong); border-radius: 12px;
}
.signup input::placeholder { color: var(--muted); }
.signup input:focus-visible {
  outline: 3px solid var(--mint-deep); outline-offset: 2px; border-color: var(--mint-deep);
}
.signup input[aria-invalid="true"] { border-color: var(--apricot-deep); }
.signup .btn { min-height: 48px; flex: 0 0 auto; }
.signup button[disabled] { opacity: .65; cursor: progress; }
.signup-status { margin: 12px 0 0; min-height: 1.4em; font-size: .95rem; color: var(--ink-soft); }
.signup-status.is-error { color: var(--apricot-deep); }
.signup-status.is-done { color: var(--mint-ink); font-weight: 600; }
@media (max-width: 460px) { .signup .btn { flex: 1 1 100%; } }

/* ------------------------------------------------ Glitter Jar: the charm trays
   Three labelled grids, the way the app's own page shows them (Anna, 17 Sep
   2026). The marquee showed all 36 but named none of them, and a collection
   you are meant to complete is worth being able to read. Auto-fill rather
   than a fixed count so 13, 14 and 9 each fill their rows instead of leaving
   a ragged tail. */
.tray {
  list-style: none; margin: 0; padding: 16px;
  background: var(--bezel); border-radius: 18px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 10px;
}
.tray li { display: flex; flex-direction: column; align-items: center; gap: 6px; }
.tray img { width: 100%; aspect-ratio: 1; object-fit: contain;
  filter: drop-shadow(0 4px 8px rgba(0, 0, 0, .5)); }
.tray figcaption, .tray span {
  font-size: .78rem; color: var(--ink-soft); text-align: center; line-height: 1.2; }
.tray-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; margin: 28px 0 10px; }
.tray-head h3 { margin: 0; }
.tray-head span { color: var(--muted); font-size: .95rem; }
@media (max-width: 520px) { .tray { grid-template-columns: repeat(auto-fill, minmax(68px, 1fr)); } }
