/* Verdict website. Porcelain theme.
   ------------------------------------------------------------------
   Porcelain is the app's frosted-glass theme and it is the only one built as a
   visual language rather than a set of flat fills, which is why it is the one
   the site uses. Values come straight from `porcelainColors` in
   design-tokens.js, and the background reproduces the `stone` backdrop art from
   components/Backdrop.js: a warm travertine gradient with soft radial light
   pools, large enough that no pool's rim ever lands where you could read it as
   a shape.

   ⚠️ No third-party requests, deliberately. The display font is served from
   this folder, there is no analytics, no CDN and no embedded anything. That is
   what lets privacy.html say the site loads nothing from anyone else, and why
   there is no cookie banner. Adding a Google Fonts link would break both.

   ⚠️ Light only, matching Porcelain, which has no dark variant in the app. Do
   not add a `prefers-color-scheme: dark` block: the glass effect depends on
   dark text over bright translucent panels, and inverting it produces a theme
   that exists nowhere in the product. */

/* Display face only. Body text uses the system stack on purpose: Inter ships as
   343KB per weight in the app's package, which is not worth a megabyte of
   download for text that a system sans renders just as well. Headings are where
   the character lives. */
@font-face {
  font-family: 'Space Grotesk';
  src: url('fonts/SpaceGrotesk_700Bold.ttf') format('truetype');
  font-weight: 700;
  font-display: swap;
}
@font-face {
  font-family: 'Space Grotesk';
  src: url('fonts/SpaceGrotesk_600SemiBold.ttf') format('truetype');
  font-weight: 600;
  font-display: swap;
}

:root {
  --paper:    #EFE9E0;
  --ink:      #1F2420;
  --ink-soft: #5A6159;
  --mono:     #8A8578;
  --hair:     rgba(31, 36, 32, 0.10);

  --glass:    rgba(255, 255, 255, 0.52);
  --glass-hi: rgba(255, 255, 255, 0.78);
  --edge:     rgba(255, 255, 255, 0.92);

  --exc:  #26814A;
  --good: #61841C;
  --poor: #A96806;
  --bad:  #C4392B;

  --ok-bg:   rgba(38, 129, 74, 0.11);
  --bad-bg:  rgba(196, 57, 43, 0.10);
  --warn-bg: rgba(169, 104, 6, 0.12);

  --display: 'Space Grotesk', ui-sans-serif, system-ui, sans-serif;
  --body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
          'Helvetica Neue', Arial, sans-serif;

  --radius: 18px;
  --shadow: 0 1px 2px rgba(31, 36, 32, 0.05),
            0 12px 34px rgba(31, 36, 32, 0.09);
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  * { animation: none !important; transition: none !important; }
  /* ⚠️ Must come with the rule above. Killing transitions leaves anything
     waiting to be revealed stranded at opacity 0, so the reveal state has to be
     cancelled outright rather than merely un-animated. Same for the bars, which
     would otherwise sit permanently at zero width. */
  .js .reveal { opacity: 1 !important; translate: none !important; }
}

