/* breathlab.org secondary pages
   Components shared by the generated blog/library pages and the hand-owned
   pillar, legal, roundup and Android pages. Loaded after site.css, which owns
   the tokens, chrome (nav, menu, footer), buttons, .card and .prose.
   GUTTER RULE still applies: .wrap* owns padding-inline, sections padding-block. */

/* ---------- widths ---------- */
.wrap-mid{max-width:calc(960px + 2 * var(--gutter));margin-inline:auto;padding-inline:var(--gutter)}
/* the roundup: one 1120px column shared by its hero, contents row, ranking
   cards, criteria and table, so every block has the same two edges */
.wrap-wide{max-width:calc(1120px + 2 * var(--gutter));margin-inline:auto;padding-inline:var(--gutter)}

/* ---------- page hero (light top, nav sits transparent over it) ---------- */
.page-hero{position:relative;padding-top:calc(var(--nav-h) + clamp(36px,5vw,64px));padding-bottom:clamp(32px,4.5vw,56px);isolation:isolate}
.page-hero::before{content:"";position:absolute;inset:0 0 -40% 0;z-index:-1;pointer-events:none;
  background:radial-gradient(60% 70% at 8% -10%,rgba(47,180,84,.16),transparent 65%)}
.page-hero h1{font-size:clamp(40px,5.5vw,72px);margin-top:18px;max-width:18ch;font-variation-settings:"opsz" 72}
.page-hero .lead{margin-top:clamp(16px,2vw,24px);max-width:58ch}
.page-hero.center{text-align:center}
.page-hero.center h1,.page-hero.center .lead{margin-inline:auto}
.page-hero.center .eyebrow{justify-content:center}
.page-hero.center .byline{justify-content:center}

/* byline row: date, author, reading time, last updated. Metadata reads in
   the body face, sentence case and muted; mono is kept for the eyebrow and
   the numeric readouts only */
.byline{--sep:33px;display:flex;flex-wrap:wrap;align-items:center;gap:10px 0;margin-top:clamp(22px,3vw,32px);
  margin-left:calc(-1 * var(--sep));clip-path:inset(-6px -6px -6px var(--sep));
  font-family:var(--font-body);font-size:14px;letter-spacing:0;text-transform:none;color:var(--muted);font-variant-numeric:tabular-nums}
.byline b{font-weight:500;color:var(--ink)}
.byline span{display:inline-flex;align-items:center;gap:.5em;white-space:nowrap}
/* every item carries a dot; the row starts --sep to the left of the column
   and clip-path hides that strip, so the dot that would open a line (the
   first, and the first of any wrapped line) is never seen and every line
   starts flush with the column */
.byline span::before{content:"";width:5px;height:5px;border-radius:50%;background:var(--leaf);margin-inline:14px calc(14px - .5em);flex:none}
.page-hero.center .byline{margin-left:0;clip-path:none}
.page-hero.center .byline span:first-child::before{display:none}
@media (max-width:480px){.byline{font-size:13.5px}}
/* a one-sentence note under a hero (the roundup's data line) */
.hero-note{margin-top:clamp(22px,3vw,32px);font-size:14px;line-height:1.5;color:var(--muted);max-width:60ch;font-variant-numeric:tabular-nums}
.hero-note b{font-weight:500;color:var(--ink)}
/* small outlined pills for the feed links in an index byline */
.byline .pills{gap:8px}
.byline .pills::before{margin-inline:14px 6px}
.byline a.pill{display:inline-flex;align-items:center;gap:6px;min-height:44px;padding:0 13px;margin-block:-6px;
  position:relative;isolation:isolate;color:var(--ink);font-size:13px;font-weight:500;letter-spacing:0;text-decoration:none}
/* the 32px pill is drawn by ::before, so the 44px anchor stays a comfortable
   tap target while the visible chip keeps its size (the -6px margins pay the
   difference back to the row) */
.byline a.pill::before{content:"";position:absolute;inset:6px 0;z-index:-1;border-radius:999px;
  border:1px solid var(--line-strong);background:rgba(255,255,255,.4);transition:background .2s ease,border-color .2s ease}
.byline a.pill:hover{color:var(--ink)}
.byline a.pill:hover::before{background:var(--white);border-color:var(--ink)}
.byline a.pill svg{width:12px;height:12px;flex:none}
/* a linked eyebrow (the article's "Journal") and a byline link are 20 to
   22px of text; give them a 44px box and pay the extra back with margins so
   the hero's rhythm does not move */
.page-hero a.eyebrow{min-height:44px;margin-block:-12px}
.byline span>a{display:inline-flex;align-items:center;min-height:44px;margin-block:-11px}

/* ---------- article layout: one centred measure, contents rail in the right gutter ---------- */
/* the measure is one column system for the whole page: the body sits in it,
   centred in the 1120px container, so the left and right margins are equal.
   The article and pillar heroes share the centre line but their title block
   may run to --title-measure (880px): a display title at 52 to 56px needs the
   room, or it stacks four deep and breaks a phrase mid-way. The compact hero
   (support, privacy, terms) has one-word titles and stays on the measure */
:root{--measure:620px;--title-measure:880px;--head-inset:0px}
/* under 1100 the body is one column at the gutter, so every hero column
   starts at the gutter too (one left edge at 1024 as well as at 1440) */
