/*
 * The marketing site, styled from the app's own palette.
 *
 * Every colour here is a token from /palette-2026-09-11.css, which the page links first. The only
 * literals are white, black (the store badge, which is black by the stores' convention) and
 * translucent white on navy. Coral is not a colour this site uses: it lives in
 * /brand/torvi-mark.svg and appears only as that image.
 *
 * Mobile first. The base rules are the phone layout; each min-width query adds room.
 */

@font-face {
  font-family: 'Sora';
  src: url('/site/fonts/Sora-Bold.ttf') format('truetype');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

*, *::before, *::after { box-sizing: border-box; }
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  font-family: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  font-size: 16px;
  line-height: 1.6;
  color: var(--ink);
  background: var(--paper);
}
h1, h2, h3 {
  font-family: 'Sora', 'Inter', system-ui, sans-serif;
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--navy);
  margin: 0;
}
p, ul, figure { margin: 0; }
ul { padding: 0; list-style: none; }
a { color: var(--teal); }
a:hover { color: var(--teal-dark); }
img { display: block; max-width: 100%; height: auto; }
:focus-visible { outline: 3px solid var(--teal); outline-offset: 2px; }

/* 1400px, not 1120: on a wide screen the narrower box left every section below the full-bleed hero
   in a column with 400px of empty paper either side (measured at 1920). Nothing changes below
   1400px wide, so tablets and phones lay out exactly as before. */
.wrap { max-width: 1400px; margin: 0 auto; padding-inline: 20px; }
.visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: 12px; top: -60px; z-index: 100;
  background: var(--card); color: var(--navy); padding: 10px 14px; border-radius: 8px;
}
.skip-link:focus { top: 12px; }