body {
  margin: 0;
  /* Protects the full-bleed `.band` technique below, which pulls a section out
     to the viewport edge with negative margins. Without this a scrollbar's
     width can leak into 100vw and produce horizontal scroll. */
  overflow-x: hidden;
  font-family: var(--body);
  font-size: 17px;
  line-height: 1.62;
  color: var(--ink);
  background-color: var(--paper);
  /* The stone backdrop. Pools are much larger than the viewport and overlap, so
     none of their edges resolves into a visible shape. Fixed, so the light
     stays put while content moves over it, which is what makes the panels read
     as sitting on a surface rather than being pasted onto a pattern. */
  background-image:
    radial-gradient(90ch 70ch at 12% 6%,   rgba(255, 255, 255, 0.92), transparent 62%),
    radial-gradient(80ch 76ch at 96% 26%,  rgba(191, 163, 120, 0.50), transparent 64%),
    radial-gradient(84ch 80ch at 2% 62%,   rgba(255, 255, 255, 0.85), transparent 64%),
    radial-gradient(96ch 88ch at 98% 84%,  rgba(191, 163, 120, 0.42), transparent 66%),
    radial-gradient(88ch 64ch at 22% 104%, rgba(132, 172, 139, 0.42), transparent 64%),
    linear-gradient(168deg, #F7F1E6 0%, #EBE0CD 52%, #D3C0A2 100%);
  background-attachment: fixed;
  background-repeat: no-repeat;
}

/* Wider than reading width on purpose. The landing page lays out in grids that
   fill it; prose inside those grids caps its own measure, so nothing here runs
   at 1040px of text. `.narrow` is for the legal pages, which are prose all the
   way down and want a proper measure. */
.wrap { max-width: 1040px; margin: 0 auto; padding: 26px 22px 90px; }
.narrow { max-width: 720px; }

/* Anything that is genuinely a paragraph in the flow of a section keeps a
   readable measure regardless of how wide its container is. */
section > p:not(.kicker) { max-width: 56ch; }

/* ---------- glass ---------- */

.glass {
  background: var(--glass);
  border: 1px solid var(--edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  -webkit-backdrop-filter: blur(18px) saturate(1.35);
  backdrop-filter: blur(18px) saturate(1.35);
}

/* ---------- header ---------- */

header.site {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 12px 18px;
  margin-bottom: 46px;
  flex-wrap: wrap;
  background: var(--glass);
  border: 1px solid var(--edge);
  border-radius: 999px;
  box-shadow: var(--shadow);
  -webkit-backdrop-filter: blur(18px) saturate(1.35);
  backdrop-filter: blur(18px) saturate(1.35);
}

.mark { width: 26px; height: 26px; flex: none; color: var(--exc); }

.wordmark {
  font-family: var(--display);
  font-weight: 700;
  font-size: 19px;
  letter-spacing: -0.5px;
  color: var(--ink);
  text-decoration: none;
  margin-right: auto;
}

header.site nav a {
  color: var(--ink-soft);
  text-decoration: none;
  font-size: 14.5px;
  margin-left: 15px;
}
header.site nav a:hover, header.site nav a:focus {
  color: var(--ink);
  text-decoration: underline;
}

/* ---------- type ---------- */

h1, h2, h3 { font-family: var(--display); color: var(--ink); }

h1 {
  font-weight: 700;
  font-size: clamp(34px, 6.4vw, 52px);
  line-height: 1.06;
  letter-spacing: -1.6px;
  margin: 0 0 18px;
  text-wrap: balance;
}

h2 {
  font-weight: 700;
  font-size: clamp(24px, 3.6vw, 31px);
  line-height: 1.15;
  letter-spacing: -0.8px;
  margin: 0 0 14px;
  text-wrap: balance;
}

h3 { font-weight: 600; font-size: 17px; letter-spacing: -0.3px; margin: 0 0 7px; }

p, li { color: var(--ink-soft); }
p { margin: 0 0 15px; }
ul { padding-left: 21px; margin: 0 0 15px; }
li { margin-bottom: 8px; }
strong { color: var(--ink); font-weight: 600; }
a { color: var(--exc); }

.lede {
  font-size: clamp(18px, 2.4vw, 21px);
  line-height: 1.5;
  color: var(--ink-soft);
  margin-bottom: 26px;
  max-width: 34em;
}

.kicker {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  letter-spacing: 1.4px;
  text-transform: uppercase;
  color: var(--mono);
  margin: 0 0 12px;
}

section { margin: 74px 0 0; }

/* ---------- hero ---------- */

/* Two columns once there is room, so the page does not read as a single narrow
   ribbon of content floating in the middle of a large background. Below that it
   stacks, copy first. */
.hero { display: grid; gap: 30px; align-items: center; }

@media (min-width: 960px) {
  .hero { grid-template-columns: 0.92fr 1.08fr; gap: 48px; margin-top: 18px; }
  .hero-copy .lede { margin-bottom: 24px; }
}

.hero-copy h1 { margin-bottom: 16px; }

/* ---------- hero demo ---------- */

.demo { padding: 24px 22px; }
.hero-demo .demo { margin: 0; }

.demo-products {
  display: grid;
  gap: 12px;
  grid-template-columns: 1fr;
}
/* Three across only where the column is genuinely wide enough. Between 960 and
   1100 the hero is two columns and the demo sits in the narrower of the two, so
   three cards would be cramped rather than compact. */
@media (min-width: 600px) and (max-width: 959px) {
  .demo-products { grid-template-columns: repeat(3, 1fr); }
}
@media (min-width: 1100px) {
  .demo-products { grid-template-columns: repeat(3, 1fr); }
}

.product {
  background: var(--glass-hi);
  border: 1px solid var(--edge);
  border-radius: 13px;
  padding: 13px 15px;
}

.product .name {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  margin: 0 0 9px;
}

.product .dose {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 21px;
  color: var(--ink);
  margin: 0;
  line-height: 1.2;
}

.product .pct { font-size: 12.5px; color: var(--mono); margin: 2px 0 0; }

.demo-join {
  text-align: center;
  font-size: 12.5px;
  color: var(--mono);
  margin: 16px 0 14px;
  letter-spacing: 0.2px;
}

.total {
  background: var(--bad-bg);
  border: 1px solid rgba(196, 57, 43, 0.26);
  border-radius: 14px;
  padding: 16px 18px;
}

.total-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
}

.total-name { font-size: 14.5px; font-weight: 600; color: var(--ink); }

.total-figure {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: clamp(26px, 5vw, 34px);
  color: var(--bad);
  line-height: 1.1;
  font-variant-numeric: tabular-nums;
}

.bar {
  height: 9px;
  border-radius: 999px;
  background: rgba(31, 36, 32, 0.10);
  overflow: hidden;
  margin: 13px 0 10px;
}

.bar span {
  display: block;
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--poor), var(--bad));
  transition: width 1.5s cubic-bezier(0.16, 1, 0.3, 1);
}