.article-hero .wrap-mid,.pillar-hero .wrap-mid,.page-hero.compact .wrap-mid{max-width:calc(1120px + 2 * var(--gutter))}
@media (min-width:1100px){
  /* the header block (eyebrow, title, standfirst, byline) starts on the
     .prose column's left edge: the title block is --title-measure wide and
     centred on the same axis as the measure, so the inset is half their
     difference. Only the cover bleeds back out to the full title block, by
     the same amount, so the page has one text edge and one image edge */
  .article-hero .wrap-mid,.pillar-hero .wrap-mid{--head-inset:calc((var(--title-measure) - var(--measure)) / 2);
    max-width:calc(var(--title-measure) + 2 * var(--gutter));padding-left:calc(var(--gutter) + var(--head-inset))}
  /* the cover sits on the text column: the same left edge as the title and
     the body, and the body's measure, so the page has one edge (a cover
     bled back out to the 880px title block sat 130px left of the text) */
  .article-hero .cover,.pillar-hero .cover{margin-left:0;max-width:var(--measure)}
  .page-hero.compact .wrap-mid{max-width:calc(var(--measure) + 2 * var(--gutter))}
}
/* a title over ~30 characters (build-site.py adds .long) drops from 72px to
   the 52 to 56px band, gets the whole title block, and balances its lines so
   the last line is never a single word */
.page-hero h1.long{font-size:clamp(44px,4.2vw,56px);max-width:none;text-wrap:balance;font-variation-settings:"opsz" 56}
/* phones: the same 36px the article hero already uses for every title there */
@media (max-width:640px){.page-hero h1.long{font-size:36px}}
.article{padding-block:clamp(32px,5vw,64px) clamp(72px,9vw,128px)}
/* a hero with no cover (support, privacy, terms) ends on its byline; the
   gap to the rail is the cover's 48px, not the article's 120 */
.page-hero.compact{padding-bottom:clamp(16px,2vw,24px)}
.page-hero.compact+.article{padding-top:clamp(20px,2.5vw,32px)}
.article-grid{display:grid;grid-template-columns:minmax(0,1fr);row-gap:40px;
  max-width:calc(1120px + 2 * var(--gutter));margin-inline:auto;padding-inline:var(--gutter)}
.article-grid>.prose{grid-column:1;min-width:0}
.article-grid>.prose>h2:first-child{margin-top:0}
.article-grid>.article-foot{grid-column:1/-1}
@media (min-width:1100px){
  /* symmetric side columns either side of the measure: the measure is
     centred, the rail hangs in the RIGHT column, flush against the measure,
     and the left column stays empty (a rail on the left with nothing on the
     right read as the page listing to one side). The side columns bottom
     out at 120px, which still leaves the measure its full width at 1100
     (1020 inner, less 680 and two 44px gaps, is 126 a side) */
  .article-grid{grid-template-columns:minmax(120px,1fr) minmax(0,var(--measure)) minmax(120px,1fr);column-gap:44px;align-items:start}
  .article-grid>.prose,.article-grid>.article-foot{grid-column:2}
  /* the closing card stays on the measure: the rail is sticky within the
     whole grid, so a card bleeding into the third column would run under it */
  /* a page with no rail (support, or an article too short for a contents
     list) drops the side columns: one column at the measure, centred, so
     the body is not laid out around an aside that is not there */
  .article-grid.no-aside{grid-template-columns:minmax(0,var(--measure));justify-content:center}
  .article-grid.no-aside>.prose,.article-grid.no-aside>.article-foot{grid-column:1}
}

/* cover, the full width of the hero column */
.cover{position:relative;margin-top:clamp(28px,4vw,48px);isolation:isolate}
.cover::before{content:"";position:absolute;inset:12% -3% -8% -3%;z-index:-1;border-radius:50%;
  background:radial-gradient(50% 50% at 50% 60%,rgba(47,180,84,.22),transparent 70%);filter:blur(28px)}
.cover img{width:100%;aspect-ratio:16/9;object-fit:cover;border-radius:var(--r-lg);box-shadow:var(--shadow-soft);background:var(--dew-2)}
.cover figcaption{margin-top:14px;font-size:13.5px;line-height:1.5;color:var(--muted);max-width:70ch}

/* table of contents. Its base form is a row of outlined chips (the byline
   pill) that scrolls sideways and bleeds to the viewport edges, the active
   chip filled. The strip carries a full gutter of trailing padding past the
   gutter, and its right edge dissolves over 112px (a 64px fade on a 13px
   chip still read as a hard clip at 390: "Reading th", "Co"), so a chip that
   runs under the edge fades out over most of a word rather than being cut
   inside it. pages.js marks the strip .is-scrollable / .is-scrolled /
   .at-end: at the end of the row the right fade lifts so the last chip is
   crisp, once scrolled the left edge fades too, and while there is more to
   the right a small chevron sits in the fade as the scroll hint. Without
   JS the right fade is simply always on. The roundup keeps the row at every
   width (.toc-row); the article pages turn it into the sticky rail from
   1100px */
/* flow-root: the strip's -6px margins stay inside the nav's box wherever it
   sits, so the nav is 64px tall both as a grid item (articles, pillars) and
   as a block in flow (the roundup), and the chevron's offset holds. Without
   it the roundup's nav was 6px taller and the chevron sat 6px low */
