/* Ripple: Sensory Spaces — site styles
   Palette and type mirror the app's own design system:
   Color+Tokens.swift (#F6F2EA / #0A0A0C grounds, #5C4521 / #B8975A accent,
   the four zone colours) and Typography.swift (serif display, system body).
   System font stacks are deliberate — they make the site look like the app
   on Apple devices, load instantly, and need no third-party requests. */

:root {
  --bg: #F6F2EA;
  --surface: #FFFFFF;
  --surface-2: #EFE8D9;
  --ink: #1A1208;
  --ink-2: #554839;
  --ink-3: #6B5C48;
  --rule: #DDD3BF;
  --accent: #5C4521;
  --accent-bg: #EBE1CD;
  --focus: #5C4521;
  --glass-bg: rgba(255,255,255,.62);
  --glass-edge: rgba(26,18,8,.16);
  --easein: #2C6494;
  --settle: #37714A;
  --breathe: #7E5911;
  --easeout: #93332F;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg: #0A0A0C;
    --surface: #14141A;
    --surface-2: #1C1C23;
    --ink: #F4EFE3;
    --ink-2: #C6BDAB;
    --ink-3: #968C7C;
    --rule: #2A2A33;
    --accent: #B8975A;
    --accent-bg: #221D12;
    --focus: #B8975A;
    /* Zone hues lifted from the app's #3B82C4 / #4A9B5F / #D4962A / #B84040.
       Ease In and Ease Out are lightened for the web: at their app values they
       measure 4.52:1 and 3.36:1 on this surface, and 3.36 fails AA outright.
       Hue and saturation are unchanged — only lightness moves. */
    --glass-bg: rgba(30,30,38,.55);
    --glass-edge: rgba(255,255,255,.20);
    --easein: #4287C6;
    --settle: #4A9B5F;
    --breathe: #D4962A;
    --easeout: #C96565;
  }
}

*, *::before, *::after { box-sizing: border-box; }

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

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  font-size: 1.0625rem;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

/* ---------- skip link ---------- */
.skip {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--accent); color: var(--bg);
  padding: 12px 20px; border-radius: 0 0 8px 0; font-weight: 600;
}
.skip:focus { left: 0; }

/* ---------- focus ---------- */
:focus-visible {
  outline: 3px solid var(--focus);
  outline-offset: 3px;
  border-radius: 3px;
}

/* ---------- layout ---------- */
.wrap { max-width: 68rem; margin: 0 auto; padding: 0 clamp(1.25rem, 4vw, 2.5rem); }
main { display: block; }
.narrow { max-width: 44rem; }

/* ---------- header / nav ---------- */
.site-header { border-bottom: .5px solid var(--rule); background: var(--bg); }
.header-inner {
  display: flex; flex-wrap: wrap; gap: 1rem 2rem;
  align-items: center; justify-content: space-between;
  padding-top: 1.1rem; padding-bottom: 1.1rem;
}
.brand {
  font-family: ui-serif, Georgia, "Times New Roman", serif;
  font-size: 1.45rem; font-weight: 500; letter-spacing: -.02em;
  color: var(--ink); text-decoration: none; line-height: 1.2;
}
.brand:hover { color: var(--accent); }
.brand span { color: var(--ink-3); font-size: .95rem; display: block; letter-spacing: 0; font-family: inherit; }
.site-nav ul { list-style: none; display: flex; flex-wrap: wrap; gap: .35rem 1.4rem; margin: 0; padding: 0; }
.site-nav a {
  color: var(--ink-2); text-decoration: none; font-size: .95rem;
  display: inline-block; padding: .55rem 0; min-height: 44px; line-height: 1.9;
}
.site-nav a:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 4px; }
.site-nav a[aria-current="page"] { color: var(--ink); font-weight: 600; box-shadow: inset 0 -2px 0 var(--accent); }

/* ---------- type ---------- */
h1, h2, h3 {
  font-family: ui-serif, Georgia, "Times New Roman", serif;
  font-weight: 500; letter-spacing: -.02em; text-wrap: balance; margin: 0;
}
h1 { font-size: clamp(2.1rem, 5.5vw, 3.1rem); line-height: 1.1; }
h2 { font-size: clamp(1.45rem, 3.2vw, 1.95rem); line-height: 1.22; }
h3 { font-size: 1.16rem; line-height: 1.35; font-family: inherit; font-weight: 600; letter-spacing: 0; }
p, li { color: var(--ink-2); }
p { margin: 0; }
.lede { font-size: clamp(1.1rem, 2.4vw, 1.3rem); line-height: 1.55; color: var(--ink-2); }
a { color: var(--accent); text-underline-offset: 3px; }
a:hover { text-decoration-thickness: 2px; }
strong { color: var(--ink); font-weight: 600; }

