/* Coffee Can — coffee-can.org
 *
 * Tokens copied from coffee_android/v1/.../theme/Theme.kt, not eyeballed: the
 * site and the app are the same product, and a brand green that is one step
 * off reads as a knock-off of your own app. See check_design.py for the same
 * argument applied between the app and the deck.
 */
:root {
  --brand:  #34C759;
  --ink:    #1B241C;
  --muted:  #515D51;
  --line:   #DCECDD;
  --paper:  #FFFFFF;
  --wash:   #EAF5EA;
  --deep:   #0B2E12;
  --radius: 24px;
  --gutter: clamp(20px, 5vw, 40px);
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* `[hidden]` is only `display: none` in the user-agent sheet, so any class
 * that sets `display` outranks it. That is not theoretical here: `.btn` is
 * `inline-block`, which left "Delete my account" on screen before anyone had
 * signed in -- a live button on the erasure page (legal-accounts.md rule 71)
 * that silently did nothing, because the handler returns without a token.
 * Fixed where the attribute is defined rather than per component, so the next
 * element to carry `hidden` inherits the fix. */
[hidden] { display: none !important; }

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

body {
  font-family: Fredoka, ui-rounded, "SF Pro Rounded", system-ui, sans-serif;
  color: var(--ink);
  background: var(--paper);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* `height: auto` is not optional here: every shot carries explicit width and
 * height attributes (which is what stops the page reflowing as they load), and
 * with only `max-width` constrained the width shrinks while the height stays
 * at its attribute value -- a 720x1404 phone rendered as a 280x1404 ribbon. */
img, svg { display: block; max-width: 100%; height: auto; }
a { color: inherit; }

.wrap { width: min(1120px, 100% - var(--gutter) * 2); margin-inline: auto; }

/* --- the frosted bar, the same idea the app's chrome uses ---------------- */
header {
  position: sticky; top: 0; z-index: 20;
  backdrop-filter: saturate(180%) blur(18px);
  -webkit-backdrop-filter: saturate(180%) blur(18px);
  background: color-mix(in srgb, var(--paper) 78%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 60%, transparent);
}
header .wrap { display: flex; align-items: center; gap: 14px; height: 60px; }
header img { width: 32px; height: 32px; }
header b { font-weight: 600; letter-spacing: -0.01em; }
header nav { margin-left: auto; display: flex; gap: clamp(14px, 3vw, 30px); }
header nav a {
  text-decoration: none; color: var(--muted); font-size: 15px;
  transition: color .18s ease;
}
header nav a:hover { color: var(--ink); }

/* The GitHub link is the one nav item that is not a section of this page, so
 * it carries a mark. The label collapses on narrow screens rather than the
 * whole link -- an unlabelled octocat is still recognisable, while five word
 * links plus a sixth is what actually overflows the bar. */
header nav a.gh { display: flex; align-items: center; gap: 7px; }
header nav a.gh svg { flex: none; }
@media (max-width: 820px) {
  header nav a.gh span { display: none; }
}
/* Below that the in-page anchors go too, and only Privacy and the octocat are
 * left. They are scroll shortcuts on a page that is one continuous scroll, so
 * losing them costs a phone reader nothing -- while six items plus the mark is
 * what pushes the wordmark onto two lines and shoves GitHub off the end. */
@media (max-width: 700px) {
  header nav a[href^="#"] { display: none; }
}
header b { white-space: nowrap; }
/* --- the language switch, on the two legal documents --------------------- */
/* French is what the listing, the policy and the deletion page are written in
 * (specs/legal-accounts.md rule 87); English is a translation one tap away.
 * The switch is a pill so it reads as a control rather than a fourth
 * destination, and it is labelled in the language it leads to -- "English" on
 * the French page -- which is the one label a reader who cannot read the
 * current page can still recognise. */
header nav a.lang {
  border: 1px solid var(--line); border-radius: 999px;
  padding: 3px 12px; line-height: 1.6;
}
header nav a.lang:hover { background: var(--wash); color: var(--ink); }

/* The legal pages carry three nav items, and the French labels are markedly
 * longer than the English ones they replace ("Supprimer le compte" against
 * "Delete account"), which overflows a 360px bar. Below that the sibling
 * document drops out of the header -- the footer carries the same link on
 * every page, and it is the only one of the three that is reachable twice.
 * The language switch never drops: it is the sole route to the other
 * language, and dropping it strands exactly the reader who needs it. */
@media (max-width: 600px) {
  header nav a.sib { display: none; }
}

/* --- hero ---------------------------------------------------------------- */
.hero { text-align: center; padding: clamp(56px, 11vw, 128px) 0 0; }
.hero-mark { width: clamp(128px, 22vw, 190px); margin: 0 auto clamp(24px, 4vw, 40px); }
h1 {
  font-size: clamp(40px, 8.2vw, 88px);
  line-height: 1.03;
  letter-spacing: -0.035em;
  font-weight: 600;
  max-width: 16ch;
  margin-inline: auto;
}
h1 em { font-style: normal; color: var(--brand); }
.lede {
  margin: clamp(18px, 3vw, 28px) auto 0;
  max-width: 54ch;
  font-size: clamp(17px, 2.1vw, 21px);
  color: var(--muted);
}
.cta { margin-top: clamp(28px, 4vw, 40px); display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
.btn {
  display: inline-block; text-decoration: none; font-size: 16px;
  padding: 13px 26px; border-radius: 999px;
  transition: transform .16s ease, box-shadow .16s ease, background .16s ease;
}
.btn-primary { background: var(--brand); color: #fff; box-shadow: 0 6px 20px rgba(52,199,89,.32); }
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 10px 26px rgba(52,199,89,.4); }
.btn-ghost { border: 1px solid var(--line); color: var(--ink); }
.btn-ghost:hover { background: var(--wash); }

/* the hero device, tilted just enough to read as an object */
.hero-shot {
  margin: clamp(44px, 7vw, 84px) auto 0;
  width: min(300px, 74vw);
  perspective: 1400px;
}
.hero-shot img {
  border-radius: 34px;
  border: 1px solid var(--line);
  box-shadow: 0 30px 70px rgba(11,46,18,.16), 0 6px 18px rgba(11,46,18,.07);
  transform: rotateX(6deg);
}

/* --- feature rows -------------------------------------------------------- */
.feature { padding: clamp(72px, 12vw, 150px) 0; }
.feature + .feature { border-top: 1px solid var(--line); }
.feature .wrap {
  display: grid; gap: clamp(32px, 6vw, 80px); align-items: center;
  grid-template-columns: 1fr;
}
@media (min-width: 860px) {
  .feature .wrap { grid-template-columns: 1fr 1fr; }
  .feature.flip .copy { order: 2; }
}
.eyebrow {
  font-size: 13px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--brand); font-weight: 600; margin-bottom: 14px;
}
h2 {
  font-size: clamp(28px, 4.6vw, 46px); line-height: 1.1;
  letter-spacing: -0.03em; font-weight: 600; max-width: 18ch;
}
.feature p { margin-top: 16px; color: var(--muted); font-size: clamp(16px, 1.9vw, 18px); max-width: 46ch; }
.shot { position: relative; }
.shot img {
  border-radius: 30px; border: 1px solid var(--line);
  box-shadow: 0 24px 56px rgba(11,46,18,.14);
  margin-inline: auto; width: min(280px, 70vw);
}
.shot .mascot {
  position: absolute; width: 96px; right: -6px; bottom: -18px;
  filter: drop-shadow(0 10px 22px rgba(11,46,18,.18));
}

/* --- privacy band -------------------------------------------------------- */
.band { background: var(--wash); padding: clamp(64px, 10vw, 120px) 0; text-align: center; }
.band h2 { margin-inline: auto; }
.band p { margin: 16px auto 0; color: var(--muted); max-width: 56ch; font-size: clamp(16px, 1.9vw, 18px); }
.pills { margin-top: 30px; display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
.pill {
  background: var(--paper); border: 1px solid var(--line);
  padding: 9px 16px; border-radius: 999px; font-size: 14px; color: var(--muted);
}

/* --- the share row -------------------------------------------------------
 *
 * The one section painted in the brand green, and it is the section about the
 * share card -- which is itself a tall green sheet with white type on it. The
 * row is the artefact it describes, at page scale, which is why this is the
 * only place `--brand` is used as a *ground* rather than as an accent. Text on
 * it is full white and the secondary role is white at reduced opacity, exactly
 * the two ink colours ShareCard.kt uses.
 *
 * It is a `.feature` row and not a centred band, because the screenshot is the
 * proof: the card in the picture is the card the copy describes, drawn by the
 * phone it is being shown on. */
.band-brand { background: var(--brand); color: var(--paper); border-top: 0; }
.band-brand .eyebrow { color: rgba(255,255,255,.82); }
.band-brand p { color: rgba(255,255,255,.9); }
.band-brand .fineprint { margin-top: 26px; font-size: 14.5px; color: rgba(255,255,255,.78); }
/* The shot sits on green, so its hairline is the same white at low alpha the
 * card's own dividers use rather than the page's pale-green rule. */
.band-brand .shot img { border-color: rgba(255,255,255,.35); box-shadow: 0 24px 56px rgba(11,46,18,.28); }

.what { list-style: none; margin-top: 20px; max-width: 46ch; }
.what li {
  position: relative; padding-left: 20px;
  margin-top: 12px; font-size: clamp(15.5px, 1.85vw, 17px);
  color: rgba(255,255,255,.9);
}
.what li::before {
  content: ""; position: absolute; left: 0; top: .62em;
  width: 7px; height: 7px; border-radius: 50%;
  background: rgba(255,255,255,.75);
}
.what b { color: var(--paper); font-weight: 600; }

/* --- the catch-all grid --------------------------------------------------
 *
 * Everything the app does that does not earn a phone shot of its own. Same
 * measure and rhythm as a `.feature` row, but text-only and two-or-three up,
 * so the page can be complete without becoming eleven screenshots long. */
.grid-band { padding: clamp(72px, 12vw, 130px) 0; border-top: 1px solid var(--line); }
.grid-band > .wrap > .eyebrow { text-align: center; }
.grid-band > .wrap > h2 { margin-inline: auto; text-align: center; max-width: 22ch; }
.grid {
  margin-top: clamp(36px, 6vw, 56px);
  display: grid; gap: clamp(26px, 4vw, 40px);
  grid-template-columns: 1fr;
}
@media (min-width: 640px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .grid { grid-template-columns: repeat(4, 1fr); } }
.grid h3 { font-size: 18px; font-weight: 600; letter-spacing: -0.01em; }
.grid p { margin-top: 10px; color: var(--muted); font-size: 16px; }
.grid a { color: var(--deep); text-decoration: underline; text-underline-offset: 3px; }

footer { padding: clamp(44px, 7vw, 72px) 0; border-top: 1px solid var(--line); }
footer .wrap { display: flex; flex-wrap: wrap; gap: 16px 28px; align-items: center; }
footer .sp { margin-left: auto; }
footer a { color: var(--muted); text-decoration: none; font-size: 15px; }
footer a:hover { color: var(--ink); }
footer small { color: var(--muted); font-size: 14px; }

/* --- the screenshot lightbox ---------------------------------------------
 *
 * Click a phone: it grows to the middle of the window, everything behind it
 * blurs. <dialog> + showModal() is still what holds it -- Escape closes, focus
 * is trapped, the page behind goes inert to a screen reader -- but the frost
 * is a real element inside the dialog rather than `::backdrop`.
 *
 * WHY NOT `::backdrop`, HAVING TRIED IT: custom properties do not reliably
 * reach it. `::backdrop` is not a descendant of any element for inheritance
 * purposes in every engine, so `var(--deep)` inside it resolves to nothing,
 * the whole `background` declaration is dropped as invalid, and what should
 * have been a tinted frost renders as clear glass over a bright page. A plain
 * div takes the same `backdrop-filter` and inherits variables like anything
 * else.
 */
dialog.lightbox {
  position: fixed; inset: 0; margin: 0; padding: 0; border: 0;
  width: 100%; height: 100%;
  max-width: none; max-height: none;
  background: transparent;
  /* The dialog is a viewport-sized box holding a centred image; nothing in it
   * should ever scroll. Without this the UA's default sizing let a large
   * image push a scrollbar down the side of the frost. */
  overflow: hidden;
}
dialog.lightbox::backdrop { background: transparent; }

.lb-scrim {
  position: absolute; inset: 0;
  background: color-mix(in srgb, var(--deep) 40%, transparent);
  backdrop-filter: blur(20px) saturate(135%);
  -webkit-backdrop-filter: blur(20px) saturate(135%);
  opacity: 0;
  transition: opacity .3s ease;
}
/* A fallback for engines without color-mix: a flat tint is better than none,
 * and the later declaration wins only where color-mix is unsupported. */
@supports not (background: color-mix(in srgb, red 40%, transparent)) {
  .lb-scrim { background: rgba(11, 46, 18, .4); }
}

.lb-stage {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
}
/* CENTRING IS ON THE DIALOG TOO, NOT ONLY ON `.lb-stage`, AND THAT IS A
 * CACHE DEFENCE RATHER THAN BELT AND BRACES. This stylesheet and the script
 * that builds `.lb-stage` ship as two files with no version in either URL, so
 * a browser can hold a fresh copy of one and a stale copy of the other -- and
 * on 2026-08-25 one did: new CSS, old JS, which put a bare <img> straight
 * inside the dialog where nothing sized it. It rendered at the full width of
 * the viewport, jammed into the top-left corner, over a correctly blurred
 * page. Sizing whatever image is in there means the worst a desynced cache
 * can now produce is a picture that does not animate. */
/* `[open]` IS LOAD-BEARING: an author `display` on a bare `dialog` selector
 * beats the UA's `dialog:not([open]) { display: none }` regardless of
 * specificity, which would leave this thing on screen permanently. */
dialog.lightbox[open] { display: flex; align-items: center; justify-content: center; }
.lb-stage img, dialog.lightbox > img {
  /* Fitted to the window, never blown up past the asset: these are 720px
   * captures and stretching one across a desktop is how a crisp screenshot
   * turns to mush. Height is what usually binds -- a phone is tall. */
  width: auto; height: auto;
  max-width: min(88vw, 460px);
  max-height: 86dvh;
  border-radius: 34px;
  box-shadow: 0 40px 90px rgba(11, 46, 18, .38);
  /* THE ORIGIN IS THE CENTRE, which is what makes the maths below one
   * translate plus one scale rather than a corner-anchored special case. */
  transform-origin: 50% 50%;
  will-change: transform;
}
dialog.lightbox.animate .lb-stage img,
dialog.lightbox.animate > img {
  transition: transform .44s cubic-bezier(.22, .78, .27, 1);
}
dialog.lightbox.open .lb-scrim { opacity: 1; }

/* The thumbnails advertise themselves. `zoom-in` rather than `pointer`: the
 * page has real links and these should not look identical to one. */
.shot img:not(.mascot), .hero-shot img { cursor: zoom-in; }
.lb-stage img, dialog.lightbox > img { cursor: zoom-out; }
.shot img:not(.mascot):focus-visible, .hero-shot img:focus-visible {
  outline: 3px solid var(--brand); outline-offset: 6px;
}

/* --- reveal on scroll ---------------------------------------------------- */
.reveal { opacity: 0; transform: translateY(18px); transition: opacity .6s ease, transform .6s ease; }
.reveal.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; transition: none; }
  .btn, header nav a { transition: none; }
  /* The lightbox still opens and still blurs -- what goes is the travel.
   * Someone who has asked for less motion wants the shot bigger, not to be
   * denied the feature. */
  /* The lightbox still opens and still frosts -- what goes is the travel.
   * Someone who asked for less motion wants the shot bigger, not the feature
   * withheld. */
  dialog.lightbox.animate .lb-stage img,
  dialog.lightbox.animate > img { transition: none; }
  .lb-scrim { transition: none; }
}

/* --- /privacy and /delete ------------------------------------------------
 *
 * Prose pages, not landing sections: one narrow column, no reveal animation
 * and no hero. They are read by someone looking for a specific sentence --
 * a reviewer checking a Play declaration, or a user who wants their account
 * gone -- so nothing here fades in or has to be scrolled past. */
.doc { padding: clamp(40px, 7vw, 76px) 0 clamp(56px, 9vw, 96px); }
.doc .wrap { width: min(72ch, 100% - var(--gutter) * 2); }
.doc h1 { font-size: clamp(30px, 5vw, 44px); letter-spacing: -0.02em; line-height: 1.15; }
.doc h2 {
  font-size: clamp(20px, 2.6vw, 25px); letter-spacing: -0.01em;
  margin-top: clamp(34px, 5vw, 52px);
}
.doc h3 { font-size: 17px; margin-top: 26px; }
.doc p, .doc li { color: var(--muted); font-size: 16.5px; margin-top: 12px; }
.doc strong { color: var(--ink); font-weight: 500; }
.doc ul { margin-top: 12px; padding-left: 1.1em; }
.doc li { margin-top: 8px; }
.doc a { color: var(--deep); text-decoration: underline; text-underline-offset: 3px; }
.doc .updated { margin-top: 10px; font-size: 14px; color: var(--muted); }
.doc hr { border: 0; border-top: 1px solid var(--line); margin: clamp(34px, 5vw, 52px) 0 0; }

/* A short, high-contrast block for the sentences a reader is most likely to
 * have come for -- the local-data statement, and what deletion does not do. */
.note {
  margin-top: 20px; padding: 18px 20px;
  background: var(--wash); border-radius: 16px;
}
.note p { color: var(--ink); margin-top: 0; }
.note p + p { margin-top: 10px; }

/* --- the deletion form --------------------------------------------------- */
.gate { margin-top: 26px; padding: 22px; border: 1px solid var(--line); border-radius: var(--radius); }
.gate h2 { margin-top: 0; }
.gate .status { margin-top: 16px; font-size: 15px; color: var(--muted); min-height: 1.4em; }
.gate .status.err { color: #8A2A1C; }
.gate .status.ok { color: var(--deep); }
.gate .who { margin-top: 14px; font-size: 15px; color: var(--ink); }
.gate .btn { margin-top: 16px; border: 0; font: inherit; cursor: pointer; }
.gate .btn[disabled] { opacity: .5; cursor: default; box-shadow: none; transform: none; }
.btn-danger { background: #8A2A1C; color: #fff; }
.btn-danger:hover:not([disabled]) { background: #741F13; }