.toc{line-height:1.4;position:relative;display:flow-root}
.toc-title{font-size:13px;font-weight:500;color:var(--muted-2);margin-bottom:10px}
.toc ol{list-style:none;margin:0;display:flex;flex-wrap:nowrap;gap:8px;overflow-x:auto;scrollbar-width:none;
  margin-inline:calc(-1 * var(--gutter));padding-inline:var(--gutter) calc(2 * var(--gutter));padding-block:8px;margin-block:-6px;
  scroll-padding-inline:var(--gutter);-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain;
  -webkit-mask-image:linear-gradient(90deg,#000 calc(100% - 112px),rgba(0,0,0,.45) calc(100% - 56px),transparent calc(100% - 8px));
  mask-image:linear-gradient(90deg,#000 calc(100% - 112px),rgba(0,0,0,.45) calc(100% - 56px),transparent calc(100% - 8px))}
.toc.at-end:not(.is-scrolled) ol{-webkit-mask-image:none;mask-image:none}
.toc.is-scrolled:not(.at-end) ol{
  -webkit-mask-image:linear-gradient(90deg,transparent 8px,#000 64px,#000 calc(100% - 112px),rgba(0,0,0,.45) calc(100% - 56px),transparent calc(100% - 8px));
  mask-image:linear-gradient(90deg,transparent 8px,#000 64px,#000 calc(100% - 112px),rgba(0,0,0,.45) calc(100% - 56px),transparent calc(100% - 8px))}
.toc.is-scrolled.at-end ol{
  -webkit-mask-image:linear-gradient(90deg,transparent 8px,#000 64px);
  mask-image:linear-gradient(90deg,transparent 8px,#000 64px)}
/* the scroll hint: a 7px chevron on the row's centre line, 16px in from the
   viewport edge (the strip bleeds a gutter past the column), shown only
   while the row can still scroll right. The chip's centre is 18px above the
   nav's bottom edge (the strip's -6px margin, 8px padding, 32px chip) */
.toc::after{content:"";position:absolute;right:calc(16px - var(--gutter));bottom:15px;width:7px;height:7px;
  border-right:1.5px solid var(--ink);border-bottom:1.5px solid var(--ink);transform:rotate(-45deg);
  pointer-events:none;opacity:0;transition:opacity .25s ease}
.toc.is-scrollable:not(.at-end)::after{opacity:.75}
.toc ol::-webkit-scrollbar{display:none}
.toc li{margin:0;flex:none}
/* each chip is a 44px anchor (the tap target) with -6px margins, so the
   strip keeps its 32px rhythm; the visible 32px chip is drawn by ::before.
   The strip's padding-block grew from 2px to 8px and its margin-block pays
   that back, so the anchors are never clipped by the scroller */
.toc a{display:inline-flex;align-items:center;min-height:44px;padding:0 13px;margin:-6px 0;position:relative;isolation:isolate;
  color:var(--ink);text-decoration:none;font-size:13px;font-weight:500;letter-spacing:0;white-space:nowrap;
  transition:color .2s ease}
.toc a::before{content:"";position:absolute;inset:6px 0;z-index:-1;border-radius:999px;
  border:1px solid var(--line-strong);background:rgba(255,255,255,.4);transition:background .2s ease,border-color .2s ease}
.toc a:hover::before{background:var(--white);border-color:var(--ink)}
.toc a.is-active{color:var(--white)}
.toc a.is-active::before{background:var(--ink);border-color:var(--ink)}
/* the roundup's contents row sits between the hero and the first heading;
   the hero has no cover, so the article starts closer to it */
.toc-row{margin-bottom:clamp(28px,4vw,44px)}
.article.roundup{padding-top:clamp(20px,2.5vw,32px)}
@media (min-width:1100px){
  /* the rail: 15px in the full ink so the entries read at a glance (14px
     in the second ink read as low contrast); the active entry is one weight
     up on a leaf tick, and the title one step darker than the chip row's */
  .toc:not(.toc-row){position:sticky;top:calc(var(--nav-h) + 28px);grid-column:3;grid-row:1;justify-self:start;width:100%;max-width:180px;font-size:15px}
  .toc:not(.toc-row) .toc-title{margin-bottom:14px;color:var(--muted)}
  .toc:not(.toc-row) ol,.toc:not(.toc-row).is-scrolled ol{display:block;overflow:visible;margin:0;padding:0;border-left:1px solid var(--line-strong);
    -webkit-mask-image:none;mask-image:none}
  .toc:not(.toc-row)::after{display:none}
  .toc:not(.toc-row) a{display:block;min-height:0;padding:6px 0 6px 16px;margin:0 0 0 -1px;border:0;border-left:1px solid transparent;
    border-radius:0;background:none;color:var(--ink);font-size:15px;font-weight:400;white-space:normal;
    transition:color .25s ease,border-color .25s ease,padding-left .3s var(--ease-out)}
  .toc:not(.toc-row) a::before{display:none}
  .toc:not(.toc-row) a:hover{color:var(--ink);background:none;border-color:transparent}
  .toc:not(.toc-row) a.is-active{color:var(--ink);font-weight:500;background:none;border-left-color:var(--leaf);padding-left:20px}
}

/* display headings: no ff / fi ligatures at display size ("Different").
   site.css sets this on h1 to h4; repeated here for the headings pages.css
   owns so the rule travels with them. The f in Bricolage Grotesque still
   touches a following f by design (its crossbar overhangs), which no
   ligature setting changes. */
.page-hero h1,.prose h2,.post-card h2,.post-card h3{font-variant-ligatures:no-common-ligatures}

/* prose refinements on top of site.css */
.prose h2[id],.prose h3[id]{scroll-margin-top:calc(var(--nav-h) + 24px)}
.prose h2{font-size:clamp(28px,3vw,38px)}
.prose blockquote{border:0;padding:6px 0 6px 30px;position:relative;font-family:var(--font-display);font-weight:500;
  font-size:clamp(22px,2.2vw,28px);letter-spacing:-.02em;line-height:1.3;color:var(--ink);margin-block:1.6em}
.prose blockquote::before{content:"";position:absolute;left:0;top:8px;bottom:8px;width:3px;border-radius:3px;background:linear-gradient(var(--leaf),var(--violet))}
.prose blockquote p{font-size:inherit}
.prose table{width:100%;border-collapse:collapse;font-size:15px}
.prose th,.prose td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--line);vertical-align:top}
.prose th{font-size:13px;color:var(--muted);font-weight:500}
.prose .note{font-size:15.5px}

/* buttons inside prose keep their own colour and no underline (.prose a
   would otherwise paint them green and underlined) */
.prose a.btn{text-decoration:none}
.prose a.btn-primary{color:#fff}
.prose .dark a.btn-primary{color:var(--ink)}
.prose a.btn-leaf{color:#04150A}
.prose a.btn-ghost{color:inherit}

/* the back link under the closing card: a 44px target */
.article-foot .link-arrow{min-height:44px}

/* soft callout card */
.note{background:rgba(255,255,255,.62);border:1px solid rgba(255,255,255,.85);border-radius:var(--r-md);padding:18px 22px;
  box-shadow:var(--shadow-soft);color:var(--ink-2);font-size:15.5px;line-height:1.6}
.note b{color:var(--ink)}
.note.warn{border-left:3px solid var(--gold)}

/* ---------- CTA band: dark glass card ---------- */
.cta-card{position:relative;overflow:hidden;border-radius:var(--r-xl);padding:clamp(32px,5vw,56px);
  display:grid;grid-template-columns:minmax(0,1fr) auto;gap:28px 40px;align-items:center;
  box-shadow:var(--shadow-deep);isolation:isolate}
.cta-card::before{content:"";position:absolute;inset:0;z-index:-1;pointer-events:none;
  background:
    radial-gradient(55% 80% at 100% 0%,rgba(47,180,84,.30),transparent 65%),
    radial-gradient(40% 60% at 0% 100%,rgba(124,108,240,.22),transparent 70%),
    linear-gradient(180deg,var(--deep-2),var(--deep))}
.cta-card .grain{z-index:0}
.cta-card>:not(.grain){position:relative;z-index:1}
/* eyebrow to headline is a fixed 24px wherever the card sits. Inside .prose
   the site-wide `.prose h3{margin-top:1.8em}` would otherwise open a ~75px
   gap above the headline, so the margin is pinned here with the same
   specificity the prose rule has */
.cta-card .eyebrow{display:flex;margin-bottom:0}
.cta-card h3,.prose .cta-card h3{font-size:clamp(26px,3.2vw,42px);line-height:1.05;letter-spacing:-.03em;max-width:18ch;color:#fff;margin-top:24px}
.cta-card p,.prose .cta-card p{color:var(--on-dark-muted);margin-top:12px;max-width:44ch;font-size:16.5px}
.cta-card .cta-actions{display:flex;flex-direction:column;align-items:flex-start;gap:12px}
.cta-card .cta-actions .mono{font-family:var(--font-body);font-size:13px;letter-spacing:0;color:var(--on-dark-muted)}
@media (max-width:720px){.cta-card{grid-template-columns:1fr}}
/* in the measure (the prose column, or the article foot that shares it) the
   card is 42rem wide at most: stack it */
.prose .cta-card,.article-foot .cta-card{grid-template-columns:1fr;gap:28px}
.prose .cta-card .cta-actions,.article-foot .cta-card .cta-actions{flex-direction:row;align-items:center;gap:20px;flex-wrap:wrap}
.prose .cta-card h3,.article-foot .cta-card h3{max-width:22ch}

/* ---------- index pages: post cards ---------- */
.post-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:clamp(18px,2.5vw,28px)}
.post-card{display:flex;flex-direction:column;overflow:hidden;text-decoration:none;color:inherit;padding:0;min-width:0}
.post-card .media{aspect-ratio:16/9;overflow:hidden;background:linear-gradient(135deg,var(--deep-2),var(--deep-3));position:relative;flex:none}
.post-card .media img{width:100%;height:100%;object-fit:cover;transition:transform 1.1s var(--ease-out)}
.post-card:hover .media img{transform:scale(1.045)}
/* a piece with no cover file gets a branded tile in every cover slot
   (build-site.py cover_or_tile): the slot's own dark green gradient, a leaf
   glow in one corner, and the title in the display face. The tile fills its
   slot exactly, so it takes the slot's radius (10px in a list row, the card's
   28px on the feature card) and never ships as an empty rectangle */
.cover-tile{position:absolute;inset:0;display:flex;align-items:flex-end;padding:clamp(10px,7%,28px);box-sizing:border-box;overflow:hidden;
  background:radial-gradient(70% 90% at 100% 0%,rgba(47,180,84,.38),transparent 65%),linear-gradient(135deg,var(--deep-2),var(--deep-3));
  color:#fff;font-family:var(--font-display);font-weight:500;letter-spacing:-.025em;line-height:1.08;text-wrap:balance;
  container-type:inline-size}
.cover-tile>span{display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:3;overflow:hidden;font-size:9cqi}
.cover-tile::before{content:"";position:absolute;left:0;bottom:0;width:22%;height:3px;background:var(--leaf)}
.latest-row .thumb,.post-card .media{position:relative}
.post-card.feature .cover-tile>span{-webkit-line-clamp:4}
.latest-row .thumb .cover-tile{padding:8px 10px}
.latest-row .thumb .cover-tile>span{-webkit-line-clamp:2;font-size:10cqi}
.latest-row .thumb .cover-tile::before{height:2px}
.post-card .body{padding:clamp(22px,2.6vw,30px) clamp(22px,2.6vw,30px) clamp(24px,2.8vw,32px);display:flex;flex-direction:column;gap:12px;flex:1}
.post-card time{font-size:13.5px;color:var(--muted);font-variant-numeric:tabular-nums}
.post-card h2{font-size:clamp(22px,2vw,27px);line-height:1.12;letter-spacing:-.025em;font-variation-settings:"opsz" 36}
.post-card p{color:var(--muted);font-size:15.5px;line-height:1.55}
.post-card .read{margin-top:auto;padding-top:8px;color:var(--leaf-deep)}
.post-card.feature{grid-column:1/-1;display:grid;grid-template-columns:1.15fr 1fr}
.post-card.feature .media{aspect-ratio:auto;min-height:360px;height:100%}
.post-card.feature .body{padding:clamp(28px,4vw,52px);justify-content:center;gap:16px}
.post-card.feature h2{font-size:clamp(28px,3.4vw,46px);font-variation-settings:"opsz" 72}
.post-card.feature p{font-size:17px}
.post-card.feature .read{margin-top:12px}
@media (max-width:820px){
  .post-grid{grid-template-columns:1fr}
  .post-card.feature{display:flex}
  .post-card.feature .media{aspect-ratio:16/9;min-height:0;height:auto}
}
/* There is deliberately no half-height "row" card variant under the feature
   card: a 320px cover beside 800px of text read as a truncated copy of the
   feature, and every other piece already sits in the .latest list that
   build-site.py renders one section gap below (clamp(56px,7vw,96px)) */
.empty{color:var(--muted);font-size:17px;padding:48px 0;text-align:center;border:1px dashed var(--line-strong);border-radius:var(--r-lg)}

/* ---------- vertical stepper (technique steps) ---------- */
/* `.prose .stepper` outranks site.css's `.prose ol{padding-left:1.3em}`,
   which was indenting the whole list 23px inside the measure */
.stepper,.prose .stepper{list-style:none;padding:0;margin:0;counter-reset:step}
.stepper li{counter-increment:step;position:relative;display:grid;grid-template-columns:48px minmax(0,1fr);column-gap:22px;padding-bottom:clamp(26px,3vw,36px)}
.stepper li:last-child{padding-bottom:0}
.stepper li::before{content:counter(step,decimal-leading-zero);grid-column:1;justify-self:center;
  font-family:var(--font-mono);font-size:13.5px;font-weight:500;letter-spacing:.02em;color:var(--leaf-deep);
  width:48px;height:48px;border-radius:50%;border:1.5px solid var(--leaf);background:var(--white);
  display:flex;align-items:center;justify-content:center;position:relative;z-index:1;
  box-shadow:0 0 0 6px rgba(47,180,84,.10),0 6px 18px -8px var(--leaf-glow)}
.stepper li::after{content:"";position:absolute;left:23.5px;top:52px;bottom:4px;width:1px;
  background:linear-gradient(var(--leaf),rgba(47,180,84,.15))}
.stepper li:last-child::after{display:none}
.stepper li>div{padding-top:9px}
.stepper b{display:block;font-family:var(--font-display);font-weight:500;font-size:clamp(20px,2vw,24px);letter-spacing:-.02em;line-height:1.15;color:var(--ink);margin-bottom:6px}
.stepper li>div{color:var(--ink-2);font-size:17px;line-height:1.6}

/* two-line ratio figure used on the pillar pages */
.ratio{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-block:8px 0}
.ratio>div{padding:18px 16px;border-radius:var(--r-md);background:rgba(255,255,255,.62);border:1px solid rgba(255,255,255,.85);box-shadow:var(--shadow-soft);text-align:center}
.ratio .n{font-family:var(--font-mono);font-size:48px;font-weight:500;letter-spacing:-.04em;line-height:1;color:var(--ink)}
.ratio .l{font-size:14px;color:var(--ink);margin-top:10px}
.ratio>div:nth-child(2) .n{color:var(--leaf-deep)}

/* ---------- FAQ: native details, animated ---------- */
.faq{border-top:1px solid var(--line-strong)}
.faq details{border-bottom:1px solid var(--line-strong)}
.faq summary{list-style:none;cursor:pointer;display:flex;justify-content:space-between;align-items:center;gap:24px;padding:22px 0;
  font-family:var(--font-display);font-weight:500;font-size:clamp(18px,1.7vw,21px);letter-spacing:-.015em;line-height:1.25;color:var(--ink);
  transition:color .2s ease}
.faq summary::-webkit-details-marker{display:none}
.faq summary:hover{color:var(--leaf-deep)}
.faq summary::after{content:"";flex:none;width:30px;height:30px;border-radius:50%;border:1px solid var(--line-strong);color:var(--ink);
  background:linear-gradient(currentColor,currentColor) center/12px 1.5px no-repeat,linear-gradient(currentColor,currentColor) center/1.5px 12px no-repeat;
  transition:transform .5s var(--ease-out),background-color .25s ease,border-color .25s ease,color .25s ease}
.faq details[open]>summary::after{transform:rotate(135deg);background-color:var(--leaf);border-color:var(--leaf);color:#04150A}
.faq-body{display:grid;grid-template-rows:1fr;opacity:1;transition:grid-template-rows .5s var(--ease-out),opacity .4s ease}
.faq-body>div{overflow:hidden;min-height:0}
.faq-body p{padding-bottom:26px;color:var(--ink-2);max-width:64ch;font-size:16.5px;line-height:1.65}
.faq-body p+p{margin-top:-10px}
.faq-body a{color:var(--leaf-deep);text-decoration:underline;text-underline-offset:3px;text-decoration-color:rgba(29,122,56,.4)}
@starting-style{.faq details[open] .faq-body{grid-template-rows:0fr;opacity:0}}
.faq details.is-closing .faq-body{grid-template-rows:0fr;opacity:0}
@media (prefers-reduced-motion:reduce){.faq-body,.faq summary::after{transition:none}}

/* ---------- roundup: ranked rows ---------- */
/* every card has one skeleton at every width: rank, name, a row of fact
   chips, the verdict, then the store link as the last line. Phones stack
   the five; from 600px the rank takes its own column; from 1100px the chips
   move into a right-hand column so the 1120px card carries its width */
.rank-list{display:grid;gap:14px}
.rank-row{display:grid;grid-template-columns:1fr;row-gap:12px;padding:clamp(22px,3vw,30px) clamp(22px,3vw,32px);
  grid-template-areas:"rank" "name" "facts" "text" "link"}
.rank-row .rank{grid-area:rank;font-family:var(--font-mono);font-size:26px;font-weight:500;letter-spacing:-.05em;line-height:1;color:var(--leaf-deep);padding-top:2px;font-variant-numeric:tabular-nums}
.rank-row.ours{border-color:rgba(47,180,84,.55);background:rgba(255,255,255,.8);
  box-shadow:0 1px 2px rgba(11,20,16,.05),0 30px 70px -30px rgba(47,180,84,.45)}
/* the name row opens on the app's icon (44px, the store's rounded square
   at the site's small radius), then the name and its badge */
.rank-row .name{grid-area:name;display:flex;align-items:center;gap:14px;min-width:0}
.rank-row .app-icon{width:44px;height:44px;border-radius:10px;flex:none;object-fit:cover;background:var(--dew-2);
  box-shadow:0 0 0 1px rgba(11,20,16,.08)}
.rank-row h3{font-size:clamp(20px,1.7vw,22px);margin:0;line-height:1.15;min-width:0}
/* one chip, outlined, for every label in a ranking card: the same 26px pill,
   1px hairline, 12px body face and tracking whether it carries a fact ("5.0
   App Store") or the badge ("Our app"). The badge is the same chip in the
   leaf colour, matching the homepage's PRO chips; a second chip style in the
   card (muted grey text on a grey hairline) read as a filled, disabled chip
   beside it. Every chip on the site is outlined; a filled chip reads as a
   button */
.chip{display:inline-flex;align-items:center;gap:.3em;height:26px;padding:0 10px;border-radius:999px;
  border:1px solid rgba(11,20,16,.22);background:transparent;color:var(--ink-2);
  font-family:var(--font-body);font-size:12px;font-weight:500;letter-spacing:.01em;line-height:1;white-space:nowrap;vertical-align:middle}
.chip b{font-weight:600;color:var(--ink)}
.chip-ours{color:var(--leaf-deep);border-color:rgba(29,122,56,.35);font-size:11px;font-weight:700;letter-spacing:.08em;text-transform:uppercase}
.rank-row h3 .chip{margin-left:10px;position:relative;top:-2px}
.facts{grid-area:facts;display:flex;flex-wrap:wrap;gap:6px}
/* .prose p:first-of-type would otherwise size the verdict at 19px */
.rank-row p,.prose .rank-row p{grid-area:text;color:var(--ink-2);font-size:17px;line-height:1.6;margin:0;max-width:70ch}
.rank-row p b{color:var(--ink);font-weight:600}
/* the store link: the site's arrow link, one size down, on the card's last line */
.rank-row .store{grid-area:link;justify-self:start;font-size:14px;color:var(--leaf-deep);min-height:44px;margin:-8px 0 -10px}
.prose .rank-row .store{text-decoration:none}
.rank-row .store svg{width:14px;height:14px}
@media (min-width:600px){
  .rank-row{grid-template-columns:56px minmax(0,1fr);column-gap:20px;
    grid-template-areas:"rank name" "rank facts" "rank text" "rank link"}
  .rank-row .rank{font-size:clamp(28px,3vw,40px)}
}
@media (min-width:1100px){
  .rank-row{grid-template-columns:56px minmax(0,1fr) 300px;column-gap:20px;
    grid-template-areas:"rank name facts" "rank text facts" "rank link facts";grid-template-rows:auto 1fr auto}
  .facts{flex-direction:column;align-items:flex-start;gap:8px;padding:4px 0 0 20px}
  .rank-row .store{margin-top:-4px}
}

.cmp-wrap{position:relative;border-radius:var(--r-lg);background:rgba(255,255,255,.66);border:1px solid rgba(255,255,255,.8);box-shadow:var(--shadow-soft);isolation:isolate}
.cmp-wrap::after{content:"";position:absolute;top:0;right:0;bottom:0;width:72px;z-index:2;pointer-events:none;
  border-radius:0 var(--r-lg) var(--r-lg) 0;opacity:0;transition:opacity .25s ease;
  background:linear-gradient(90deg,rgba(243,246,242,0),rgba(243,246,242,.92) 60%,var(--dew))}
.cmp-wrap.is-scrollable::after{opacity:1}
.cmp-wrap.is-scrollable.at-end::after{opacity:0}
.cmp-hint{display:none;align-items:center;gap:8px;padding:12px 16px 0;font-size:13px;color:var(--muted)}
.cmp-hint::after{content:"";flex:none;width:7px;height:7px;margin-left:2px;transform:rotate(-45deg);
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor}
.prose .cmp-hint{margin:0}
@media (min-width:720px) and (max-width:900px){
  .cmp-wrap.is-scrollable .cmp-hint,.no-js .cmp-wrap .cmp-hint{display:flex}
}
.cmp{overflow-x:auto;border-radius:var(--r-lg);-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}
.cmp table{border-collapse:separate;border-spacing:0;width:100%;min-width:820px;font-size:15px}
.cmp th,.cmp td{padding:13px 16px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}
.cmp th{font-size:13px;color:var(--muted);font-weight:500;background:rgba(11,20,16,.03)}
/* one type system with the ranking cards and the phone cards: the rank is
   the rank badge (.cmp-card-head .rank: mono, 13px, 500, leaf), every
   header is the 13px body face, and the numeric values read in the body
   face with tabular figures (the phone cards' dd.num), so mono is reserved
   for the rank numeral */
.cmp td:first-child{font-family:var(--font-mono);font-size:13px;font-weight:500;color:var(--leaf-deep);font-variant-numeric:tabular-nums;letter-spacing:0}
.cmp td:nth-child(3),.cmp td:nth-child(4),.cmp td:nth-child(5){font-variant-numeric:tabular-nums;color:var(--ink)}
.cmp td:nth-child(2){min-width:200px}
@media (min-width:1100px){.cmp td:nth-child(2){white-space:nowrap}}
.cmp tr:last-child td{border-bottom:0}
/* our row: one step up from the .08 the phone cards used to share (it was
   invisible on the off-white frame); the sticky ground below is the same
   .15 leaf resolved over the frame's #FBFCFB */
.cmp tr.oursrow td{background:rgba(47,180,84,.15)}
/* the rank and the app name stay put while the rest scrolls. At rest the
   sticky cells are transparent, so a row reads as one row: the same tint
   edge to edge, and only a 1px hairline after the name column. They take an
   opaque ground only once the table has actually scrolled (pages.js sets
   .is-scrolled), because then other cells slide under them. That ground is
   the frame's own colour (the .66 white over --dew resolves to #FBFCFB),
   not --dew, so nothing changes hue when the scroll starts */
.cmp th:nth-child(1),.cmp td:nth-child(1){position:sticky;left:0;z-index:1;width:48px;min-width:48px}
.cmp th:nth-child(2),.cmp td:nth-child(2){position:sticky;left:48px;z-index:1;
  box-shadow:1px 0 0 var(--line);transition:box-shadow .25s ease}
.cmp th:nth-child(1),.cmp th:nth-child(2){z-index:3}
.cmp-wrap.is-scrolled .cmp td:nth-child(1),.cmp-wrap.is-scrolled .cmp td:nth-child(2){background:#FBFCFB}
.cmp-wrap.is-scrolled .cmp th:nth-child(1),.cmp-wrap.is-scrolled .cmp th:nth-child(2){background:#F4F5F4}
.cmp-wrap.is-scrolled .cmp tr.oursrow td:nth-child(1),.cmp-wrap.is-scrolled .cmp tr.oursrow td:nth-child(2){background:#DCF1E2}
.cmp-wrap.is-scrolled .cmp th:nth-child(2),.cmp-wrap.is-scrolled .cmp td:nth-child(2){box-shadow:1px 0 0 var(--line),10px 0 16px -10px rgba(11,20,16,.28)}
/* phones: the same data as one card per app, a two-column definition list
   under a rank + name header; the table is not rendered below 720px */
.cmp-cards{display:grid;gap:10px}
@media (min-width:720px){.cmp-cards{display:none}}
@media (max-width:719px){.cmp-wrap{display:none}}
.cmp-card{padding:18px 20px 20px;border-radius:var(--r-md);background:rgba(255,255,255,.62);border:1px solid rgba(255,255,255,.85);box-shadow:var(--shadow-soft)}
.cmp-card.ours{background:rgba(47,180,84,.15);border-color:rgba(47,180,84,.45)}
.cmp-card-head{display:flex;align-items:baseline;gap:12px;padding-bottom:12px;border-bottom:1px solid var(--line)}
.cmp-card-head .rank{font-family:var(--font-mono);font-size:13px;font-weight:500;color:var(--leaf-deep);font-variant-numeric:tabular-nums;flex:none}
.cmp-card-head b{font-family:var(--font-display);font-weight:500;font-size:18px;letter-spacing:-.015em;line-height:1.2;color:var(--ink)}
.cmp-card dl{display:grid;grid-template-columns:104px minmax(0,1fr);column-gap:14px;row-gap:9px;margin:12px 0 0;padding:0}
.cmp-card dt{font-size:13px;color:var(--muted);font-weight:500;padding-top:1px}
.cmp-card dd{margin:0;font-size:15px;line-height:1.45;color:var(--ink-2)}
/* every value in the body face; the numbers only take tabular figures */
.cmp-card dd.num{font-variant-numeric:tabular-nums;color:var(--ink)}
/* the criteria: ruled text columns, no boxes, so uneven copy lengths rag
   naturally instead of leaving a card half empty. One column on phones,
   two on tablets, five from 1100px. The columns end at different heights,
   so the list closes on one shared bottom rule. Hairlines only, no
   numbers: the criteria are not ranked (the ranking below carries the
   numbers), and a numbered, ruled, indented list read as a nested list.
   `.prose .method` outranks site.css's `.prose ul{padding-left:1.3em}`,
   which was indenting the whole list 23px inside the measure */
.method,.prose .method{list-style:none;padding:0 0 24px;margin:0;display:grid;grid-template-columns:1fr;gap:20px;border-bottom:1px solid var(--line-strong)}
.method li{padding-top:16px;border-top:1px solid var(--line-strong);font-size:15px;color:var(--ink-2);line-height:1.55}
.prose .method li+li{margin-top:0}
.method li b{display:block;color:var(--ink);font-family:var(--font-display);font-weight:500;font-size:18px;letter-spacing:-.015em;margin-bottom:6px}
@media (min-width:600px){.method,.prose .method{grid-template-columns:repeat(2,minmax(0,1fr));gap:24px}.method li:last-child{grid-column:1/-1;max-width:60ch}
  /* abreast, each item is a subgrid of its two rows (heading, body), so
     the bodies of a row start on one baseline whether or not a heading
     wraps, with no fixed reservation under the ones that do not */
  .method li{display:grid;grid-template-rows:subgrid;grid-row:span 2;row-gap:0;align-content:start}
  .method li b{line-height:1.25}}
@media (min-width:1100px){.method,.prose .method{grid-template-columns:repeat(5,minmax(0,1fr));gap:24px}.method li:last-child{grid-column:auto;max-width:none}}

/* ---------- section heading inside long pages ---------- */
.sec{padding-block:clamp(40px,6vw,72px)}
.sec>.wrap-mid>h2,.sec-head h2{font-size:clamp(30px,3.4vw,44px);margin:12px 0 clamp(20px,3vw,32px)}
.sec-head{display:flex;flex-direction:column;align-items:flex-start;margin-bottom:clamp(22px,3vw,36px)}
.sec-head h2{margin-bottom:0}
.sec-head p{color:var(--muted);margin-top:12px;max-width:56ch}

/* generic reveal stagger helper */
.stagger>*{--d:0ms}
.stagger>*:nth-child(2){--d:80ms}.stagger>*:nth-child(3){--d:160ms}.stagger>*:nth-child(4){--d:240ms}
.stagger>*:nth-child(5){--d:320ms}.stagger>*:nth-child(6){--d:400ms}

/* ---------- chrome overrides for the secondary pages ---------- */
/* mobile menu foot: the label reads in the body face, the CTA is the white
   pill (.menu carries .dark so site.css paints .btn-primary white) */
.menu-foot .mono{font-family:var(--font-body);font-size:13px;letter-spacing:.06em;text-transform:uppercase}
/* light pages: the scrolled nav needs a denser ground so a photo behind the
   blur does not smear through it */
.nav.is-scrolled:not(.on-dark){background:rgb(243,246,242)}

/* the support page's primary button: lighter fill and a 1px lift, quick */
.contact .btn-primary{transition:transform .15s var(--ease-out),box-shadow .15s var(--ease-out),background .15s ease}
.contact .btn-primary:hover{background:var(--ink-2);transform:translateY(-1px);box-shadow:0 1px 0 rgba(255,255,255,.12) inset,0 14px 32px -12px rgba(11,20,16,.5)}
.contact .btn-primary:active{transform:scale(.97)}

/* body copy on phones: 18px everywhere, including the opening paragraph */
@media (max-width:640px){
  .prose{font-size:18px;line-height:1.7}
  .prose p:first-of-type{font-size:18px}
}


/* "ff" in a display title: build-site.py wraps the FIRST f of the pair
   only, so the tracking parts the two crossbars without opening a gap
   before the letter after them. .035em is the smallest value that
   separates them at 72px; .05em already reads as "Dif ferent". */
.ff{letter-spacing:.035em}

/* phones: the list row keeps date + title; the category chip would force a second line */
@media (max-width:420px){.latest-row .when>span{display:none}}