.total-note { font-size: 13.5px; color: var(--ink-soft); margin: 0; }

/* ---------- steps ---------- */

.steps { display: grid; gap: 14px; counter-reset: step; }
@media (min-width: 700px) { .steps { grid-template-columns: repeat(3, 1fr); } }

.step { padding: 20px 21px; }

.step .num {
  font-family: var(--display);
  font-weight: 700;
  font-size: 13px;
  color: var(--exc);
  display: block;
  margin-bottom: 9px;
}

.step p { margin: 0; font-size: 15px; }

/* ---------- features ---------- */

.features { display: grid; gap: 14px; }
@media (min-width: 700px) { .features { grid-template-columns: 1fr 1fr; } }
@media (min-width: 1000px) { .features { grid-template-columns: repeat(3, 1fr); } }

.feature { padding: 20px 21px; }
.feature p { margin: 0; font-size: 15px; }

/* ---------- honesty ---------- */

.limits { padding: 24px 24px 10px; }

.limit {
  display: flex;
  gap: 12px;
  padding: 13px 0;
  border-top: 1px solid var(--hair);
}
.limit:first-of-type { border-top: 0; padding-top: 0; }

.limit svg { flex: none; width: 17px; height: 17px; margin-top: 3px; color: var(--mono); }
.limit p { margin: 0; font-size: 15px; }

/* ---------- faq ---------- */

.faq { padding: 6px 24px; }

.faq details { border-top: 1px solid var(--hair); }
.faq details:first-of-type { border-top: 0; }

.faq summary {
  cursor: pointer;
  padding: 17px 0;
  font-weight: 600;
  color: var(--ink);
  font-size: 15.5px;
  list-style: none;
  /* Opening and closing one of these is a double click, which otherwise
     selects the question text and leaves it highlighted. The answer below
     stays selectable, only the toggle is not. */
  -webkit-user-select: none;
  user-select: none;
}
.faq summary::-webkit-details-marker { display: none; }
.faq details p { font-size: 15px; padding-bottom: 4px; max-width: 74ch; }

/* ---------- misc blocks ---------- */

/* The single claim a section is built around. Larger than body text so it reads
   as a statement rather than another paragraph. */
.standout { padding: 26px 28px; margin: 24px 0 18px; }
.standout p {
  font-size: clamp(16.5px, 2vw, 19px);
  line-height: 1.52;
  max-width: 58ch;
}
.standout p:last-child { margin-bottom: 0; }
.standout em { font-style: italic; color: var(--ink); }

.card { padding: 21px 23px; margin: 22px 0; }
.card :last-child { margin-bottom: 0; }

.note {
  background: var(--ok-bg);
  border: 1px solid rgba(38, 129, 74, 0.22);
  border-radius: var(--radius);
  padding: 18px 21px;
  margin: 24px 0;
}
.note :last-child { margin-bottom: 0; }

