/* Bulletin — editorial visual system
   Serif headlines, thin rules, two accents on strict duty, community-newspaper
   confidence. */

:root {
  /* Brand palette — the one place these colours are defined. Everything else points
     here. Two families, and the split is by job, not by taste:

       blue  — everything interactive. Links, buttons, nav, focus rings, and the one
               display word in the hero. Sampled from the logo mark (192deg, 78% sat)
               and dropped in lightness only, so mark and interface stay one colour.
       green — editorial marks. Rules, bars and quote marks that punctuate a passage
               but cannot be clicked. It never touches an interactive element; two
               colours both saying "interactive" is what this split exists to avoid.

     --brand-periwinkle is the folded wing in the logo artwork and drives the paperboy
     illustration; it is not part of either family and is never used outside artwork.
     Contrast ratios are measured against white; recheck them if you touch a value. */
  --accent: #147C97;       /* 4.82:1 on white — passes AA for text, and under white labels */
  --accent-deep: #106F84;  /* 5.59:1 on paper — passes AA for normal-size text */
  --accent-alt: #445B33;   /* 7.53:1 on white, 6.66:1 on the quote band — marks only */
  --brand-periwinkle: #A3B0D5;  /* illustration fold — decorative only, fails AA by design */

  /* The page canvas, and the only thing that paints it. Swap this one value to retint
     the whole site. Surfaces that sit *on* the canvas — dropdown panels, the tinted
     bands, card whites — are deliberately not tied to it and stay put when it moves.
     Anything drawn to look cut out of the canvas (the paperboy's wheels) points here
     too, so the illustration follows the swap instead of stranding cream discs. */
  --page-bg: #FFFFFF;

  --paper-tint: #F4F2EA;
  --ink: #191714;
  --ink-soft: #45413A;
  --muted: #6E6A60;
  --rule: #DEDACC;
  --rule-dark: #191714;

  /* Semantic roles. These name a job, not a colour — repoint them, don't respell them.
     --accent-deeper is mixed rather than hand-picked so hover and active states follow
     the pair automatically if it is ever repointed. */
  --accent-deeper: color-mix(in srgb, var(--accent-deep) 85%, #000);
  --btn: var(--accent);
  --btn-hover: var(--accent-deep);

  --max: 1120px;
  --display: "Libre Franklin", "Helvetica Neue", Arial, sans-serif;
  --sans: "Libre Franklin", "Helvetica Neue", Arial, sans-serif;
}

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

html { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

body {
  background: var(--page-bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.65;
  -webkit-font-smoothing: antialiased;
}

.wrap { max-width: var(--max); margin: 0 auto; padding: 0 24px; }
.wrap-narrow { max-width: 760px; margin: 0 auto; padding: 0 24px; }

a { color: var(--accent-deep); text-decoration: none; transition: color .15s ease; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }
/* The accent blue also marks non-clickable copy (.eyebrow, step numbers),
   so links in body copy underline to tell themselves apart. Chrome links — nav, footer,
   buttons, cards — carry a class and stay clean. */
main a:not([class]) { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
main a:not([class]):hover { text-decoration-thickness: 2px; }
/* .table-scroll is included because a horizontally scrolling table carries
   tabindex="0" — it is the only way to reach the off-screen columns by keyboard,
   and a focusable element needs a visible ring. */
a:focus-visible, button:focus-visible, .table-scroll:focus-visible {
  outline: 2px solid var(--accent-deep);
  outline-offset: 3px;
  border-radius: 2px;
}
/* Belt and braces: some engines (Safari, older WebKit) still paint a ring after a
   pointer click. The nav toggle already carries a 1px border, so a stray ring reads
   as a double box. */
a:focus:not(:focus-visible), button:focus:not(:focus-visible) { outline: none; }

/* ---------- Masthead ---------- */

/* Sign in used to live in a strip above the masthead. It now sits in the nav as the
   secondary action beside the CTA, which buys back a row above the fold and puts it
   in the mobile menu — the old strip hid it entirely below 560px. */
.nav-signin a { color: var(--ink); }

.masthead { position: relative; }
.masthead .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-top: 18px;
  padding-bottom: 16px;
}
.logo {
  font-family: var(--display);
  font-weight: 700;
  font-size: 27px;
  letter-spacing: -0.02em;
  color: var(--ink);
  display: flex;
  align-items: baseline;
  gap: 0.235em;
}
.logo:hover { text-decoration: none; }

/* Two lists now: the pages, and the auth pair that pins to the sheet footer on
   mobile. On desktop they sit in one row, so the nav itself carries the flex. */
nav.primary { font-family: var(--sans); font-size: 14px; font-weight: 500; display: flex; align-items: center; gap: 26px; }
nav.primary ul { list-style: none; }
nav.primary > ul { display: flex; align-items: center; gap: 26px; }
nav.primary > ul > li { position: relative; }
nav.primary a { color: var(--ink); }
nav.primary a:hover { color: var(--accent-deep); text-decoration: none; }
nav.primary a[aria-current="page"] { color: var(--accent-deep); }
nav.primary a.btn, nav.primary a.btn:hover { color: #fff; }

.has-menu > button {
  background: none;
  border: 0;
  font: inherit;
  color: var(--ink);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 0;
}
.has-menu > button::after {
  content: "";
  flex: none;
  width: 7px; height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  transition: transform .2s ease;
}
.has-menu.open > button::after { transform: rotate(-135deg) translateY(-2px); }
.has-menu > button:hover { color: var(--accent-deep); }
/* The wrapper exists only so the sheet has a box to animate to height. Out of the
   way on desktop, or it would become the flyout's containing block. */
.submenu-wrap { display: contents; }
nav.primary .has-menu ul.submenu,
.submenu {
  display: none;
  position: absolute;
  top: calc(100% + 14px);
  left: -20px;
  /* Same white as every other raised surface — cards, the price grid, the steps, the
     honesty box. Its own border and shadow are what lift it off the page, not a tint. */
  background: #fff;
  border: 1px solid var(--rule);
  border-top: 3px solid var(--accent);
  min-width: 280px;
  padding: 14px 0;
  z-index: 60;
  box-shadow: 0 12px 30px rgba(25, 23, 20, .09);
}
.submenu li { display: block; }
.submenu a { display: block; padding: 8px 22px; }
nav.primary .has-menu.open ul.submenu,
.has-menu.open .submenu { display: block; }
.submenu .menu-label {
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 4px 22px 10px;
  border-bottom: 1px solid var(--rule);
  margin-bottom: 8px;
}
.submenu-all { border-top: 1px solid var(--rule); margin-top: 8px; padding-top: 8px; }

.btn {
  font-family: var(--sans);
  font-size: 14px;
  font-weight: 700;
  display: inline-block;
  padding: 12px 22px;
  border: 1.5px solid var(--btn);
  color: #fff;
  background: var(--btn);
  cursor: pointer;
  white-space: nowrap;
  border-radius: 6px;
  transition: background-color .15s ease, border-color .15s ease, color .15s ease;
}
.btn:hover { background: var(--btn-hover); border-color: var(--btn-hover); color: #fff; text-decoration: none; }
/* The outlined button splits the pair: the label is small text and takes the deep step,
   the border is decoration and takes the brand bright, which frames it to match the
   filled button beside it. */
.btn.ghost { background: transparent; color: var(--accent-deep); border-color: var(--accent); }
.btn.ghost:hover { background: var(--paper-tint); color: var(--accent-deeper); border-color: var(--accent-deeper); }
.btn.big { font-size: 16px; padding: 15px 30px; }

/* The masthead used to close with a 3px + 1px near-black double rule. It was the
   heaviest mark on the page, so the header now separates from the hero on white
   space alone. If a rule comes back, match the topbar divider: 1px var(--rule). */

.nav-toggle { display: none; }

/* ---------- Sections & typography ---------- */

section { padding: 84px 0; }
section + section { border-top: 1px solid var(--rule); }

/* A section that continues the one above it rather than opening a new subject:
   its own .eyebrow rule is the divider, so it drops the border and the top
   padding and the eyebrow lands where the hairline would have been. */
section + section.section-continued { border-top: 0; padding-top: 0; }

.eyebrow {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--accent-deep);
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 22px;
}
.eyebrow::after { content: ""; flex: 1; height: 1px; background: var(--rule); }

h1, h2, h3 { font-family: var(--display); font-weight: 700; line-height: 1.12; letter-spacing: -0.022em; }
h1 { font-size: clamp(42px, 6.4vw, 74px); }
h2 { font-size: clamp(30px, 4vw, 44px); margin-bottom: 22px; }
h3 { font-size: 23px; margin-bottom: 10px; }

.lede { font-size: clamp(20px, 2.3vw, 24px); color: var(--ink-soft); line-height: 1.5; }
p + p { margin-top: 1em; }
.muted { color: var(--muted); }

.kicker-list { list-style: none; }
.kicker-list li {
  padding: 13px 0 13px 26px;
  border-bottom: 1px solid var(--rule);
  position: relative;
  font-size: 18px;
}
.kicker-list li::before {
  content: "";
  position: absolute;
  left: 0; top: 24px;
  width: 12px; height: 3px;
  background: var(--accent);
}

/* ---------- Hero ---------- */

.hero { padding: 90px 0 96px; }
/* Column ratio is arithmetic, not taste — this is the one number to turn if the
   illustration wants resizing. Container is 1072px at the 1120px cap; the 44px gap
   leaves 1028px, split 3.266:1 to give 787px of text and 241px of art, putting the
   art at 22.5% of the container — the size the drawing rendered at originally, before
   its transparent margin was cropped away.
   Raise the first number to shrink the art, lower it to grow it. The floor is ~1.46
   (text 610px); past that the text column crosses under the lede's 34ch measure at
   561px and the subhead rewraps off its 42-character break.
   align-items:end rather than center so both columns share a floor: the art's bottom
   edge lands on the bottom of the CTA buttons, which are the text column's last row. */
.hero .wrap {
  display: grid;
  grid-template-columns: minmax(0, 3.266fr) minmax(0, 1fr);
  gap: 44px;
  align-items: end;
}
/* The one display word in a hero headline. Listed for both hero types so an interior
   page gets the same single accent the homepage has, rather than a second rule. */
.hero h1 .accent,
.page-hero h1 .accent { color: var(--accent); }
.hero .lede { margin: 26px 0 14px; max-width: 34ch; }
.offer {
  font-family: var(--sans);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink-soft);
  border-left: 3px solid var(--accent-alt);
  padding-left: 14px;
  margin: 22px 0 30px;
  max-width: 46ch;
}
.hero-actions { display: flex; gap: 14px; flex-wrap: wrap; align-items: center; }
.hero-art { text-align: center; }
/* The four layers are cropped flush to the ink — no transparent margin — so the scene
   box IS the drawing. Filling the column therefore puts the illustration's right edge
   on the container's right edge, the same vertical line as the nav's Start free button.
   It used to be capped at 380px inside a 389px column while carrying ~90px of baked-in
   transparent padding, which is where the dead space to its right came from. */
.hero-art svg,
.hero-art .cat-scene { width: 100%; max-width: 100%; height: auto; }

/* One drawing in four layers. The frame and the plane keep the full original canvas,
   so stacking them at inset:0 puts every pixel back where it was drawn. The two spoke
   sets are cropped to 240x240 boxes centred exactly on their hubs, which is why they
   can rotate about `50% 50%` with no origin arithmetic.
   The frame is in flow and sets the height; everything else floats over it. */
/* Nudged off the container's right edge so the drawing sits more centred in the space
   beside the copy. A relative offset rather than a grid change: it moves the artwork
   without resizing it or disturbing the column maths, and the layers inside — which are
   positioned against this box — come with it. Raise the percentage to move it further
   left; it is a share of the art column, so it holds proportion as the column flexes. */
.cat-scene { position: relative; margin: 0 auto; right: 20%; }
.cat-scene img { display: block; width: 100%; height: auto; }
/* The plane and its trail are cropped tight and placed like the spokes. Cropping the
   plane is what makes it bank instead of orbit: as a full-canvas layer its rotation
   origin was the centre of the whole 837x1009 drawing, roughly 160px away from the
   plane itself, so a 21deg rotation swung it in a wide arc rather than tilting it. */
.cat-scene .cat-plane { position: absolute; left: 0; top: 0; width: 19.355%; height: auto; }
.cat-scene .cat-trail { position: absolute; left: 20.072%; top: 13.082%; width: 15.054%; height: auto; }
/* Scoped to .cat-scene so these beat `.cat-scene img { width: 100% }` above — an
   unscoped .cat-spokes loses on specificity and the wheels render full-canvas. */
.cat-scene .cat-spokes { position: absolute; width: 28.674%; height: auto; }
.cat-scene .cat-spokes-front { left: 14.337%; top: 73.043%; }
.cat-scene .cat-spokes-rear  { left: 69.654%; top: 74.827%; }

/* The bike rolls; it does not bounce. An earlier version rocked the whole cat, which
   reads as bouncing because a bicycle in motion holds its line — the wheels turn, the
   frame stays level. So the frame is now still and only the spokes rotate.
   The rims are plain circles and rotationally symmetric, so they never needed to move;
   the spokes are the only part of a wheel that shows rotation at all. Linear timing,
   because a wheel at speed does not ease.
   Every animation here stops under the prefers-reduced-motion rule at the top of this
   file, leaving each layer at its drawn position — the still illustration, as before. */
.cat-spokes {
  transform-origin: 50% 50%;
  animation: wheel-roll 1.9s linear infinite;
}
/* --fly scales the whole flight path. At full size the plane arcs out of the scene box
   and over the gap toward the headline, which is the point. Stacked on a phone there is
   no gap to arc into — the scene starts at the left margin — so the same travel carries
   it off the edge of the screen. The breakpoint shortens the arc instead of clipping it. */
.cat-plane {
  --fly: 1;
  animation: plane-away 4.6s ease-out infinite;
}
/* Counter-clockwise, because the bike faces left: the fork and handlebars are on the
   left wheel, the chain and cranks drive the right one. Rotating positive (clockwise)
   spins the wheels as though it were travelling right, and reads as rolling backwards. */
@keyframes wheel-roll {
  from { transform: rotate(0deg); }
  to   { transform: rotate(-360deg); }
}
/* Starts and ends at the drawn position at full opacity, so the first and last frame
   of the loop are the still illustration. It flies out, fades, then cuts back to the
   cat's paw while invisible and fades in again — the reset is never seen.
   Beginning at opacity 0 was wrong: a backgrounded tab parks a CSS animation on its
   first frame, which left the plane missing until the tab was focused. */
/* An arc, not a straight diagonal. The plane climbs, flattens, then leans back down
   and to the left toward the headline, so the two halves of the hero are tied together
   instead of the plane drifting off into the corner. The vertical stops (-7, -11, -11,
   -7) are what bend it; a single end-stop can only ever draw a straight line.
   Starts and ends at the drawn position at full opacity, so frame one of the loop is
   the still illustration; the reset to the paw happens while invisible. */
/* Percentages are of the plane's own box now that it is cropped tight, which is why
   the numbers are large — the travel is several plane-widths, not a fraction of the
   scene. They still scale with the illustration, since the plane scales with it.

   The climb never reverses. An earlier version arced over the top and came back down,
   which is geometrically an arc but reads as the plane stalling and falling — nose-down
   at the end made it worse. The curve now comes from a decelerating climb: the vertical
   steps shrink (-85, -70, -50, -40) so the path flattens as it goes, the way a thrown
   plane loses lift without dropping. It leaves the frame still rising. Keep those steps
   descending if you retune the path; equal or growing steps bring the fall back.

   Distance is carried by the scale, not the travel. Shrinking to .82 over the flight
   was too little to read as depth, so the plane just slid sideways; .42 is what makes
   it recede. The horizontal steps stay roughly even after launch so it holds speed
   while it shrinks, which is what "going away" looks like.
   The rest: ease-in off the paw then linear through the glide, so it accelerates away
   and holds speed instead of easing in and out of every waypoint; the nose pitched up
   at launch and levelling off (-6deg to 0), never pitching down; and a slight
   scale-down as it recedes. */
@keyframes plane-away {
  0%   { transform: translate(0, 0) rotate(0deg) scale(1); opacity: 1;
         animation-timing-function: ease-in; }
  20%  { transform: translate(calc(-45% * var(--fly)),  calc(-85% * var(--fly)))  rotate(-6deg) scale(.88); opacity: 1;
         animation-timing-function: linear; }
  45%  { transform: translate(calc(-150% * var(--fly)), calc(-155% * var(--fly))) rotate(-4deg) scale(.72); opacity: 1;
         animation-timing-function: linear; }
  62%  { transform: translate(calc(-260% * var(--fly)), calc(-205% * var(--fly))) rotate(-2deg) scale(.58); opacity: 1;
         animation-timing-function: linear; }
  80%  { transform: translate(calc(-370% * var(--fly)), calc(-245% * var(--fly))) rotate(0deg)  scale(.42); opacity: 0; }
  81%  { transform: translate(36%, 48%) rotate(-8deg) scale(.9); opacity: 0; }
  100% { transform: translate(0, 0) rotate(0deg) scale(1); opacity: 1; }
}

/* The trail is the mark the throw left behind, so it stays put while the plane goes.
   It fades once the plane is clear and returns as the plane settles, which keeps the
   resting frame identical to the still drawing. */
.cat-trail { animation: trail-fade 4.6s ease-in-out infinite; }
@keyframes trail-fade {
  0%, 10%  { opacity: 1; }
  32%, 86% { opacity: 0; }
  100%     { opacity: 1; }
}
.hero-art figcaption {
  font-family: var(--sans);
  font-size: 11px;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
  margin-top: 18px;
}

/* The cat, set into the about page's story column. Sized against the viewport as well
   as in pixels so it comes down on a phone without a breakpoint of its own.
   Larger than it looks it needs to be, and tight against the text above and below:
   this is the flat source image, which still carries the transparent margin the hero's
   layers were cropped to remove, so the ink occupies roughly three-quarters of the box
   and the image's own padding supplies most of the breathing room. */
.story-art { display: block; margin: 8px auto 12px; width: min(360px, 72vw); height: auto; }

/* Page hero (interior pages) */
.page-hero { padding: 76px 0 70px; }
.page-hero h1 { font-size: clamp(38px, 5vw, 60px); max-width: 18ch; }
.page-hero .lede { margin-top: 22px; max-width: 58ch; }
.page-hero .hero-actions { margin-top: 32px; }

/* The audience heroes carry their own drawing beside the copy. The split follows the
   homepage's arithmetic rather than a fraction: 1072px container less the 44px gap is
   1028px, at 2.6:1 that is 742px of text and 285px of art — wide enough for the h1's
   18ch measure at its 60px cap, and the drawings are square, so the art column is also
   the art's height. Centred rather than bottom-aligned: these drawings have no ground
   line to stand on the way the paperboy's bicycle does. */
.page-hero.has-art .wrap {
  display: grid;
  grid-template-columns: minmax(0, 2.6fr) minmax(0, 1fr);
  gap: 44px;
  align-items: center;
}
.aud-art { width: 100%; max-width: 285px; height: auto; justify-self: end; }

/* ---------- Grids & cards ---------- */

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 48px; align-items: start; }
/* The internal rules are a 1px gap letting the container's background through, not
   borders on the cells. Cell borders drew the verticals but nothing between rows, so
   features' six items read as three tall columns of two paragraphs each rather than a
   3x2 grid. A gap rules both axes for any item count, and the three single-row grids
   elsewhere are unchanged — one row has no horizontal rule to draw. Cells are painted
   rather than left transparent so the gap cannot show through them; every .grid-3 sits
   on the canvas, so --page-bg keeps them following a retint the way they do now. */
.grid-3 {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}
.grid-3 > div { background: var(--page-bg); padding: 30px 26px; }
.grid-3 h3 { font-size: 21px; }
.grid-3 p { font-size: 16.5px; color: var(--ink-soft); }

.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 18px; }
.aud-card {
  border: 1px solid var(--rule);
  border-radius: 8px;
  padding: 22px 22px 20px;
  display: block;
  color: var(--ink);
  background: #fff;
  transition: border-color .15s ease;
}
.aud-card:hover { border-color: var(--accent); text-decoration: none; }
/* The plain mark of the same drawing the audience page opens with. Decorative — the
   card's own name says which audience it is — so it is hidden from screen readers. */
.aud-card .aud-icon { width: 38px; height: 38px; display: block; margin-bottom: 14px; }
.aud-card .aud-name { font-family: var(--display); font-weight: 600; font-size: 21px; display: block; }
.aud-card .aud-line { font-family: var(--sans); font-size: 13.5px; color: var(--muted); display: block; margin-top: 6px; line-height: 1.5; }
.aud-card .aud-go { font-family: var(--sans); font-size: 12px; font-weight: 700; letter-spacing: .1em; text-transform: uppercase; color: var(--accent-deep); display: block; margin-top: 14px; }

/* ---------- "Something worth saying" (home) ---------- */

/* .grid-2 is shared by ten sections across seven files, so the asymmetric split lives
   on a modifier only this section carries rather than on the shared rule. 5:6 with a
   44px gutter — see the note in the commit about 5/12 + 6/12 leaving a twelfth over. */
.grid-2.split-5-6 { grid-template-columns: 5fr 6fr; gap: 44px; }

.worth-saying h2 { text-wrap: balance; }
/* Closes the gap under the shorter column without touching the shared `section` rule. */
.worth-saying { padding-bottom: 56px; }

.worth-list { list-style: none; margin-top: 30px; border-top: 0.5px solid var(--rule); }
.worth-list li {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 0;
  border-bottom: 0.5px solid var(--rule);
  font-family: var(--sans);
  font-size: 16.5px;
  color: var(--ink-soft);
}
/* Sized here, not in the include, so the shapes stay reusable at other sizes. */
.note-icon { width: 18px; height: 18px; flex: none; }

/* The card-scale marks, heading a .grid-3 cell — about's "Our values" columns and
   features' "essentials". Named for the job rather than either section, since it serves
   both. These nine are drawings rather than editorial rules: the same hand and the same
   blue as the audience marks on the who-it's-for hub, so a reader meets one set of
   drawings across the site. That is why they take --accent and not --accent-alt, even
   though nothing in the cards is clickable — the split still holds for the rules, bars
   and quote marks it was written for. The 18px .note-icon shapes on the homepage are
   untouched and stay editorial. Left-aligned above the heading rather than centred over
   it, so the cards keep the site's card style and only gain the mark. */
.card-mark { display: block; width: 30px; height: 30px; color: var(--accent); margin-bottom: 16px; }

/* ---------- Product screenshots ---------- */

/* The frame, the caption and the space around them belong to the figure, so replacing a
   shot is replacing the <img> and nothing else. Deliberately no aspect-ratio: a screenshot
   left at height:auto sets its own, and forcing one either crops it or letterboxes it.
   Keep replacements near the ratio of the file they replace, or the column reflows. */
.shot { margin: 0; }
/* Set below a column of prose rather than standing alone in its own section. Two columns
   of unequal copy would otherwise start their frames at unequal heights, so the pair is
   pushed to the foot of a stretched row and lines up on its bottom edge. Scoped through
   :has() rather than a modifier class because .grid-2 is shared by ten sections across
   seven files and only the ones holding a shot should stretch. margin-top is auto for the
   push and the 28px gap is padding, so a short column can't close it up. */
.grid-2:has(.shot-inline) { align-items: stretch; }
.grid-2:has(.shot-inline) > * { display: flex; flex-direction: column; }
.shot-inline { margin-top: auto; padding-top: 28px; }

/* The drawings are inline SVG (in _includes/) rather than files in assets/ for one
   reason: a standalone .svg cannot read :root, so every brand colour would have to be
   respelled as a hex literal in three more files. Inlined, they point at the tokens.
   Sized by width alone — the viewBox supplies the height. */
.shot svg {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--rule);
  border-radius: 8px;
}