/* ── buttons ─────────────────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-height: 48px; padding: 12px 22px; border-radius: 10px;
  font: 600 15px/1.2 'Inter', system-ui, sans-serif;
  text-decoration: none; border: 1.5px solid transparent; cursor: pointer;
}
.btn-primary { background: var(--teal); color: #fff; }
.btn-primary:hover { background: var(--teal-dark); color: #fff; }
.btn-secondary { background: var(--card); color: var(--navy); border-color: var(--control-edge); }
.btn-secondary:hover { background: var(--surface-2); color: var(--navy); }
/* On navy, the focus ring is lifted so it reads against the fill. */
body > header :focus-visible, body > footer :focus-visible, .hero :focus-visible { outline-color: var(--teal-bright); }
.btn-ghost { background: transparent; color: #fff; border-color: rgba(255, 255, 255, .6); }
.btn-ghost:hover { border-color: #fff; color: #fff; }
.btn-block { width: 100%; }

/* ── header ──────────────────────────────────────────────────────────────────────────── */
/* Bare <header> and <footer>, no class: tools/brandcheck.py finds the mark by those tags. */
body > header { background: var(--navy); color: #fff; position: sticky; top: 0; z-index: 50; }
.nav { display: flex; align-items: center; justify-content: space-between; gap: 16px; min-height: 64px; }
.brand-link { text-decoration: none; color: #fff; }
.brand, .footer-brand {
  display: flex; align-items: center; gap: 10px;
  font-family: 'Sora', 'Inter', system-ui, sans-serif; font-weight: 700; font-size: 21px; color: #fff;
}
.torvi-mark { flex-shrink: 0; }

.nav-links { display: none; align-items: center; gap: 26px; }
.nav-links a { color: var(--navy-text); text-decoration: none; font-weight: 500; font-size: 15px; }
.nav-links a:hover { color: #fff; }
.nav-links .btn { min-height: 40px; padding: 8px 16px; font-size: 14px; }
.nav-links .btn-primary { color: #fff; }

/* The phone menu is a <details>: it opens and closes with no script at all. */
.nav-menu { position: relative; }
.nav-menu summary {
  list-style: none; cursor: pointer; color: #fff; font-weight: 600; font-size: 15px;
  min-height: 44px; padding: 10px 14px; border: 1.5px solid rgba(255, 255, 255, .5); border-radius: 10px;
}
.nav-menu summary::-webkit-details-marker { display: none; }
.nav-menu[open] summary { background: var(--navy-2); }
/*
 * ── THE PANEL HAS TO FIT ON THE SCREEN (5 Oct) ────────────────────────────────────────────────
 *
 * It had no height limit and no overflow, and the menu has twenty-one items in it. On an iPhone that
 * means the panel runs off the bottom of the screen -- and because it is ABSOLUTELY POSITIONED, a
 * swipe inside it scrolls the PAGE behind instead of the panel, so the lower half of the menu could
 * not be reached at all. Reported from iPhone Safari on torvi.com.au.
 *
 * dvh, WITH A FALLBACK, in that order. Safari's 100vh is the window with the toolbars hidden, which
 * is taller than what you can see, so a vh-sized panel is itself cut off by the chrome it does not
 * know about. dvh is the viewport as it actually is right now. The vh line stays first for anything
 * that has never heard of dvh -- it is wrong by the height of a toolbar, which beats unbounded.
 *
 * The 96px is the header above it plus the gap: --nav-h would be better and this file does not have
 * one, so it is written once, here, rather than guessed at in three places.
 */
.nav-menu-panel {
  position: absolute; right: 0; top: calc(100% + 8px); width: min(260px, calc(100vw - 40px));
  background: var(--navy-2); border-radius: 12px; padding: 8px; box-shadow: 0 16px 40px rgba(0, 0, 0, .35);
  max-height: calc(100vh - 96px);
  max-height: calc(100dvh - 96px);
  overflow-y: auto;
  /*
   * CONTAIN, so a swipe that reaches the end of the list stops there instead of handing the gesture to
   * the page behind -- which on iOS is how a menu ends up scrolling the article underneath it and
   * leaving the reader somewhere they did not ask to be.
   */
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.nav-menu-panel a {
  display: block; padding: 12px 14px; border-radius: 8px;
  color: #fff; text-decoration: none; font-weight: 500;
}
.nav-menu-panel a:hover { background: rgba(255, 255, 255, .08); color: #fff; }
.nav-menu-panel .btn { display: flex; margin-top: 6px; }
/* The last item needs somewhere to sit: flush against a rounded corner reads as cut off, which is
   exactly the thing being fixed. */
.nav-menu-panel > :last-child { margin-bottom: 4px; }

/*
 * THE PAGE BEHIND DOES NOT MOVE WHILE THE MENU IS OPEN. position: fixed on the body is the only thing
 * iOS Safari reliably honours -- overflow: hidden is not enough there, which is why this is a class
 * the script adds and removes rather than a rule that could live on its own. The top offset is
 * written by the script from the scroll position it froze, so the page does not jump to the top when
 * the menu opens and does not have to be scrolled back afterwards.
 */
body.nav-locked {
  position: fixed;
  left: 0;
  right: 0;
  width: 100%;
  overflow: hidden;
}

@media (min-width: 900px) {
  .nav-links { display: flex; }
  .nav-menu { display: none; }
}

/* ── hero ────────────────────────────────────────────────────────────────────────────── */
.hero { background: var(--navy); color: #fff; padding-block: 36px 56px; overflow: hidden; }
.hero-grid { display: grid; gap: 40px; align-items: center; }
/* Grid items default to min-width:auto as well, which would reopen the track the rule below closes. */
.hero-grid > * { min-width: 0; }
/* The eyebrow is context, not a second headline: small, muted, and close enough to read as one unit. */
.hero-eyebrow { font-size: 15px; font-weight: 600; color: var(--navy-text); letter-spacing: .01em; }
.hero h1 { color: #fff; margin-top: 12px; font-size: clamp(2rem, 1.3rem + 3.2vw, 3.3rem); max-width: 14ch; }
.hero-sub { margin-top: 16px; color: var(--navy-text); font-size: 17px; max-width: 34em; }
.hero-ctas { margin-top: 28px; display: flex; flex-wrap: wrap; gap: 12px; }
.hero-note { margin-top: 14px; font-size: 14px; color: var(--navy-text); }

@media (min-width: 960px) {
  .hero { padding-block: 72px 88px; }
  /*
   * minmax(0, ...), not a bare fr. A bare `6fr` is `minmax(auto, 6fr)`, and that auto minimum can let
   * the screenshot's 2000px natural width hold its column open: the frame then runs past the grid
   * and the hero's overflow:hidden cuts off its right side. With a zero minimum the column is what
   * the grid gives it, and the picture scales down to fit.
   */
  .hero-grid { grid-template-columns: minmax(0, 5fr) minmax(0, 6fr); gap: 56px; }
}

/* ── browser frame ───────────────────────────────────────────────────────────────────── */
.browser { background: var(--card); border-radius: 12px; overflow: hidden; box-shadow: 0 24px 60px rgba(0, 0, 0, .28); }
.hero .browser { box-shadow: 0 30px 80px rgba(0, 0, 0, .45); max-width: 100%; }
.browser-bar { display: flex; align-items: center; gap: 6px; padding: 9px 12px; background: var(--surface-2); border-bottom: 1px solid var(--line); }
.browser-bar i { width: 10px; height: 10px; border-radius: 50%; background: var(--line); }
.browser-url {
  margin-left: 10px; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: 12px; color: var(--muted); background: var(--card); border-radius: 6px; padding: 3px 10px;
}
/* Each screenshot keeps its own proportions: its width and height attributes reserve the space,
   and nothing is cropped. */
.shot img { width: 100%; max-width: 100%; height: auto; }

/* A picture that cannot be loaded takes its frame with it: the copy beside it stands alone, and a
   visitor never sees a dashed box naming a file they have no idea about. Which pictures a page may
   ask for is settled before it ships, by tools/sitecheck.py. */
.slot-label { display: none; }
[data-slot].is-missing { display: none; }

/*
 * A SECTION SOMEBODY ARRIVES AT BY ITS OWN ADDRESS sits below the bar that is pinned to the top of
 * the window, not under it. The header is sticky and about 64px tall; without this, following
 * /products/mobile-app#what-it-does puts the heading behind it and the first thing on the screen
 * is the second feature.
 */
section[id] { scroll-margin-top: 84px; }

/* ── sections ────────────────────────────────────────────────────────────────────────── */
.section { padding-block: 56px; }
.section-alt { background: var(--surface-2); }
.section-head { max-width: 660px; margin-bottom: 32px; }
.eyebrow { font-size: 13px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase; color: var(--teal); margin-bottom: 10px; }
.section-head h2 { font-size: clamp(1.6rem, 1.2rem + 1.8vw, 2.3rem); }
.section-head p { margin-top: 12px; color: var(--muted); font-size: 16.5px; }

@media (min-width: 900px) {
  .section { padding-block: 88px; }
  .section-head { margin-bottom: 44px; }
}

/* ── products ────────────────────────────────────────────────────────────────────────── */
.product-grid { display: grid; gap: 18px; }
.product-card { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); overflow: hidden; display: flex; flex-direction: column; }
.product-card img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.product-body { padding: 18px 20px 22px; }
.product-body h3 { font-size: 18px; }
.product-body p { margin-top: 8px; font-size: 15px; color: var(--muted); }
/* Two across at most: each picture carries its own headline and small print, which four across
   shrinks to around 250px wide, where the small print can no longer be read. */
@media (min-width: 600px) { .product-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ── screenshots ─────────────────────────────────────────────────────────────────────── */
.screen-grid { display: grid; gap: 28px; }
.screen figcaption { margin-top: 12px; font-size: 15px; color: var(--muted); }
.screen figcaption strong { display: block; font-family: 'Sora', 'Inter', system-ui, sans-serif; color: var(--navy); font-size: 16px; }
@media (min-width: 760px) { .screen-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 36px 28px; } }
/* Three pictures, one row: an industry page carries three, and two columns left the third alone
   under half an empty row with the call to action crowding its shadow. */
@media (min-width: 1000px) {
  .screen-grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); align-items: start; }
}
.cta-after-shots { margin-top: 40px; }

/* ── agency portal ───────────────────────────────────────────────────────────────────── */
.agency-grid { display: grid; gap: 28px; align-items: center; }
.free-badge {
  display: inline-block; font-size: 13px; font-weight: 600; color: var(--teal-text);
  background: var(--teal-tint); border-radius: 999px; padding: 4px 12px; margin-bottom: 14px;
}
.agency-copy h2 { font-size: clamp(1.6rem, 1.2rem + 1.8vw, 2.3rem); }
.agency-copy > p { margin-top: 12px; color: var(--muted); font-size: 16.5px; }
.tick-list { margin-top: 20px; }
.tick-list li { position: relative; padding: 8px 0 8px 30px; border-top: 1px solid var(--line); }
.tick-list li:first-child { border-top: 0; }
.tick-list li::before {
  content: ''; position: absolute; left: 4px; top: 15px; width: 12px; height: 7px;
  border: solid var(--teal); border-width: 0 0 2.5px 2.5px; transform: rotate(-45deg);
}
.agency-copy .btn { margin-top: 24px; }
@media (min-width: 900px) { .agency-grid { grid-template-columns: 1fr 1fr; gap: 56px; } }
/* One column, not one column and an empty half: a section whose picture has not been made yet. */
@media (min-width: 900px) {
  .agency-grid.is-single { grid-template-columns: minmax(0, 640px); justify-content: center; }
}

/* ── pricing ─────────────────────────────────────────────────────────────────────────── */
.plan-grid { display: grid; gap: 18px; align-items: stretch; }
.plan-loading, .plan-error { grid-column: 1 / -1; color: var(--muted); padding: 20px 0; }
.plan {
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  padding: 24px 22px; display: flex; flex-direction: column;
}
.plan.featured { border: 2px solid var(--teal); }
.plan-name { font-size: 20px; }
/* Always present, empty when a plan has no tagline, so the prices line up across the row. */
.plan-tagline { font-size: 14px; color: var(--muted); margin-top: 4px; min-height: 2.8em; line-height: 1.4; }
.plan-price { font-family: 'Sora', 'Inter', system-ui, sans-serif; font-weight: 700; font-size: 32px; color: var(--navy); margin-top: 10px; line-height: 1.1; }
/* The unit wraps as a whole or not at all: "per user, per / month" split across two lines. */
.plan-per { font-family: 'Inter', system-ui, sans-serif; font-weight: 400; font-size: 14px;
  color: var(--muted); display: inline-block; white-space: nowrap; }
/* "+ $3.99 per staff member" under a base price: its own line, because the two amounts are two
   facts and running them together reads as one. */
.plan-plus { display: block; font-family: 'Inter', system-ui, sans-serif; font-weight: 500;
  font-size: 15px; color: var(--navy); margin-top: 2px; }
.plan-min {
  margin-top: 12px; font-size: 14px; font-weight: 600; color: var(--sand-text);
  background: var(--sand); border-radius: 8px; padding: 8px 10px;
}
.plan-trial { margin-top: 10px; font-size: 14px; color: var(--muted); }
.plan ul { margin-top: 16px; flex: 1; }
.plan li { font-size: 14.5px; padding: 8px 0; border-top: 1px solid var(--line); }
.plan li:first-child { border-top: 0; }
.plan .btn { margin-top: 20px; width: 100%; }
@media (min-width: 700px) { .plan-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
/*
 * FIVE PLANS, FIVE COLUMNS. Four columns left Enterprise alone on a second row beside three
 * empty card-widths, which reads as a plan that did not load rather than the last of the five.
 */
@media (min-width: 1080px) { .plan-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (min-width: 1240px) { .plan-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 14px; } }

.worked {
  margin-top: 28px; background: var(--card); border: 1px solid var(--line); border-left: 4px solid var(--teal);
  border-radius: var(--radius); padding: 20px 22px;
}
.worked h3 { font-size: 18px; }
.worked ol { margin: 12px 0 0; padding-left: 20px; }
.worked li { padding: 6px 0; }
.worked .calc { display: block; font-variant-numeric: tabular-nums; font-weight: 600; color: var(--navy); }
.worked-note { margin-top: 10px; font-size: 14px; color: var(--muted); }
.pricing-foot { margin-top: 18px; font-size: 14px; color: var(--muted); }

/* The part of a plan card that changes when Monthly or Yearly is pressed. A flex column of its own,
   so the feature list still stretches and the button still sits on the bottom edge whichever period
   is chosen and however many lines each one has. */
.plan-body { display: flex; flex-direction: column; flex: 1; }
.plan-toggle {
  display: flex; gap: 4px; margin-top: 14px; padding: 4px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: 999px;
}
.plan-toggle-item {
  flex: 1; border: 0; background: transparent; border-radius: 999px; padding: 7px 10px;
  font: inherit; font-size: 14px; font-weight: 600; color: var(--muted); cursor: pointer;
}
.plan-toggle-item.on { background: var(--card); color: var(--navy); border: 1px solid var(--line); }
/* A product that is priced and not yet buyable: the card keeps its figures and says so where
   the button would be. Not a button — there is nothing to press. */
.plan-soon { display: inline-block; margin-top: auto; padding: 11px 18px; border-radius: 10px;
  background: var(--sand); color: var(--sand-text); font-family: 'Sora', sans-serif;
  font-weight: 600; font-size: 14px; text-align: center; width: 100%; }
.plan-saving { margin-top: 10px; font-size: 13.5px; font-weight: 600; color: var(--teal-dark); }

/* The products that are live and have no price in the system. Their own row under the plans rather
   than mixed in with them: a card with no number beside four with numbers reads as a plan somebody
   forgot to price, which is the opposite of what it is. */
.plan-extra { margin-top: 36px; }
.plan-extra h3 { font-size: 18px; }
.plan-extra > p { margin-top: 8px; margin-bottom: 18px; color: var(--muted); font-size: 15px; }
@media (min-width: 700px) { .plan-extra .plan-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1080px) { .plan-extra .plan-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* A phone-shaped frame for a screenshot taken on a phone. The browser frame beside it has a title
   bar because a browser does; this has rounded sides, because a phone does. */
.phone {
  position: relative;
  background: var(--navy); border-radius: 30px; padding: 9px; overflow: hidden;
  max-width: 300px; margin-inline: auto; box-shadow: 0 24px 60px rgba(0, 0, 0, .28);
}
.phone img { border-radius: 22px; display: block; }

/*
 * ── WHICH PHONE ──────────────────────────────────────────────────────────────────────────────
 *
 * The app is on both stores and every picture of it was one dark rounded rectangle, which reads
 * as an Android phone to anybody who has held one. These two frames say which is which: an iPhone
 * with the Dynamic Island and the home indicator, an Android with a hole-punch camera and squarer
 * corners.
 *
 * DRAWN IN THE BEZEL, NEVER OVER THE SCREENSHOT. The screenshots are 390x844 renders of the real
 * pages with no status bar of their own, so an island floated over the picture would sit on top of
 * "Good afternoon, Marisol". The frame grows its top edge instead and the island is drawn in that
 * margin -- the picture is untouched, which is the point of taking it from the product.
 */
.phone-iphone { border-radius: 34px; padding: 26px 9px 18px; }
.phone-iphone::before {
  content: ''; position: absolute; top: 8px; left: 50%; transform: translateX(-50%);
  width: 86px; height: 24px; border-radius: 999px; background: #0b0f1a;
}
/* The home indicator, in the bottom margin for the same reason. */
.phone-iphone::after {
  content: ''; position: absolute; bottom: 7px; left: 50%; transform: translateX(-50%);
  width: 96px; height: 4px; border-radius: 999px; background: rgba(255, 255, 255, .55);
}
.phone-iphone img { border-radius: 26px; }

.phone-android { border-radius: 22px; padding: 20px 8px 10px; }
.phone-android::before {
  content: ''; position: absolute; top: 7px; left: 50%; transform: translateX(-50%);
  width: 9px; height: 9px; border-radius: 50%; background: #0b0f1a;
}
.phone-android img { border-radius: 16px; }

/*
 * A row of phones rather than one. Small enough that four or five of them are a gallery somebody
 * scans rather than four pictures somebody scrolls past: two across a tablet, four across a
 * laptop, and one above the other on a phone, where a phone-shaped picture is already the width
 * of the screen.
 */
.screen-gallery { display: grid; gap: 26px 20px; }
.screen-gallery .phone { max-width: 230px; }
.screen-gallery figcaption { margin-top: 12px; font-size: 14.5px; color: var(--muted); text-align: center; }
.screen-gallery figcaption strong {
  display: block; font-family: 'Sora', 'Inter', system-ui, sans-serif; color: var(--navy); font-size: 15.5px;
}
@media (min-width: 560px) { .screen-gallery { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1000px) { .screen-gallery { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* Two pictures where a section has one slot: a desktop screen and a phone beside it, which is how
   the two halves of Contractors actually differ. One column until there is room for both. */
.shot-pair { display: grid; gap: 22px; }
@media (min-width: 520px) {
  .shot-pair { grid-template-columns: minmax(0, 1.7fr) minmax(0, 1fr); align-items: start; }
}


/* ── the products menu ───────────────────────────────────────────────────────────────────
 *
 * A <details> rather than script: it opens on click and on Enter, closes on Escape, and works
 * before any JavaScript has loaded. The panel is positioned only where there is room for it;
 * on a narrow screen the menu is the one in the hamburger and this is not shown at all.
 */
.nav-drop { position: relative; }
/*
 * THE SAME COLOUR AS THE LINKS BESIDE IT. This said var(--navy), which is the colour of the bar
 * it sits on: the word "Products" was drawn in navy on navy, so the main menu item on every page
 * of the site was invisible, with only its little arrow showing.
 */
.nav-drop > summary {
  list-style: none; cursor: pointer; padding: 6px 2px; font-size: 15px; font-weight: 500;
  color: var(--navy-text);
}
.nav-drop > summary:hover { color: #fff; }
.nav-drop > summary::-webkit-details-marker { display: none; }
.nav-drop > summary::after { content: ' ▾'; color: var(--muted); }
.nav-drop[open] > summary { color: #fff; }
.nav-drop-panel {
  position: absolute; top: calc(100% + 10px); left: 0; z-index: 30; min-width: 260px;
  display: grid; gap: 2px; padding: 10px; background: var(--card);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 18px 40px rgba(0, 0, 0, .18);
}
.nav-drop-panel a { padding: 8px 10px; border-radius: 8px; font-size: 14.5px; color: var(--ink); }
.nav-drop-panel a:hover { background: var(--surface-2); color: var(--navy); }

/* ── a product page ──────────────────────────────────────────────────────────────────────── */
.product-hero { padding-block: 44px; }
.product-grid { display: grid; gap: 28px; align-items: center; }
.product-grid h1 { font-size: clamp(1.7rem, 1.2rem + 2vw, 2.4rem); }
.product-who { margin-top: 14px; color: var(--muted); font-size: 16.5px; }
.product-note {
  margin-top: 18px; font-size: 14px; color: var(--sand-text); background: var(--sand);
  border-radius: 10px; padding: 10px 12px;
}
/*
 * ── THE TWO STORE BADGES (6 Oct) ──────────────────────────────────────────────────────────────
 *
 * Both apps are live, so both are the stores' OWN artwork rather than text links. The files are
 * pinned by hash in site/badges/MANIFEST and neither is redrawn, recoloured or cropped: Apple's and
 * Google's guidelines both ask for the badge unaltered, so an update is a new download, never an edit.
 *
 * APPLE'S SPACING RULES, which are specific and the reason this is a rule rather than a flex gap:
 * the clear space around the badge must be at least 10% of its height, and the badge must not be
 * smaller than the artwork's own size. The badge is 40px tall, so the 12px gap and the 8px padding
 * are both comfortably past the 4px minimum -- and the two badges are set to the SAME height, which
 * is what stops one store looking like the afterthought.
 */
/* A paragraph of prose on a page that is otherwise lists and cards. A measure, so a sentence does
   not run the full width of a laptop: the eye loses the start of the next line past about 75
   characters, and these are the paragraphs somebody actually has to read. */
.prose { max-width: 70ch; }

/* ── the stores' own badges ───────────────────────────────────────────────────────────────
 *
 * ONE HEIGHT FOR THE BADGE, WHICH IS NOT ONE HEIGHT FOR THE FILE. Both badges were drawn at
 * height:40px and Google's looked a third smaller, because the two files are not the same kind of
 * thing:
 *
 *   Apple's download-on-the-app-store.svg is 119.66x40 and the black badge fills it.
 *   Google's en_badge_web_generic.png is 646x250 and the black badge is 564x168 of that, with 41
 *   transparent pixels on every side -- the quarter-of-the-badge-height clear space Google's
 *   guidelines ask for, built into the artwork.
 *
 * So at height:40px Apple's badge was 40px tall and Google's was 40 x 168/250 = 26.9px. Equal
 * boxes, visibly unequal badges. Scaling Google's file by 250/168 puts the black badge at the same
 * height as Apple's, and its own clear space then renders at a proportional 9.8px.
 *
 * NEITHER FILE IS TOUCHED. Both stores' guidelines ask that the badge is not redrawn, recoloured,
 * cropped or stretched; the artwork is served exactly as downloaded (pinned by hash in
 * www/site/badges/MANIFEST) and only its drawn height differs. The 12px gap is on top of Google's
 * own 9.8px, and is more than the tenth of the badge height Apple asks be kept clear around it.
 * tools/sitelayoutcheck.js measures the BLACK BADGE in the rendered page, at every width, rather
 * than trusting these numbers.
 */
.store-badges {
  --badge-visible: 40px;
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap; padding: 8px 0;
}
.store-badges a { display: inline-flex; align-items: center; text-decoration: none; }
.store-badges img { height: var(--badge-visible); width: auto; display: block; }
.store-badges .store-badge-google img {
  height: calc(var(--badge-visible) * 250 / 168);
}

/* Where the app is: the store that has it is a link, the one that does not is a sentence. */
.product-note .store-link {
  display: inline-block; font-weight: 600; color: var(--teal-dark); text-decoration: underline;
}
.product-note .store-soon { display: inline-block; margin-left: 14px; }
.product-note .store-or { display: block; margin-top: 6px; }
.feature-grid .tick-list li { padding-block: 12px; }
.feature-grid .tick-list strong { display: block; color: var(--navy); font-size: 16px; }
.how-grid { display: grid; gap: 28px; align-items: start; }
.how-steps { margin-top: 16px; padding-left: 20px; list-style: decimal; }
.how-steps li { padding: 7px 0; color: var(--ink); }
.cta-card {
  background: var(--card); border: 1px solid var(--line); border-left: 4px solid var(--teal);
  border-radius: var(--radius); padding: 24px 22px;
}
.cta-card p { margin: 10px 0 18px; color: var(--muted); font-size: 15px; }
.cta-card .btn { margin-right: 8px; }
@media (min-width: 900px) {
  .product-grid { grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr); gap: 56px; }
  .how-grid { grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); gap: 48px; }
  .product-hero { padding-block: 72px; }
}

/* ── the products overview, and the home page's list of them ─────────────────────────────── */
.product-grid-cards { display: grid; gap: 18px; }
.product-card-link { text-decoration: none; color: inherit; display: block; }
.product-card-link:hover .product-card { border-color: var(--teal); }
.product-more { display: inline-block; margin-top: 12px; color: var(--teal-dark); font-weight: 600; font-size: 14.5px; }
@media (min-width: 700px) { .product-grid-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .product-grid-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.home-products { display: grid; gap: 14px; }
.home-product {
  display: grid; gap: 4px; padding: 16px 18px; text-decoration: none;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
}
.home-product:hover { border-color: var(--teal); }
.home-product-name { font-family: 'Sora', 'Inter', system-ui, sans-serif; font-weight: 700; color: var(--navy); }
.home-product-what { color: var(--muted); font-size: 14.5px; }
@media (min-width: 700px) { .home-products { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) { .home-products { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

/* ── the public certificate check ───────────────────────────────────────────────────────── */
.verify-wrap { max-width: 640px; }
.verify-lede { margin-top: 12px; color: var(--muted); font-size: 16.5px; }
.verify-form { margin-top: 28px; }
/* The contact form's label and hint, which live under .field; this form is one row and does not
   use that wrapper, so it says so itself rather than inheriting furniture it does not have. */
.verify-form label {
  display: block; font-size: 14px; font-weight: 600; color: var(--navy); margin-bottom: 6px;
}
.verify-form .field-hint { margin-top: 8px; font-size: 13px; color: var(--muted); }
.verify-form input:focus { outline: 3px solid var(--teal-tint); border-color: var(--teal); }
.verify-row { display: grid; gap: 10px; }
.verify-row input {
  width: 100%; min-height: 48px; padding: 10px 14px; font-size: 16px; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink); background: var(--card);
  border: 1.5px solid var(--control-edge); border-radius: 10px;
}
@media (min-width: 560px) { .verify-row { grid-template-columns: 1fr auto; } }
.verify-result {
  margin-top: 28px; padding: 22px; background: var(--card); border: 1px solid var(--line);
  border-left: 4px solid var(--line); border-radius: var(--radius);
}
/* The verdict is the first thing read, so it is a colour as well as a word. */
.verify-result.is-good { border-left-color: var(--teal); }
.verify-result.is-expired { border-left-color: var(--sand-text); }
.verify-result.is-bad { border-left-color: var(--muted); }
.verify-badge {
  display: inline-block; margin-bottom: 10px; padding: 3px 10px; border-radius: 999px;
  font-size: 12px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  background: var(--surface-2); color: var(--navy);
}
.is-good .verify-badge { background: var(--teal); color: #fff; }
.is-expired .verify-badge { background: var(--sand); color: var(--sand-text); }
.verify-course { margin-top: 4px; font-size: 17px; color: var(--navy); font-weight: 600; }
.verify-fact { display: grid; gap: 2px; padding: 10px 0; border-top: 1px solid var(--line); }
.verify-fact:first-of-type { margin-top: 14px; }
.verify-label { font-size: 12px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.verify-value { color: var(--ink); }
@media (min-width: 560px) {
  .verify-fact { grid-template-columns: 170px 1fr; gap: 16px; align-items: baseline; }
}
.verify-note { margin-top: 16px; font-size: 13.5px; color: var(--muted); }

/* ── the five businesses ─────────────────────────────────────────────────────────────────── */
.industry-grid { display: grid; gap: 14px; }
.industry-tile {
  display: grid; gap: 6px; padding: 18px 20px; text-decoration: none;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
  border-left: 4px solid var(--teal);
}
.industry-tile:hover { border-color: var(--teal); }
.industry-name {
  font-family: 'Sora', 'Inter', system-ui, sans-serif; font-weight: 700; color: var(--navy);
}
.industry-what { color: var(--muted); font-size: 14.5px; }
@media (min-width: 700px) { .industry-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 1100px) {
  .industry-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  /* Five tiles in three columns: the last two share the bottom row rather than leaving a gap. */
  .industry-tile:nth-child(4) { grid-column: span 1; }
}

/* ── how it works ───────────────────────────────────────────────────────────────────────── */
/*
 * An ordered list, not a picture. A diagram of five boxes is an image a screen reader cannot
 * read, that needs a second copy for dark mode and a third for a phone; the arrows here are
 * drawn by CSS and disappear at the end of each row on their own.
 */
.flow { display: grid; gap: 14px; counter-reset: none; list-style: none; padding: 0; }
.flow-step {
  position: relative; display: grid; gap: 6px; padding: 18px 20px;
  background: var(--card); border: 1px solid var(--line); border-radius: var(--radius);
}
.flow-rank {
  display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px;
  border-radius: 50%; background: var(--teal); color: #fff; font-weight: 700; font-size: 13px;
}
.flow-name {
  font-family: 'Sora', 'Inter', system-ui, sans-serif; font-weight: 700; color: var(--navy);
}
.flow-what { color: var(--muted); font-size: 14.5px; }
@media (min-width: 900px) {
  .flow { grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 26px; }
  .flow-step + .flow-step::before {
    content: ''; position: absolute; left: -19px; top: 30px; width: 12px; height: 12px;
    border: solid var(--line); border-width: 2px 2px 0 0; transform: rotate(45deg);
  }
}

/* ── a week at an invented business ─────────────────────────────────────────────────────── */
.week-list { display: grid; gap: 0; list-style: none; padding: 0; max-width: 900px; }
.week-list li {
  display: grid; gap: 4px; padding: 14px 0; border-top: 1px solid var(--line); color: var(--ink);
}
.week-list li:first-child { border-top: 0; }
.week-day {
  font-family: 'Sora', 'Inter', system-ui, sans-serif; font-weight: 700; color: var(--navy);
  font-size: 15px;
}
@media (min-width: 800px) {
  .week-list li { grid-template-columns: 150px 1fr; gap: 20px; align-items: baseline; }
}

/* ── the staff app band, and the questions under pricing ────────────────────────────────── */
.app-band { display: grid; gap: 28px; align-items: center; }
.app-band .section-head { margin-bottom: 0; }
@media (min-width: 900px) { .app-band { grid-template-columns: 1.1fr 0.9fr; } }
.home-faq { margin-top: 28px; }

/* A heading inside the phone menu, so Industries is not a list of links with no name on it. */
.nav-menu-head {
  display: block; margin-top: 10px; padding: 6px 0 2px; font-size: 12px; letter-spacing: .08em;
  text-transform: uppercase; color: var(--muted);
}

/*
 * TWO COLUMNS ON A WIDE SCREEN. Four reasons in one 820px column left the right-hand half of a
 * laptop empty beside them, which reads as a page that did not finish loading.
 */
.benefits { max-width: 820px; }
@media (min-width: 1000px) {
  .benefits { max-width: none; display: grid; grid-template-columns: 1fr 1fr; column-gap: 48px; }
  /* The rule above each row is per column once they sit side by side, so the second column's
     first item needs its own line back. */
  .benefits li:nth-child(2) { border-top: 0; }
}
.benefits strong { display: block; color: var(--navy); font-size: 16px; }
.home-cta { margin-top: 28px; display: flex; gap: 10px; flex-wrap: wrap; }

/* ── the questions people ask ────────────────────────────────────────────────────────────── */
.faq { display: grid; gap: 10px; max-width: 820px; }
/* Two columns of questions on a laptop, rather than a column down the left and nothing beside it. */
@media (min-width: 1000px) { .faq { max-width: none; grid-template-columns: 1fr 1fr; column-gap: 20px; } }
.faq-item { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 18px; }
.faq-item > summary { cursor: pointer; font-weight: 600; color: var(--navy); list-style: none; }
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary::after { content: ' +'; color: var(--muted); }
.faq-item[open] > summary::after { content: ' –'; }
.faq-item p { margin-top: 10px; color: var(--muted); font-size: 15px; }

/* ── the company page ────────────────────────────────────────────────────────────────────── */
.company { max-width: 760px; }
.company p { margin-top: 14px; color: var(--muted); font-size: 16.5px; }
.company h2 { margin-top: 36px; font-size: 1.3rem; }
.company-detail { margin-top: 22px; display: grid; grid-template-columns: auto 1fr; gap: 8px 22px; }
.company-detail dt { font-weight: 600; color: var(--navy); font-size: 14.5px; }
.company-detail dd { margin: 0; color: var(--ink); font-size: 14.5px; }
.company-note { font-size: 14.5px; }

/* ── live vs coming ──────────────────────────────────────────────────────────────────── */
.status-grid { display: grid; gap: 18px; align-items: start; }
.status-box { border-radius: var(--radius); padding: 22px 24px; }
.status-live { background: var(--card); border: 1px solid var(--line); }
/* Dashed and unfilled, so it reads as a different kind of list before a word is read. */
.status-soon { background: transparent; border: 1.5px dashed var(--control-edge); }
.status-box h3 { font-size: 18px; }
.status-note { margin-top: 6px; font-size: 14px; color: var(--muted); }
.status-box ul { margin-top: 12px; }
.status-box li { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 0; border-top: 1px solid var(--line); font-size: 15px; }
.status-box li:first-child { border-top: 0; }
.pill { font-size: 11.5px; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; border-radius: 999px; padding: 2px 9px; white-space: nowrap; }
.pill-live { color: var(--teal-text); background: var(--teal-tint); }
.pill-soon { color: var(--sand-text); background: var(--sand); }
/* Built and submitted, not yet available: amber, the palette's "not done yet" status, so it cannot be
   mistaken for the teal Live beside it. */
.pill-review { color: var(--amber); background: var(--warn-bg); }
@media (min-width: 760px) { .status-grid { grid-template-columns: 1fr 1fr; } }

/* The .apps / .badges / .store-badge-* block that used to live here is gone. It styled a section
   no page has carried since the staff app page was rewritten, and it had solved this same problem
   once already -- Google's badge at 72px with a negative margin to cancel its built-in padding,
   Apple's at 48px -- while saying in a comment that "Apple's badge goes in when the app is
   approved". It is on the App Store. Worse, its class names matched the ones the live badges now
   use, so a dead rule was quietly drawing Apple's badge 8px taller than Google's. The live rules
   are .store-badges, above. */

/* ── contact ─────────────────────────────────────────────────────────────────────────── */
.contact-grid { display: grid; gap: 32px; }
.contact-info h2 { font-size: clamp(1.6rem, 1.2rem + 1.8vw, 2.3rem); }
.contact-info p { margin-top: 12px; color: var(--muted); max-width: 30em; }
.contact-detail { margin-top: 24px; }
.contact-detail dt { font-size: 12.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); }
.contact-detail dd { margin: 2px 0 16px; font-weight: 600; color: var(--navy); }

.contact-form { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 22px; }
.form-row { display: grid; gap: 0 16px; }
.field { margin-bottom: 16px; }
.field label { display: block; font-size: 14px; font-weight: 600; color: var(--navy); margin-bottom: 6px; }
/* 16px, not smaller: iOS zooms the whole page into any focused field under 16px. */
.field input, .field textarea {
  width: 100%; padding: 11px 13px; border: 1.5px solid var(--control-edge); border-radius: 8px;
  font: 16px/1.4 'Inter', system-ui, sans-serif; color: var(--ink); background: var(--paper);
}
.field input:focus, .field textarea:focus { outline: 3px solid var(--teal-tint); border-color: var(--teal); }
.field textarea { resize: vertical; min-height: 120px; }
.turnstile-box { margin: 4px 0 14px; }
.form-note { font-size: 13px; color: var(--muted); margin-top: 10px; }
/* Shown by its class alone; the script never sets an inline display. */
#formStatus { display: none; font-size: 14.5px; margin-top: 14px; }
#formStatus.ok { display: block; color: var(--teal-text); }
#formStatus.err { display: block; color: var(--danger-button); }
@media (min-width: 600px) { .form-row { grid-template-columns: 1fr 1fr; } .contact-form { padding: 30px; } }
@media (min-width: 900px) { .contact-grid { grid-template-columns: 5fr 6fr; gap: 56px; } }

/* ── footer ──────────────────────────────────────────────────────────────────────────── */
body > footer { background: var(--navy); color: var(--navy-text); padding-block: 40px 28px; }
.footer-top { display: flex; flex-direction: column; gap: 20px; padding-bottom: 24px; border-bottom: 1px solid rgba(255, 255, 255, .12); }
.footer-links { display: flex; flex-wrap: wrap; gap: 12px 22px; font-size: 14.5px; }
.footer-links a { color: var(--navy-text); text-decoration: none; }
.footer-links a:hover { color: #fff; }
.footer-bottom { padding-top: 18px; font-size: 13px; display: flex; flex-wrap: wrap; justify-content: space-between; gap: 8px; }
@media (min-width: 900px) { .footer-top { flex-direction: row; justify-content: space-between; align-items: center; } }