.updated { font-size: 14px; color: var(--mono); margin-bottom: 28px; }

table { width: 100%; border-collapse: collapse; margin: 18px 0; font-size: 15px; }
th, td {
  text-align: left;
  padding: 11px 12px 11px 0;
  border-bottom: 1px solid var(--hair);
  vertical-align: top;
}
th { color: var(--ink); font-weight: 600; }
td { color: var(--ink-soft); }

.cta {
  display: inline-block;
  background: var(--ink);
  color: var(--paper);
  text-decoration: none;
  font-weight: 600;
  font-size: 15.5px;
  padding: 13px 24px;
  border-radius: 999px;
  box-shadow: var(--shadow);
}
.cta:hover { opacity: 0.9; }

.cta-row { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.cta-note { font-size: 14px; color: var(--mono); margin: 0; }

.disclaimer {
  font-size: 13.5px;
  color: var(--mono);
  line-height: 1.55;
  margin-top: 22px;
}

/* ---------- full bleed band ---------- */

/* Breaks out of `.wrap` to the viewport edge. The fill is the app's dark
   Verdict palette rather than an invented colour, so the section reads as the
   product in dark mode instead of as a different brand. */
.band {
  margin-left: calc(-50vw + 50%);
  margin-right: calc(-50vw + 50%);
  padding: 66px 22px 60px;
  background: #0E130F;
  color: #E9EFE9;
}

.band-inner { max-width: 1040px; margin: 0 auto; }
.band h2 { color: #E9EFE9; }
.band p { color: #A2AFA6; }
.band .kicker { color: #7B8A81; }

/* Light variant. Alternating full width bands are what stop the page reading as
   one narrow column of content floating on a large empty background. */
.band--light {
  background: rgba(255, 255, 255, 0.40);
  border-top: 1px solid var(--edge);
  border-bottom: 1px solid var(--edge);
  -webkit-backdrop-filter: blur(14px) saturate(1.2);
  backdrop-filter: blur(14px) saturate(1.2);
}
/* ⚠️ Written as `.band.band--light` deliberately. `.band h3` is declared much
   further down the file, and at equal specificity the later rule wins, so a
   plain `.band--light h3` here loses and the headings render near-white on
   cream. Invisible rather than merely wrong, which is why it reads as a font
   problem rather than a colour one.
   Doubling the class outranks `.band` whatever the source order, so these hold
   wherever they sit in the file. Every element the dark band recolours needs
   its counterpart here. */
.band.band--light h2,
.band.band--light h3 { color: var(--ink); }
.band.band--light p { color: var(--ink-soft); }
.band.band--light .kicker { color: var(--mono); }

/* ⚠️ The blanket `p` rule above is 0,2,1, so it outranks any component rule that
   colours a paragraph by class alone, whatever the source order. That is how the
   red wasted-spend figure ended up rendering in body grey: `.bignum .figure` sets
   `--bad` at 0,2,0 and quietly lost. It reads as a design choice rather than a
   bug, which is why it survived a deploy. Any coloured paragraph inside a light
   band needs a line here, at 0,3,0, to win. */
.band.band--light .bignum .figure { color: var(--bad); }

/* ---------- screenshots ---------- */

/* ⚠️ Not a wall of equal tiles. Nine screenshots at the same size in a masonry
   grid reads as a dump: nothing leads, so nothing is looked at. The four that
   carry an argument get a row each with copy beside them and alternate sides;
   the rest are a smaller supporting strip underneath. Hierarchy is the whole
   point, so do not flatten these back into one grid. */

.rows { margin-top: 40px; }

.row {
  display: grid;
  gap: 22px;
  align-items: center;
  margin-bottom: 44px;
}

@media (min-width: 880px) {
  .row { grid-template-columns: 1fr 1fr; gap: 48px; margin-bottom: 62px; }
  /* Alternate which side the panel sits on, so the eye zigzags down the section
     instead of running straight past one column.
     ⚠️ Must list every panel class. This silently stopped working when the
     screenshots became recreated `.ui` panels and the selector still said
     `.shot`: nothing errors, the rows just all line up on one side again. */
  .row:nth-child(even) .ui,
  .row:nth-child(even) .shot { order: 2; }
}

.row-copy h3 {
  font-size: clamp(19px, 2.4vw, 23px);
  font-weight: 700;
  letter-spacing: -0.5px;
  margin-bottom: 10px;
}
.row-copy p { margin: 0; max-width: 40ch; }

/* ⚠️ There was a three-across strip here for the leftovers and it was removed.
   Grid stretches every cell to the tallest row, so the shorter screenshots sat
   in tall slabs of empty mat. `align-items: start` would have fixed the
   symptom, but the section reads better with every screenshot given a row and
   an explanation, so there is no leftover tier at all now. */

.band h3 { color: #E9EFE9; }

/* ---------- recreated app panels ----------
   ⚠️ These replaced real screenshots, and the reason is worth keeping.
   Captures brought three problems: JPEG edges that had to be cropped to the
   pixel, inconsistent framing depending on whether the capture happened to
   include app background, and 527KB of images. Recreating the panels in markup
   is crisp at any size, weighs nothing, restyles with the palette, and cannot
   develop an edge artefact.

   ⚠️ The cost is that these can drift from the product. Every panel below was
   built from a real capture in `screenshots/raw`, and they must keep matching
   what the app actually renders. This site has already once described a feature
   that did not exist. If a screen changes, change these. */

.ui {
  background: linear-gradient(168deg, #FCFAF6 0%, #F4EFE6 100%);
  border: 1px solid rgba(255, 255, 255, 0.9);
  border-radius: 18px;
  padding: 18px 19px;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.18),
              0 22px 48px rgba(0, 0, 0, 0.28);
  color: var(--ink);
  font-size: 14px;
  line-height: 1.5;
}

.ui-kicker {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 10.5px;
  letter-spacing: 1.6px;
  color: var(--mono);
  margin: 0 0 12px;
}

.ui-note { font-size: 12.5px; color: var(--mono); margin: 12px 0 0; line-height: 1.45; }
.ui p { color: var(--ink-soft); }

/* rows shared by ingredients, totals and reminders */
.ui-row { display: flex; gap: 10px; padding: 11px 0; border-top: 1px solid var(--hair); }
.ui-row:first-of-type { border-top: 0; padding-top: 2px; }
.ui-row .body { flex: 1; min-width: 0; }
.ui-dot { width: 8px; height: 8px; border-radius: 4px; flex: none; margin-top: 6px; }
.ui-name { font-weight: 600; color: var(--ink); font-size: 14px; }
.ui-dose {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  color: var(--mono);
  margin-left: 7px;
}
.ui-desc { font-size: 12.5px; color: var(--ink-soft); margin: 3px 0 0; }
.ui-status { font-size: 12.5px; font-weight: 600; margin: 3px 0 0; }

.ui-tag {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 9.5px;
  letter-spacing: 0.6px;
  padding: 3px 8px;
  border-radius: 999px;
  white-space: nowrap;
  flex: none;
  align-self: flex-start;
}
.tag-exc  { background: var(--ok-bg);   color: var(--exc); }
.tag-poor { background: var(--warn-bg); color: var(--poor); }
.tag-bad  { background: var(--bad-bg);  color: var(--bad); }

/* score header */
.ui-score { display: flex; gap: 16px; align-items: center; }
.ui-ring { width: 92px; height: 92px; flex: none; }
.ui-verdict {
  font-family: var(--display);
  font-weight: 700;
  font-size: 25px;
  letter-spacing: -0.4px;
  color: var(--good);
  margin: 2px 0 6px;
}
.ui-part { display: flex; align-items: center; gap: 10px; margin-top: 9px; font-size: 13px; }
.ui-part span:first-child { width: 62px; color: var(--ink); }
.ui-part .track { flex: 1; height: 5px; border-radius: 3px; background: rgba(31, 36, 32, 0.10); }
.ui-part .track i { display: block; height: 100%; border-radius: 3px; }
.ui-part .val {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12.5px;
  color: var(--ink-soft);
  width: 22px;
  text-align: right;
}

/* alert box */
.ui-alert {
  display: flex;
  gap: 11px;
  background: var(--warn-bg);
  border-radius: 13px;
  padding: 13px 14px;
}
/* ⚠️ The mark is an SVG, not a text "!". As text it sits on its baseline
   inside the circle and reads low and slightly left however the flexbox is
   set, because centring a glyph box is not the same as centring the ink in it.
   Drawn, it is exactly centred. */
.ui-alert .ico {
  width: 22px; height: 22px; border-radius: 11px; flex: none;
  background: var(--poor);
  display: flex; align-items: center; justify-content: center;
}
.ui-alert strong { display: block; margin-bottom: 3px; }
.ui-alert p { margin: 0; font-size: 13px; }

/* cost cells */
.ui-cells {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  border: 1px solid var(--hair);
  border-radius: 13px;
  overflow: hidden;
}
.ui-cell { padding: 14px 10px; text-align: center; border-left: 1px solid var(--hair); }
.ui-cell:first-child { border-left: 0; }
.ui-cell b {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: 25px;
  letter-spacing: -0.6px;
}
.ui-cell span { font-size: 12px; color: var(--ink-soft); }

/* timing / reminder icon rows */
.ui-item { display: flex; gap: 11px; margin-top: 14px; }
.ui-item:first-of-type { margin-top: 0; }
.ui-item .ico { color: var(--blue, #3A6EBE); flex: none; margin-top: 1px; }
.ui-item strong { display: block; font-size: 13.5px; margin-bottom: 2px; }
.ui-item p { margin: 0; font-size: 12.5px; }

/* ⚠️ Bells are drawn, not emoji. A 🔔 renders as a full-colour glyph from the
   system emoji font, which is a completely different object from the app's line
   icon and cannot be tinted, so an off slot looked identical to an on one. */
.ui-bell { width: 16px; height: 16px; flex: none; color: var(--blue, #3A6EBE); }
.ui-off .ui-bell { color: var(--mono); }

.ui-slot { display: flex; align-items: center; gap: 11px; padding: 9px 0; }
.ui-slot .when { flex: 1; }
.ui-slot .when b { display: block; font-size: 13.5px; }
.ui-slot .when span { font-size: 12.5px; color: var(--ink-soft); }
.ui-tick {
  width: 22px; height: 22px; border-radius: 11px; flex: none;
  border: 1px solid var(--hair);
  display: flex; align-items: center; justify-content: center;
  color: var(--mono); font-size: 12px;
}
.ui-off { opacity: 0.45; }

/* The mat, in Porcelain's own `paper`, so a capture's surroundings continue
   into it rather than stopping at a visible edge. Kept for any real capture
   that gets used again. */
.shot {
  margin: 0 0 16px;
  padding: 10px;
  background: var(--paper);
  border-radius: 22px;
  box-shadow: 0 3px 8px rgba(0, 0, 0, 0.20),
              0 20px 44px rgba(0, 0, 0, 0.30);
}

/* ⚠️ The hairline border is what makes these look consistent, and it is doing
   real work. Captures that frame a card are lighter than the mat, so they look
   framed on their own. Captures that include app *background* are the same
   cream as the mat and dissolve into it, which is why some looked like they had
   an edge and others looked like they trailed off. An explicit border gives
   every one the same defined edge whatever it contains.
   It also has to be stroked in CSS rather than baked into the JPEG: the border
   and the corner must follow the same curve, and a baked radius cannot, since
   it would have to be re-derived from whatever width the image renders at. */
.shot img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 12px;
  border: 1px solid rgba(31, 36, 32, 0.13);
}

.shot figcaption {
  font-size: 12.5px;
  line-height: 1.45;
  color: #7B8A81;
  text-align: center;
  padding: 10px 6px 3px;
}

/* ---------- big number ---------- */

/* ---------- the money ---------- */

.money { display: grid; gap: 14px; align-items: stretch; }
@media (min-width: 860px) { .money { grid-template-columns: 0.85fr 1.15fr; } }

.bignum { padding: 30px 28px; display: flex; flex-direction: column; justify-content: center; }
.bignum .figure {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(52px, 9vw, 80px);
  line-height: 1;
  letter-spacing: -2.6px;
  color: var(--bad);
  margin: 8px 0 10px;
}
.bignum .sub { font-size: 15px; color: var(--ink-soft); margin: 0; max-width: 32ch; }

/* The itemised half. This is what turns a headline figure into something you
   believe: named products, real prices, and a reason attached to each one. */
.ledger { padding: 22px 24px; }
.ledger-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  padding-bottom: 12px;
  margin-bottom: 4px;
  border-bottom: 1px solid var(--hair);
}
.ledger-head strong { font-size: 14.5px; }
.ledger-head span { font-size: 13px; color: var(--mono); }

.line {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 4px 14px;
  padding: 12px 0;
  border-bottom: 1px solid var(--hair);
}
.line:last-of-type { border-bottom: 0; }
.line .what { font-size: 14.5px; color: var(--ink); font-weight: 600; }
.line .cost {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 14px;
  color: var(--ink);
  text-align: right;
}
.line .why { font-size: 13px; color: var(--ink-soft); grid-column: 1 / -1; }

.pill {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.2px;
  padding: 2px 8px;
  border-radius: 999px;
  margin-right: 7px;
  vertical-align: 1px;
}
.pill.waste { background: var(--bad-bg); color: var(--bad); }
.pill.keep { background: var(--ok-bg); color: var(--exc); }

.ledger-total {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  margin-top: 14px;
  padding-top: 14px;
  border-top: 1px solid var(--hair);
  font-size: 14.5px;
}
.ledger-total strong { color: var(--ink); }
.ledger-total .waste-figure {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--bad);
  font-size: 15px;
}

.tiles { display: grid; gap: 10px; margin-top: 22px; }
@media (min-width: 640px) { .tiles { grid-template-columns: repeat(4, 1fr); } }
.tile {
  background: var(--glass-hi);
  border: 1px solid var(--edge);
  border-radius: 12px;
  padding: 13px 11px;
}
.tile strong { display: block; font-size: 14px; margin-bottom: 3px; }
.tile span { font-size: 12.5px; color: var(--mono); }

/* ---------- topics ---------- */

.topics { margin-top: 30px; }
.topics h3 { font-size: 14px; margin-bottom: 10px; }
.topics a {
  display: inline-block;
  color: var(--ink-soft);
  text-decoration: none;
  font-size: 13.5px;
  margin: 0 14px 7px 0;
}
.topics a:hover { color: var(--ink); text-decoration: underline; }

/* ==================================================================
   MOTION
   ------------------------------------------------------------------
   Rules this follows, because a marketing page that janks is worse
   than a static one:

   1. Transform and opacity only. Nothing here animates width, top,
      or anything else that forces layout, with the single exception
      of the miniature bars above, which are tiny and one-shot.
   2. Everything reveal-based is one-shot. Elements do not re-animate
      when you scroll back up, which is the thing that makes these
      pages feel like a fairground.
   3. `prefers-reduced-motion` is honoured at the bottom of the file
      by disabling every animation and transition, so each of these
      must be written to look correct in its final state.
   ================================================================== */

/* ⚠️ Scoped to `.js`, which the script adds to <html> as its first act. Without
   that scope, anyone with JavaScript blocked or a script that failed to parse
   gets a blank page, because the hidden state would apply with nothing left to
   remove it. Hiding content on the assumption that JS will run is how a
   marketing page ends up showing nothing at all. */
/* ⚠️ Uses `translate`, not `transform`, and that is load-bearing.
   `.js .reveal.in` outscores `.tile:hover` on specificity, so when both wrote
   to `transform` the reveal silently won and hover lift did nothing at all
   once a card had appeared. `translate` is an independent property, so the two
   compose instead of competing and neither needs to know about the other.
   Do not fold this back into `transform`. */
/* ⚠️ Longhands, not the `transition` shorthand, and that is load-bearing too.
   The shorthand resets every transition property, so writing it here wiped the
   `transform` and `box-shadow` transitions that hover relies on. The card still
   had a hover state, it just had no transition attached, so it snapped. Both
   sets of properties are therefore declared together, with the reveal delay
   applied only to the two properties that reveal uses.
   Order below is: opacity, translate, transform, box-shadow, border-color. */
.js .reveal {
  opacity: 0;
  translate: 0 15px;
  transition-property: opacity, translate, transform, box-shadow, border-color;
  transition-duration: 0.65s, 0.65s, 0.5s, 0.5s, 0.5s;
  transition-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: var(--d, 0ms), var(--d, 0ms), 0ms, 0ms, 0ms;
}
.js .reveal.in { opacity: 1; translate: 0 0; }

/* Slow drifting light over the fixed stone base. Transform only, so it is
   composited on the GPU and costs nothing per frame. Deliberately slow enough
   that you never catch it moving, you only notice the light is not quite where
   it was. */
.aurora {
  position: fixed;
  inset: -25%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(58ch 52ch at 24% 18%, rgba(255, 255, 255, 0.55), transparent 62%),
    radial-gradient(54ch 50ch at 78% 68%, rgba(132, 172, 139, 0.28), transparent 64%);
  animation: drift 52s ease-in-out infinite alternate;
  will-change: transform;
}

@keyframes drift {
  from { transform: translate3d(-1.5%, -1%, 0) scale(1); }
  to   { transform: translate3d(2%, 2.5%, 0) scale(1.07); }
}

/* Sticky glass header. It is already a floating pill, so this costs one
   property and makes the page feel like an app rather than a document. */
header.site { position: sticky; top: 10px; z-index: 40; }

/* Lift on hover. Only on devices that actually have a pointer, so a tap on a
   phone does not leave a card stuck in its hover state. */
@media (hover: hover) {
  /* For anything that is not also a `.reveal`, which declares its own set. */
  .product, .shot {
    transition:
      transform 0.5s cubic-bezier(0.22, 1, 0.36, 1),
      box-shadow 0.5s cubic-bezier(0.22, 1, 0.36, 1),
      border-color 0.5s;
  }
  /* Small on purpose. The shadow does most of the work, the movement only has
     to confirm the card is a live surface rather than announce it. */
  .feature:hover, .step:hover, .tile:hover, .ledger:hover, .bignum:hover {
    transform: translateY(-4px) scale(1.008);
    border-color: #FFFFFF;
    box-shadow: 0 3px 7px rgba(31, 36, 32, 0.07),
                0 22px 46px rgba(31, 36, 32, 0.15);
  }
  .product:hover { transform: translateY(-3px); border-color: #FFFFFF; }
  .shot:hover {
    transform: translateY(-5px);
    box-shadow: 0 5px 12px rgba(0, 0, 0, 0.24),
                0 28px 58px rgba(0, 0, 0, 0.38);
  }
  .cta { transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.3s; }
  .cta:hover {
    transform: translateY(-2px);
    box-shadow: 0 3px 6px rgba(31, 36, 32, 0.12),
                0 16px 34px rgba(31, 36, 32, 0.22);
  }
  header.site nav a { transition: color 0.22s; }
}

/* FAQ answers slide rather than snap. `details` cannot transition its own
   height without newer CSS than is safe to rely on, so the child animates
   instead, which gets most of the effect for none of the risk.
   ⚠️ Closing needs JavaScript. The browser removes the content the instant
   `open` is unset, so there is nothing left on screen to animate out. The
   script defers unsetting `open` until `faqOut` has finished, which is the only
   reason the close is not an instant drop. */
.faq details[open] > p { animation: faqIn 0.32s cubic-bezier(0.16, 1, 0.3, 1); }
.faq details > p.closing { animation: faqOut 0.24s cubic-bezier(0.4, 0, 1, 1) forwards; }

@keyframes faqIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}

@keyframes faqOut {
  from { opacity: 1; transform: none; }
  to   { opacity: 0; transform: translateY(-6px); }
}

/* The +/- marker turns rather than swapping glyph, which reads as one control
   changing state instead of two different labels. */
.faq summary::after {
  content: ' +';
  color: var(--mono);
  display: inline-block;
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1), color 0.2s;
}
.faq details[open] summary::after { transform: rotate(135deg); color: var(--exc); }

.faq summary { transition: color 0.2s; }
.faq summary:hover { color: var(--exc); }

/* The hero figure gets a soft pulse the moment it finishes counting, which
   marks the arrival of the number rather than letting it just stop. */
.total-figure.landed { animation: land 0.5s cubic-bezier(0.16, 1, 0.3, 1); }

@keyframes land {
  0%   { transform: scale(1); }
  38%  { transform: scale(1.055); }
  100% { transform: scale(1); }
}

/* ---------- footer ---------- */

footer.site {
  margin-top: 76px;
  padding: 22px 24px;
  font-size: 14px;
  color: var(--mono);
}

footer.site a { color: var(--ink-soft); margin-right: 16px; }
footer.site p { color: var(--mono); font-size: 13.5px; }