/* One vocabulary, shared by all three drawings, so a change lands on every screen at
   once. Colours are the site's own tokens on their usual duty: --accent for the single
   filled button each screen has, --accent-alt for the chart bars (editorial marks,
   nothing clickable), --brand-periwinkle for the depicted email design, which is
   artwork. Type sizes are in viewBox units, not pixels — each drawing is scaled to its
   column, so 13 here is not 13 on screen. */
.ui-shot { font-family: var(--sans); }
.ui-panel  { fill: #fff; stroke: var(--rule); stroke-width: 1; }
.ui-canvas { fill: var(--paper-tint); }
.ui-rule   { stroke: var(--rule); stroke-width: 1; }
.ui-grid   { stroke: var(--rule); stroke-width: 1; stroke-dasharray: 3 4; }

.ui-label   { fill: var(--muted); font-size: 12px; font-weight: 700; letter-spacing: 1.4px; }
.ui-value   { fill: var(--ink); font-size: 30px; font-weight: 700; }
.ui-heading { fill: var(--ink); font-size: 17px; font-weight: 600; }
.ui-dim     { fill: var(--muted); font-size: 13px; }
.ui-tick    { fill: var(--muted); font-size: 12px; }
.ui-axis .ui-tick { text-anchor: end; }
.ui-baseline .ui-tick { text-anchor: middle; }
.ui-bar     { fill: var(--accent-alt); }

.ui-tab       { fill: var(--muted); font-size: 15px; }
.ui-tab.is-on { fill: var(--ink); font-weight: 600; }
.ui-tab-mark  { fill: var(--ink); }
.ui-col       { fill: var(--muted); font-size: 12px; font-weight: 700; }
.ui-cell      { fill: var(--ink); font-size: 14px; }
.ui-cell-dim  { fill: var(--ink-soft); font-size: 14px; }
.ui-pill      { fill: var(--paper-tint); }
.ui-pill-text { fill: var(--ink-soft); font-size: 12px; text-anchor: middle; }
.ui-link      { fill: var(--accent-deep); font-size: 12px; font-weight: 700; }

.ui-btn        { fill: var(--accent); }
.ui-btn-label  { fill: #fff; font-size: 13px; font-weight: 700; text-anchor: middle; }
.ui-ghost      { fill: #fff; stroke: var(--rule); stroke-width: 1; }
.ui-ghost-label{ fill: var(--ink-soft); font-size: 12px; text-anchor: middle; }

.ui-art          { fill: var(--brand-periwinkle); }
.ui-art-eyebrow  { fill: #fff; font-size: 10px; font-weight: 700; letter-spacing: 1.2px; }
.ui-art-right    { text-anchor: end; }
.ui-art-display  { fill: #fff; font-size: 34px; font-weight: 800; text-anchor: middle; letter-spacing: .5px; }
.ui-art-sub      { fill: #fff; font-size: 12px; text-anchor: middle; }
.ui-art-image    { fill: rgba(255, 255, 255, .28); stroke: rgba(255, 255, 255, .55); stroke-width: 1; }
.shot figcaption {
  font-family: var(--sans);
  font-size: 13px;
  color: var(--muted);
  margin-top: 12px;
}

/* ---------- Tables ---------- */

.table-scroll { overflow-x: auto; }
table.compare {
  width: 100%;
  border-collapse: collapse;
  font-family: var(--sans);
  font-size: 15px;
  background: #fff;
  border: 1px solid var(--rule);
}
table.compare caption {
  text-align: left;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  padding-bottom: 12px;
}
table.compare th, table.compare td { padding: 13px 16px; text-align: left; border-bottom: 1px solid var(--rule); }
table.compare thead th {
  border-bottom: 2px solid var(--ink);
  font-size: 12px;
  letter-spacing: .1em;
  text-transform: uppercase;
}
table.compare tbody tr:last-child td, table.compare tbody tr:last-child th { border-bottom: 0; }
/* Winning cells are emphasis, not links — a tinted cell highlights them without
   borrowing the accent blue that body copy uses for anchors. */
table.compare .win { background: color-mix(in srgb, var(--accent) 10%, transparent); font-weight: 700; }
table.compare td:first-child, table.compare th:first-child { font-weight: 600; }
.table-note { font-family: var(--sans); font-size: 13px; color: var(--muted); margin-top: 12px; }

/* ---------- Pricing ---------- */

.price-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; border: 1px solid var(--rule); border-right: 0; background: #fff; }
.price-cell { border-right: 1px solid var(--rule); padding: 34px 26px; }
.price-cell.feature { background: var(--paper-tint); }
.price-cell .plan {
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: var(--muted);
}
.price-cell .amount { font-family: var(--display); font-size: 44px; font-weight: 600; margin: 12px 0 2px; letter-spacing: -0.02em; }
.price-cell .amount sup { font-size: 20px; top: -18px; position: relative; }
.price-cell .per { font-family: var(--sans); font-size: 14px; color: var(--muted); }
.price-cell .desc { font-size: 16px; color: var(--ink-soft); margin-top: 14px; line-height: 1.5; }

/* ---------- Quote / testimonial ---------- */

/* Three stacked tiers: headline quote, optional body quote, attribution. The band is a
   quiet tint rather than a near-black slab — it separates from the page canvas without
   the page having to go dark around it. The greys below are local to this component;
   nothing else on the site uses them. */
.pull-quote { background: #F2F1EC; padding: 52px 56px; }

/* Headline tier. The negative text-indent is what hangs the opening quote mark in
   the margin: it shifts the first line left by one mark's width, so the first letter
   sits flush with the body copy and attribution beneath it. In em, so it holds at
   any size. */
.pull-quote .quote-headline {
  font-size: 34px;
  font-weight: 400;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: #1A1A18;
  max-width: 26ch;
  text-indent: -0.42em;
  margin-bottom: 20px;
}
.pull-quote .quote-headline::before { content: "“"; color: var(--accent-alt); }
.pull-quote .quote-headline::after { content: "”"; color: var(--accent-alt); }

/* Body tier. Carries its own bottom gap, so a quote with no body closes up cleanly
   instead of leaving the attribution stranded. No marks here — the headline's pair
   wraps both. Note `ch` is the width of a zero, and Libre Franklin's is .688em, so
   52ch measures out nearer 70 characters a line than 52. */
.pull-quote .quote-body {
  font-size: 16px;
  line-height: 1.6;
  color: #4A4944;
  max-width: 52ch;
  margin-bottom: 28px;
}

.pull-quote figcaption {
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: .11em;
  text-transform: uppercase;
  /* --muted, not the lighter grey this started as: at 12px the attribution needs
     4.5:1, and #8A897F only reached 3.11:1 on the band. */
  color: var(--muted);
}

/* ---------- Steps ---------- */

.steps { list-style: none; counter-reset: step; display: grid; grid-template-columns: repeat(4, 1fr); gap: 0; border: 1px solid var(--rule); border-right: 0; background: #fff; }
.steps li { counter-increment: step; border-right: 1px solid var(--rule); padding: 28px 24px; }
.steps li::before {
  content: counter(step, decimal-leading-zero);
  font-family: var(--sans);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .14em;
  color: var(--accent-deep);
  display: block;
  margin-bottom: 12px;
}
.steps .step-word { font-family: var(--display); font-size: 24px; font-weight: 600; display: block; margin-bottom: 6px; }
.steps p { font-size: 15.5px; font-family: var(--sans); color: var(--muted); line-height: 1.55; }

/* ---------- FAQ ---------- */

.faq { border-top: 1px solid var(--rule); }
.faq details { border-bottom: 1px solid var(--rule); }
.faq summary {
  cursor: pointer;
  list-style: none;
  padding: 22px 40px 22px 0;
  font-family: var(--display);
  font-size: 21px;
  font-weight: 600;
  position: relative;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+";
  position: absolute;
  right: 4px; top: 50%;
  transform: translateY(-50%);
  font-family: var(--sans);
  font-size: 24px;
  font-weight: 300;
  color: var(--accent-deep);
}
.faq details[open] summary::after { content: "–"; }
.faq .answer { padding: 0 0 26px; max-width: 66ch; color: var(--ink-soft); font-size: 17.5px; }

/* ---------- CTA band ---------- */

.cta-band { background: var(--paper-tint); text-align: center; }
.cta-band h2 { max-width: 22ch; margin: 0 auto 18px; }
.cta-band p { max-width: 52ch; margin: 0 auto 32px; color: var(--ink-soft); }

/* The homepage's closing band is the page's last word, so it runs its own rhythm:
   a smaller plane, wider gaps, and a bigger target than the six shared bands carry.
   Everything here is scoped to .cta-closing so those bands stay as they are. */
.cta-band.cta-closing { padding: 96px 0 63px; }
.cta-closing h2 { max-width: 26ch; margin-bottom: 32px; text-wrap: balance; }
.cta-closing p { margin-bottom: 40px; }
.cta-closing .btn.big { font-size: 19px; padding: 20px 40px; }

/* ---------- Honesty box ---------- */

/* The left bar is the second accent, same as the hero callout and the quote marks:
   a mark that sets a passage apart without being clickable. One rule for all four
   boxes — the about-page bios used to override it and no longer need to. */
.honesty {
  border: 1px solid var(--rule);
  border-left: 4px solid var(--accent-alt);
  background: #fff;
  padding: 34px 36px;
}
.honesty h3 { margin-bottom: 12px; }
.honesty ul { margin: 14px 0 16px 20px; color: var(--ink-soft); }
.honesty li { margin: 6px 0; }

/* ---------- Footer ---------- */

footer { border-top: 3px solid var(--ink); margin-top: 40px; }
footer .inner-rule { border: 0; border-top: 1px solid var(--ink); margin: 2px 0 0; }
.foot-grid {
  display: grid;
  grid-template-columns: 1.4fr 1fr 1fr 1fr;
  gap: 40px;
  padding: 54px 0 40px;
}
.foot-grid h4 {
  font-family: var(--sans);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 16px;
}
.foot-grid ul { list-style: none; }
.foot-grid li { margin: 9px 0; }
.foot-grid a { color: var(--ink); font-family: var(--sans); font-size: 14.5px; }
.foot-grid a:hover { color: var(--accent-deep); }
.foot-brand .logo { font-size: 26px; }
.foot-brand p { font-family: var(--sans); font-size: 14px; color: var(--muted); margin-top: 12px; max-width: 28ch; }
.colophon {
  border-top: 1px solid var(--rule);
  padding: 18px 0 26px;
  font-family: var(--sans);
  font-size: 12px;
  letter-spacing: .06em;
  color: var(--muted);
  display: flex;
  justify-content: space-between;
  gap: 16px;
  flex-wrap: wrap;
}

/* ---------- Responsive ---------- */

@media (max-width: 960px) {
  .hero .wrap { grid-template-columns: 1fr; gap: 44px; }
  .hero-art { order: -1; }
  .hero-art svg,
  .hero-art .cat-scene { max-width: 280px; }
  /* Stacked, the illustration is centred in a full-width column already —
     the desktop nudge off the right edge would just knock it off centre. */
  .cat-scene { right: 0; }
  /* Stacked, the scene sits at the left margin with no gap to arc into. */
  .cat-plane { --fly: .22; }
  /* The audience drawing stacks under the copy rather than over it: the homepage art
     leads because it is the page's picture, but here the headline names the audience
     and should still be the first thing on a phone. */
  .page-hero.has-art .wrap { grid-template-columns: 1fr; gap: 36px; }
  .aud-art { max-width: 230px; justify-self: center; }
  /* .grid-3 stacks on its own gap — the rules come free. .steps still draws borders. */
  .grid-3 { grid-template-columns: 1fr; }
  .steps { grid-template-columns: 1fr; border-right: 1px solid var(--rule); }
  .steps li { border-right: 0; border-bottom: 1px solid var(--rule); }
  .steps li:last-child { border-bottom: 0; }
  .price-grid { grid-template-columns: 1fr 1fr; border-right: 1px solid var(--rule); }
  .price-cell { border-bottom: 1px solid var(--rule); border-right: 0; }
  .price-cell:nth-child(odd) { border-right: 1px solid var(--rule); }
  .grid-2 { grid-template-columns: 1fr; }
  /* Higher specificity than the rule above, so the modifier must be restated here. */
  .grid-2.split-5-6 { grid-template-columns: 1fr; gap: 32px; }
  .foot-grid { grid-template-columns: 1fr 1fr; }

  .nav-toggle { display: inline-block; }

  /* Close and Sign in are the same outlined chip, so they share one declaration block
     and cannot drift apart. Both live inside this breakpoint on purpose: a sheet opened
     on a narrow window used to leave desktop's Sign in wearing the chip once the window
     was widened past 960px. site.js now clears .open at this same width, so this is the
     second line of defence — desktop's Sign in is the plain nav link that About is even
     if the class survives. */
  .nav-toggle,
  nav.primary.open .nav-signin a {
    background: none;
    border: 1px solid var(--ink);
    font-family: var(--sans);
    font-size: 12px;
    /* Stated, not inherited: the button already defaults to 400, but nav.primary sets
       500 on everything inside it, which would land Sign in a notch heavier. */
    font-weight: 400;
    letter-spacing: .12em;
    text-transform: uppercase;
    padding: 8px 14px;
    cursor: pointer;
    color: var(--ink);
  }

  /* The menu is a full-bleed overlay, not a dropdown hanging off the header: fixed to
     the viewport at inset 0, so no ancestor padding, width, or background can show
     through at an edge. The masthead is lifted above it so the logo and Close button
     stay live; --masthead-h (published by site.js) is how far the list clears them.
     The overlay is a descendant of .masthead, so lifting the masthead can't put it
     under its own child — the logo and toggle are the pair that has to be raised. */
  .masthead { z-index: 80; }
  .masthead .logo, .nav-toggle { position: relative; z-index: 90; }
  /* The sheet is a column: the page list scrolls, the auth pair is pinned to the
     bottom. Padding moves onto the scroller so the footer sits flush to the edge. */
  nav.primary {
    display: none;
    position: fixed;
    inset: 0;
    width: 100vw;
    height: 100dvh;
    background: var(--page-bg);
    z-index: 70;
    overscroll-behavior: contain;
    padding: 0;
    gap: 0;
  }
  nav.primary.open { display: flex; flex-direction: column; align-items: stretch; }
  body.nav-open { overflow: hidden; }

  nav.primary > ul { flex-direction: column; align-items: stretch; gap: 0; }
  nav.primary > ul.nav-pages {
    flex: 1 1 auto;
    min-height: 0;
    overflow-y: auto;
    overscroll-behavior: contain;
    padding: calc(var(--masthead-h, 80px) + 10px) 24px 20px;
  }
  nav.primary > ul.nav-pages > li { border-bottom: 1px solid var(--rule); }
  nav.primary > ul.nav-pages > li:last-child { border-bottom: 0; }

  /* Top level is a step up in size and weight so the sheet reads as two tiers
     rather than one long list of near-identical rows. */
  nav.primary > ul.nav-pages > li > a, .has-menu > button {
    display: flex;
    align-items: center;
    justify-content: space-between;
    width: 100%;
    text-align: left;
    padding: 15px 0;
    font-size: 19px;
    font-weight: 600;
    letter-spacing: -.01em;
  }
  /* Features / Pricing / Why switch / About leave the sheet; "Who it's for" opens in
     place. A chevron that points where the tap goes tells the two apart before it. */
  nav.primary > ul.nav-pages > li > a::after {
    content: "";
    flex: none;
    width: 7px; height: 7px;
    border-right: 1.5px solid var(--rule);
    border-bottom: 1.5px solid var(--rule);
    transform: rotate(-45deg);
  }

  /* Height, not display, so the disclosure can animate. 0fr→1fr needs the row to be
     able to shrink, hence min-height/overflow on the list. visibility keeps the
     collapsed links out of the tab order — they are laid out, just not shown.
     The reduced-motion block at the top of this file drops both transitions. */
  .submenu-wrap {
    display: grid;
    grid-template-rows: 0fr;
    visibility: hidden;
    transition: grid-template-rows .24s ease, visibility .24s;
  }
  .has-menu.open > .submenu-wrap { grid-template-rows: 1fr; visibility: visible; }
  /* Matches the desktop selector's specificity — plain `.submenu` loses to
     `nav.primary .has-menu ul.submenu` and the dropdown stays floating. */
  nav.primary .has-menu ul.submenu {
    display: block;
    position: static;
    border: 0;
    border-left: 3px solid var(--accent);
    box-shadow: none;
    min-width: 0;
    overflow: hidden;
    min-height: 0;
    /* No vertical padding or margin: this is the animated grid item, and its box
       does not collapse with the 0fr row — either would leave a ghost gap under
       the trigger when shut. Vertical spacing lives on the rows instead. */
    margin: 0 0 0 4px;
    padding: 0;
  }
  .submenu-all { padding-bottom: 6px; }
  nav.primary .submenu a { font-size: 15px; font-weight: 400; color: var(--ink-soft); padding: 9px 18px; }
  /* The eyebrow labels the desktop flyout, which floats free of its trigger. In the
     sheet the trigger sits directly above the list and already says "Who it's for",
     so the label is the same words twice. Hidden here, kept there — one markup. */
  .submenu .menu-label { display: none; }
  /* First five audiences only; the rest live behind "All audiences", which is the
     last child. Counted from the label at :nth-child(1), so five names are 2–6.
     Keeps Pricing and Why switch above the fold with the sheet open. */
  .submenu > li:nth-child(n+7):not(.submenu-all) { display: none; }
  .submenu-all a { font-weight: 500; color: var(--accent-deep); }

  /* Pinned footer: the two actions stay put while the list behind them scrolls. */
  nav.primary > ul.nav-actions {
    flex: none;
    display: flex;
    flex-direction: row;
    align-items: center;
    gap: 16px;
    border-top: 1px solid var(--rule);
    background: var(--page-bg);
    padding: 14px 24px calc(14px + env(safe-area-inset-bottom, 0px));
  }
  /* 40/60. Basis 0 rather than auto so the split is the ratio alone and neither
     label's own width skews it; the 16px gap comes out of the row, not the shares. */
  nav.primary > ul.nav-actions > li.nav-signin { flex: 4 1 0; display: flex; }
  .nav-cta { flex: 6 1 0; padding: 0; }
  /* Both actions fill their share and clear the 44px minimum target. The chip's
     colour, border, and letter-spacing come from the shared rule above. */
  nav.primary .nav-signin a,
  .nav-cta .btn {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    min-height: 44px;
    text-align: center;
  }
}

@media (max-width: 560px) {
  body { font-size: 17.5px; }
  section { padding: 60px 0; }
  .cta-band.cta-closing { padding: 68px 0 45px; }
  .cta-closing .btn.big { display: block; width: 100%; min-height: 56px; }
  .hero { padding: 54px 0 64px; }
  .price-grid { grid-template-columns: 1fr; }
  .price-cell, .price-cell:nth-child(odd) { border-right: 0; }
  .foot-grid { grid-template-columns: 1fr; }
  /* The band's 56px inset would stack on top of .wrap's own 24px and leave the quote
     in a 230px column on a phone. Below this width the wrap alone sets the inset. */
  .pull-quote { padding-left: 0; padding-right: 0; }
}

/* ---------- Logo mark (hand-drawn paper airplane) ---------- */
/* Sized in em so the mark tracks the wordmark wherever it renders. Libre Franklin's
   cap height is 0.742em; 1.05em clears that top and bottom, which is the smallest
   the badge goes before the plane's facets merge into a smudge. `top` re-centres the
   circle on the cap band — .logo aligns on the baseline, so without it the whole
   overshoot would hang above the "B" instead of splitting evenly. */
.logo-mark {
  height: 1.05em;
  width: auto;
  display: block;
  flex-shrink: 0;
  position: relative;
  top: 0.154em; /* (1.05em - 0.742em) / 2 */
}

/* ---------- Closing airplane ---------- */
.plane-send { display: block; margin: 0 auto 48px; width: min(300px, 70vw); height: auto; }

/* ---------- Legal pages ---------- */
.legal h2 { font-size: 22px; margin: 44px 0 12px; }
.legal p { margin: 0 0 14px; color: var(--ink-soft); font-size: 16px; }
.legal p strong { color: var(--ink); }
.legal .effective { font-family: var(--sans); font-size: 13px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; }