.eyebrow {
  font-size: .74rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600; margin: 0;
}

/* ---------- spacing helpers ---------- */
.stack   { display: grid; gap: 1rem; }
.stack-s { display: grid; gap: .55rem; }
.stack-l { display: grid; gap: 2rem; }
section { padding: clamp(2.75rem, 7vw, 4.5rem) 0; }
section + section { border-top: .5px solid var(--rule); }
.hero { padding-top: clamp(3rem, 9vw, 6rem); }

/* ---------- cards ---------- */
.grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); }
.card {
  background: var(--surface); border: .5px solid var(--rule);
  border-radius: 18px; padding: 1.4rem; display: grid; gap: .6rem;
}
.card p { font-size: .95rem; }
.zone { --zone: var(--accent); }
.zone h3 { display: flex; align-items: center; gap: .55rem; }
.zone h3::before {
  content: ""; width: .6rem; height: .6rem; border-radius: 50%;
  background: var(--zone); flex: none;
}
.z-easein  { --zone: var(--easein); }
.z-settle  { --zone: var(--settle); }
.z-breathe { --zone: var(--breathe); }
.z-easeout { --zone: var(--easeout); }

/* ---------- callout ---------- */
.note {
  background: var(--accent-bg);
  padding: 1.2rem 1.4rem; display: grid; gap: .5rem;
  border-radius: 16px;
}
.note p, .note li { color: var(--ink-2); }

/* ---------- lists ---------- */
.bullets { margin: 0; padding-left: 1.25rem; display: grid; gap: .5rem; }
.plain { list-style: none; margin: 0; padding: 0; display: grid; gap: .5rem; }

/* ---------- definition rows ---------- */
.rows { display: grid; gap: 0; border: .5px solid var(--rule); border-radius: 18px; overflow: hidden; }
.row { display: grid; gap: .3rem; padding: 1rem 1.25rem; background: var(--surface); }
.row + .row { border-top: 1px solid var(--rule); }
.row dt, .row .k { font-weight: 600; color: var(--ink); font-size: .95rem; }
.row dd, .row .v { margin: 0; color: var(--ink-2); font-size: .95rem; }
@media (min-width: 40rem) {
  .row { grid-template-columns: 15rem 1fr; gap: 1.5rem; align-items: start; }
}

/* ---------- table ---------- */
.scroll { overflow-x: auto; border: .5px solid var(--rule); border-radius: 18px; background: var(--surface); }
/* It carries `tabindex="0"` so it can be scrolled from the keyboard, which means
   it also has to show where the focus is. */
.scroll:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
table { border-collapse: collapse; width: 100%; min-width: 34rem; font-size: .93rem; }
th, td { text-align: left; padding: .8rem 1.1rem; border-bottom: 1px solid var(--rule); vertical-align: top; color: var(--ink-2); }
th { color: var(--ink); font-weight: 600; background: var(--surface-2); }
tbody tr:last-child td { border-bottom: none; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: .7rem 1.5rem; border-radius: 16px;
  background: var(--accent); color: var(--bg);
  text-decoration: none; font-weight: 600; font-size: 1rem;
  border: 2px solid transparent;
}
.btn:hover { filter: brightness(1.12); }
.btn-ghost { background: transparent; color: var(--accent); border-color: var(--rule); border-width: .5px; }
.btn-ghost:hover { border-color: var(--accent); filter: none; }
.btn-row { display: flex; flex-wrap: wrap; gap: .75rem; }

/* ---------- footer ---------- */
.site-footer { border-top: .5px solid var(--rule); margin-top: 2rem; padding: 2.5rem 0 3.5rem; }
.footer-inner { display: grid; gap: 1.5rem; }
.site-footer p, .site-footer li { font-size: .9rem; color: var(--ink-3); }
.site-footer ul { list-style: none; display: flex; flex-wrap: wrap; gap: .35rem 1.4rem; margin: 0; padding: 0; }
.site-footer a { color: var(--ink-2); }

/* ---------- misc ---------- */
img, svg { max-width: 100%; height: auto; }
hr { border: none; border-top: 1px solid var(--rule); margin: 0; }
.updated { font-size: .88rem; color: var(--ink-3); }
code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: .9em; background: var(--surface-2); padding: .1em .4em; border-radius: 4px; color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .001ms !important; animation-iteration-count: 1 !important; transition-duration: .001ms !important; scroll-behavior: auto !important; }
}


/* ---------- ripple surface panel ---------- */
/* The surface is a specimen, not a backdrop. Text never sits on top of it —
   the app does the same thing: Home keeps its title on a calm ground and the
   live surface is its own object. Only the full-screen experience is edge to
   edge, and the only chrome over it there is a glass circle button. */
.surface-panel {
  position: relative;
  border-radius: 24px;                 /* Radius.hero */
  border: .5px solid var(--rule);
  overflow: hidden;
  background: var(--bg);
  aspect-ratio: 16 / 10;
  max-height: 30rem;
}
@media (max-width: 34rem) { .surface-panel { aspect-ratio: 4 / 3; } }
.surface-panel canvas {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: block; touch-action: manipulation; cursor: crosshair;
}
/* Glass circle control, 44px, matching the app's `glassButton`. The app falls
   back to an opaque chrome colour plus a hairline under Reduce Transparency,
   so this does the same rather than leaving a blur nobody asked for. */
.surface-controls { position: absolute; right: 12px; bottom: 12px; display: flex; gap: 8px; }
.glass-btn {
  width: 44px; height: 44px; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  border: .5px solid var(--glass-edge);
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(14px); backdrop-filter: blur(14px);
  color: var(--ink); cursor: pointer; padding: 0;
}
.glass-btn:hover:not(:disabled) { border-color: var(--accent); }
.glass-btn:disabled { opacity: .45; cursor: default; }
.glass-btn svg { width: 17px; height: 17px; fill: currentColor; display: block; }
@media (prefers-reduced-transparency: reduce) {
  .glass-btn { background: var(--surface); -webkit-backdrop-filter: none; backdrop-filter: none; }
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

.surface-caption {
  display: flex; align-items: center; gap: .5rem;
  font-size: .85rem; color: var(--ink-3); margin-top: .75rem;
}
.surface-caption::before {
  content: ""; width: .55rem; height: .55rem; border-radius: 50%;
  background: var(--accent); flex: none;
}



/* ---------------------------------------------------------------------------
   Reflow at 320px with the text scaled up (WCAG 1.4.10).

   Two things forced the page wider than the viewport, both only visible once
   the root font size grows: the support address is a single 30-character token
   that cannot wrap, so it set the *min-content* width of every block holding it
   and pushed the whole column out; and the header's flex children default to
   `min-width: auto`, so the nav could not shrink below its widest row.
   --------------------------------------------------------------------------- */
/* `anywhere`, not `break-word`. They look identical — a word breaks only when it
   cannot fit — but only `anywhere` reduces the element's intrinsic *min-content*
   width, and min-content is what was propagating outwards and widening the page.
   `break-word` alone left every measurement unchanged. */
p, li, h1, h2, h3, dd, dt, figcaption, a { overflow-wrap: anywhere; }
/* Not table cells. `anywhere` reduces min-content, which is what lets a narrow
   column collapse and shatter "Google Firebase" into one letter per line -
   `break-word` keeps whole words intact and lets the table keep its width. */
td, th { overflow-wrap: break-word; }

/* Grid and flex children default to `min-width: auto`, so a track cannot shrink
   below its widest item — "touch-responsive" in a card was setting the width of
   the whole page once the text was scaled up. */
.header-inner > *, .footer-inner > *, .site-nav, .site-nav ul,
.grid > *, .card, .row > *, .rows > * { min-width: 0; }

/* ---------------------------------------------------------------------------
   Target size (WCAG 2.2, 2.5.8 Target Size Minimum, AA).

   Links inside a sentence are exempt — their size is set by the line-height of
   the text around them, and padding one out would push the line apart. The two
   navigation lists are not in a sentence and had no exemption to claim: each
   link measured 16px tall with 5.6px between wrapped rows, so a 24px target
   circle overlapped its neighbour in both directions.

   Padding rather than a larger font, so the tap area grows and the design does
   not. The header's current-page underline is drawn with an inset box-shadow,
   which follows the padding down and now sits clear of the text rather than
   tight against it.
   --------------------------------------------------------------------------- */
.site-nav a, .site-footer nav a {
  display: inline-block;
  padding-block: .28rem;
  min-height: 24px;
}
.site-nav ul, .site-footer ul { gap: .5rem 1.4rem; }

/* ---------------------------------------------------------------------------
   Card content sits at the top, always.

   `.card` is a grid, and a grid's rows stretch to fill the box by default. With
   two children in a row of equal-height cards, the heading row grew and the
   heading floated in the middle of it, so "Settle" started lower than "Ease In"
   and the gap under each heading was a different size. `align-content: start`
   packs both rows at the top, which makes the gap under every heading the same
   .6rem and lines the headings up across the row.

   Equal card heights are kept on purpose: a 2x2 of boxes that each stop at
   their own text reads as ragged, and the space now falls at the bottom of a
   card where nobody is looking, rather than between a heading and its sentence.
   --------------------------------------------------------------------------- */
.card { align-content: start; }

/* ---------------------------------------------------------------------------
   The surface is the object on the page, so it sits in the middle of the
   column with its caption and buttons centred underneath, rather than being a
   wide band with the controls hanging off the left edge.
   --------------------------------------------------------------------------- */
.surface-panel { max-width: 46rem; margin-inline: auto; width: 100%; }
.surface-caption { justify-content: center; text-align: center; }
/* Scoped: only the row under the surface. Other pages keep left-aligned buttons
   under left-aligned text. */
.surface-caption ~ .btn-row { justify-content: center; }


/* ---------------------------------------------------------------------------
   The collection table becomes a list on a phone.

   It has three columns and prose in two of them, so at 390px it was either a
   sideways scroll or - once cells could break mid-word - a third column squeezed
   to about 40px, spelling "Google Firebase" one letter per line. Neither is a
   reasonable way to read the part of a privacy policy people actually check.

   Below 44rem each row becomes its own block and each cell is prefixed with its
   column name from `data-label`. The header row is hidden because those names now
   appear inline; it is hidden with `position: absolute` rather than
   `display: none` so a screen reader still meets the table's real header cells and
   can announce the association.
   --------------------------------------------------------------------------- */
@media (max-width: 44rem) {
  .scroll { border: none; background: none; border-radius: 0; }
  table { min-width: 0; display: block; }
  thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
  tbody, tr, td { display: block; }
  tr {
    background: var(--surface); border: .5px solid var(--rule);
    border-radius: 14px; padding: .35rem .2rem; margin-bottom: .75rem;
  }
  td { border-bottom: none; padding: .5rem 1rem; }
  td::before {
    content: attr(data-label);
    display: block; margin-bottom: .15rem;
    font-size: .75rem; font-weight: 600; letter-spacing: .04em;
    text-transform: uppercase; color: var(--ink-3);
  }
}

/* ---------------------------------------------------------------------------
   Increase Contrast, matching what the app promises.

   The app's contract is AA normally and AAA when the system asks for more
   contrast: PaletteContrastTests checks every palette at both, and
   AccessibilityConfig raises `rippleMaxOpacity` to 0.85 and widens the outline
   when `colorSchemeContrast == .increased`. `prefers-contrast: more` is the same
   signal on the web, so the site answers it the same way.

   Only the muted tones move, because only they were short of AAA - measured
   against the worst ground each one actually sits on. `--ink` and the dark
   theme's `--ink-2` already clear 7:1 and are left alone.
   --------------------------------------------------------------------------- */
@media (prefers-contrast: more) {
  :root {
    --ink-2: #524536;   /* 7.15:1 */
    --ink-3: #534430;   /* 7.23:1 */
    --accent: #59421E;  /* 7.28:1 */
    --focus: #59421E;
    --rule: #B9AB92;
  }
}
/* The dark values must be gated on the dark scheme as well as on contrast.
   A first version put them in the block above, so a light-theme reader who asks
   for more contrast got the dark theme's pale grey on cream: 2.13:1, worse than
   what they started with, and worse for exactly the person who asked. */
@media (prefers-contrast: more) and (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink-3: #B1A797;   /* 7.06:1 */
    --accent: #C4A366;  /* 7.01:1 */
    --focus: #C4A366;
    --rule: #454550;
  }
}
@media (prefers-contrast: more) {
  :root[data-theme="dark"] {
    --ink-3: #B1A797;
    --accent: #C4A366;
    --focus: #C4A366;
    --rule: #454550;
  }
}

/* ---------- App Store badge ---------- */
/* line-height 0 kills the descender gap under the image, which otherwise reads as a
   stray few pixels of padding below the badge. Width is capped rather than fixed so the
   badge shrinks with the column on a narrow phone instead of forcing a scroll. */
.badge-link { display: inline-block; line-height: 0; }
.badge-link img { width: 180px; max-width: 100%; height: auto; }
