/* ─────────────────────────────────────────────────────────────────────────────
   Skip Pilot — components for generated pages (features, comparisons, verticals,
   blog). Loaded AFTER sp-site.v1.css so it can lean on the same tokens
   (--purple, --ink, --muted, --line, --card) rather than restating colours.

   Nothing in here overrides an existing chrome rule. New class names only, so a
   generated page and a hand-built page can sit side by side without either
   changing how the other renders.
   ───────────────────────────────────────────────────────────────────────────── */

/* index.html defines these on its own :root; sp-site.v1.css (extracted from about-us,
   which never needed them) does not. Declared here so the ported FAQ and figure rules
   resolve to the same values they do on the homepage. */
:root{
  --content:1290px;
  --purple-border:rgba(147,51,234,0.28);
  --card-hi:rgba(20,10,40,0.14);
  /* The one tinted surface on the site. Every band, panel, table header and inset
     uses this; white stays for cards that need to lift off it. Change it here and
     the whole site follows. The hand-built pages carry the literal in their own
     inline CSS, so grep for the hex if you change it. */
  --tint:#f5f2fc;
}

/* Bands are body-level, full-bleed sections. The horizontal padding repeats the
   site's own container maths (see `main` and `nav` in sp-site.v1.css) so a generated
   section lines up pixel-for-pixel with the hand-built pages above and below it. */
.band { padding:84px max(6%,calc((100% - 1290px)/2)); }
.band.tight { padding:56px max(6%,calc((100% - 1290px)/2)); }
.band.alt { background:var(--tint); border-top:1px solid var(--line); border-bottom:1px solid var(--line); }

/* Reading measure. Long-form copy stops being comfortable much past ~90 characters.
   Centred, not left-aligned: a section with no picture beside it was leaving half the
   band empty on the right, which read as a missing image rather than as a choice. */
.measure { max-width:880px; margin-left:auto; margin-right:auto; }
.measure-wide { max-width:1060px; margin-left:auto; margin-right:auto; }
/* The heading has to travel with the column it introduces, or the copy centres and the
   title above it stays stuck to the left edge. Only outside split rows, where the
   heading belongs hard against its own column. */
.band:not(.split) > .section-head { max-width:880px; margin-left:auto; margin-right:auto; }
.band:not(.split) > .section-head.centered { max-width:760px; }

/* ── Long-form typography ──────────────────────────────────────────────────
   The site's body copy is 15px/1.7. Article copy goes to 16.5px/1.8: these pages
   are read, not scanned, and the extra leading is what stops a 1,200-word page
   feeling like a wall. */
.prose { font-size:16.5px; line-height:1.8; color:var(--muted); font-weight:500; }
.prose > * + * { margin-top:22px; }
.prose h2 { font-size:clamp(1.35rem,2.4vw,1.85rem); font-weight:800; color:var(--ink);
            letter-spacing:-.3px; margin-top:52px; line-height:1.25; }
.prose h2 em { font-style:normal; color:#7c3aed; font-weight:300; }
.prose h3 { font-size:1.12rem; font-weight:800; color:var(--ink); margin-top:36px; }
.prose strong { color:var(--ink); font-weight:700; }
/* Underline inline links only. .post-row and .link-card are block components that
   happen to be anchors; underlining them (and everything nested inside them) is what
   you get if this selector is left unqualified. */
.prose a:not(.post-row):not(.link-card) { color:#7c3aed; text-decoration:underline; text-underline-offset:2px; }
.prose a:not(.post-row):not(.link-card):hover { color:#5b21b6; }
.prose .post-row, .prose .link-card { text-decoration:none; color:inherit; }
.prose ul, .prose ol { padding-left:22px; }
.prose li { margin-bottom:10px; }
.prose li::marker { color:var(--purple); }
.prose figure { margin:32px 0; }
.prose img { max-width:100%; height:auto; border-radius:14px; border:1px solid var(--line); }
.prose figcaption { font-size:13px; color:var(--faint); margin-top:10px; }

/* ── Callout ───────────────────────────────────────────────────────────────
   Used for the one thing on the page an operator should take away. Left rule
   rather than a filled box: a filled box at this width reads as an ad. */
.callout { border-left:3px solid var(--purple); background:rgba(147,51,234,.045);
           border-radius:0 12px 12px 0; padding:20px 24px; font-size:15.5px; line-height:1.75; }
.callout strong { color:var(--ink); }

/* ── Tick / cross lists ────────────────────────────────────────────────────── */
.tick-list { list-style:none; padding:0; }
.tick-list li { position:relative; padding-left:30px; margin-bottom:13px;
                font-size:15.5px; line-height:1.65; color:var(--muted); font-weight:500; }
.tick-list li::before { content:""; position:absolute; left:0; top:6px; width:17px; height:17px;
  border-radius:50%; background:rgba(34,197,94,.14);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2316a34a' stroke-width='3.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  background-size:11px 11px; background-repeat:no-repeat; background-position:center; }
.tick-list.crosses li::before { background-color:rgba(239,68,68,.12);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23dc2626' stroke-width='3.4' stroke-linecap='round'%3E%3Cline x1='18' y1='6' x2='6' y2='18'/%3E%3Cline x1='6' y1='6' x2='18' y2='18'/%3E%3C/svg%3E"); }

/* ── Comparison table ──────────────────────────────────────────────────────
   Wrapped in .cmp-scroll because a 3-column feature matrix cannot shrink below
   about 640px without the cells becoming unreadable. Scrolling the table beats
   reflowing it into an unreadable stack. */
.cmp-scroll { overflow-x:auto; -webkit-overflow-scrolling:touch; border:1px solid var(--line);
              border-radius:16px; background:#fff; }
.cmp-table { width:100%; border-collapse:collapse; min-width:640px; font-size:14.5px; }
.cmp-table th, .cmp-table td { padding:15px 18px; text-align:left; border-bottom:1px solid var(--line);
                               vertical-align:top; line-height:1.55; }
.cmp-table thead th { font-size:12px; text-transform:uppercase; letter-spacing:.6px; font-weight:800;
                      color:var(--ink); background:var(--tint); white-space:nowrap; }
.cmp-table thead th.us { color:#6d28d9; background:rgba(147,51,234,.07); }
.cmp-table tbody th { font-weight:700; color:var(--ink); width:29%; }
.cmp-table td { color:var(--muted); font-weight:500; }
.cmp-table td.us { background:rgba(147,51,234,.035); color:var(--ink); }
.cmp-table tr:last-child th, .cmp-table tr:last-child td { border-bottom:none; }
.cmp-note { font-size:12.5px; color:var(--faint); margin-top:14px; line-height:1.6; }

/* ── Feature / link cards on a grid ────────────────────────────────────────── */
/* auto-fill, not auto-fit. During a staged rollout a group can be down to one card,
   and auto-fit would stretch that single card across the full content width. */
.card-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(268px,1fr)); gap:18px; }
.link-card { display:block; text-decoration:none; border:1px solid var(--card-border); border-radius:16px;
             padding:26px; background:#fff; transition:border-color .2s, box-shadow .2s, transform .2s; }
.link-card:hover { border-color:rgba(147,51,234,.32); box-shadow:0 10px 28px rgba(20,10,40,.10);
                   transform:translateY(-3px); }
.link-card h3 { font-size:15px; font-weight:800; color:var(--ink); margin-bottom:8px; letter-spacing:-.1px; }
.link-card p { font-size:13.5px; color:var(--muted); line-height:1.6; font-weight:500; }
.link-card .lc-more { display:inline-block; margin-top:14px; font-size:12.5px; font-weight:700;
                      color:#7c3aed; text-transform:uppercase; letter-spacing:.5px; }

/* ── On-page table of contents ─────────────────────────────────────────────── */
.toc { border:1px solid var(--line); border-radius:14px; padding:22px 26px; background:var(--tint); }
.toc h2 { font-size:12px; text-transform:uppercase; letter-spacing:.7px; color:var(--faint);
          font-weight:800; margin:0 0 12px; }
.toc ol { margin:0; padding-left:20px; }
.toc li { margin-bottom:8px; font-size:14.5px; }
.toc a { color:var(--ink); text-decoration:none; font-weight:600; }
.toc a:hover { color:#7c3aed; text-decoration:underline; }

/* ── FAQ ────────────────────────────────────────────────────────────────────
   Lifted from index.html so an FAQ on a feature page behaves and looks exactly
   like the one on the homepage. Plain <details>: the answers are in the DOM for
   crawlers and for the FAQPage schema whether or not anyone opens them, and it
   needs no JavaScript.

   Two columns, using grid rather than CSS `columns`: with multi-column an opening
   <details> reflows every item after it and the thing the reader just clicked
   jumps out from under the cursor. */
.faq-list { max-width:var(--content); margin:0 auto;
  display:grid; grid-template-columns:1fr 1fr; gap:14px; align-items:start; }
@media (max-width:860px){ .faq-list { grid-template-columns:1fr; } }
.faq-item { margin-bottom:0; border:1px solid var(--card-border); border-radius:14px;
  background:var(--card); overflow:hidden; transition:border-color .18s, background .18s; }
.faq-item[open] { border-color:var(--purple-border); background:rgba(147,51,234,.04); }
.faq-item summary { cursor:pointer; padding:20px 24px; font-weight:700; font-size:16.5px;
  color:#171226; list-style:none; display:flex; align-items:center; justify-content:space-between;
  gap:16px; }
.faq-item summary::-webkit-details-marker { display:none; }
/* The question is an <h3> inside the <summary> so it lands in the outline and matches
   the FAQPage schema and support.html. */
.faq-item summary h3 { font:inherit; color:inherit; margin:0; min-width:0; }
.faq-item summary::after { content:'+'; font-size:24px; font-weight:400; color:var(--purple);
  flex-shrink:0; line-height:1; transition:transform .18s; }
.faq-item[open] summary::after { transform:rotate(45deg); }
.faq-item summary:hover { color:var(--purple); }
.faq-item summary:focus-visible { outline:2px solid var(--purple); outline-offset:-2px; }
.faq-answer { padding:0 24px 22px; color:var(--muted); font-size:15.5px; line-height:1.7; }
.faq-answer a { color:var(--purple); font-weight:600; }
@media (max-width:640px){
  .faq-item summary{ font-size:15.5px; padding:17px 18px; }
  .faq-answer{ padding:0 18px 18px; font-size:15px; }
}

/* ── Keep reading ───────────────────────────────────────────────────────────
   Centred: heading, grid and card text. The strip closes a page rather than
   continuing it, so it reads as a deliberate pause instead of one more left-aligned
   section. justify-content centres a part-full last row, which matters during the
   staged rollout when a strip may hold two cards rather than four.

   Takes an optional background photograph. Without one it falls back to the tinted
   band, so the section is never broken by a missing file. */
.related-band { background:var(--tint); border-top:1px solid var(--line);
  border-bottom:1px solid var(--line); text-align:center; }
.related-band > .section-head { max-width:640px; margin-left:auto; margin-right:auto;
  text-align:center; }
.related-band .section-sub { margin-left:auto; margin-right:auto; }

/* With a photograph behind it the band goes dark, so the type has to invert. */
.related-band.has-bg { position:relative; background-size:cover; background-position:center;
  border-color:transparent; }
.related-band.has-bg::before { content:""; position:absolute; inset:0;
  background:linear-gradient(180deg, rgba(23,18,38,.86), rgba(23,18,38,.78)); }
.related-band.has-bg > * { position:relative; z-index:1; }
.related-band.has-bg .section-label { color:#c4b5fd; }
.related-band.has-bg .section-title { color:#fff; }
.related-band.has-bg .section-title em { color:#e9d5ff; }
.related-band.has-bg .related a { background:rgba(255,255,255,.09);
  border-color:rgba(255,255,255,.18); backdrop-filter:blur(6px); }
.related-band.has-bg .related a:hover { background:rgba(255,255,255,.16);
  border-color:rgba(255,255,255,.36); }
.related-band.has-bg .rc-label { color:rgba(255,255,255,.6); }
.related-band.has-bg .rc-title { color:#fff; }
.related-band.has-bg .related a:hover .rc-title { color:#e9d5ff; }
.related-band.has-bg .rc-arrow { background:rgba(255,255,255,.16); color:#fff; }

.related { display:flex; flex-wrap:wrap; justify-content:center; gap:14px; }
.related a { flex:0 1 236px; display:flex; flex-direction:column; align-items:center;
  gap:10px; text-align:center; text-decoration:none; background:#fff;
  border:1px solid var(--card-border); border-radius:14px; padding:22px 20px;
  transition:border-color .2s, box-shadow .2s, transform .2s, background .2s; }
.related a:hover { border-color:rgba(147,51,234,.34); box-shadow:0 10px 26px rgba(20,10,40,.09);
  transform:translateY(-2px); }
.related .rc-label { display:block; font-size:10px; font-weight:800; letter-spacing:.14em;
  text-transform:uppercase; color:var(--faint); }
.related .rc-title { display:block; font-size:15px; font-weight:750; color:var(--ink);
  line-height:1.35; letter-spacing:-.1px; }
.related a:hover .rc-title { color:#7c3aed; }
.related .rc-arrow { width:26px; height:26px; border-radius:50%;
  background:rgba(147,51,234,.09); color:var(--purple); display:grid; place-items:center;
  font-size:15px; line-height:1; transition:transform .2s, background .2s; }
.related a:hover .rc-arrow { transform:translateY(2px); background:rgba(147,51,234,.18); }

/* ── Article meta ──────────────────────────────────────────────────────────── */
.article-meta { display:flex; flex-wrap:wrap; gap:8px 18px; font-size:12.5px; color:var(--faint);
                font-weight:600; margin-bottom:8px; }
.article-meta time { color:var(--faint); }

/* ── Blog index rows ───────────────────────────────────────────────────────── */
.post-row { display:block; text-decoration:none; padding:28px 0; border-bottom:1px solid var(--line); }
.post-row:hover h3 { color:#7c3aed; }
.post-row h3 { font-size:19px; font-weight:800; color:var(--ink); margin:6px 0 8px; line-height:1.35;
               transition:color .2s; }
.post-row p { font-size:14.5px; color:var(--muted); line-height:1.7; font-weight:500; }
.post-tag { font-size:11px; text-transform:uppercase; letter-spacing:.6px; font-weight:800; color:#7c3aed; }

/* ── Stat row ──────────────────────────────────────────────────────────────── */
.stat-row { display:grid; grid-template-columns:repeat(auto-fit,minmax(160px,1fr)); gap:16px; }
.stat-box { border:1px solid var(--card-border); border-radius:14px; padding:22px; background:#fff; }
.stat-box .sv { font-size:26px; font-weight:800; color:#7c3aed; letter-spacing:-.5px; }
.stat-box .sl { font-size:12.5px; color:var(--muted); margin-top:5px; line-height:1.5; font-weight:600; }

@media (max-width:680px){
  .band { padding:58px 5%; }
  .band.tight { padding:44px 5%; }
  .prose { font-size:16px; }
  .prose h2 { margin-top:40px; }
}


/* ═════════════════════════════════════════════════════════════════════════════
   LEAD FORM — lifted verbatim from index.html so a lead captured on a feature
   page behaves exactly like one captured on the homepage.

   Why this is here at all: the first cut of these pages ended with two buttons
   pointing at /contact. That is a click, a page load and a second decision
   between a warm reader and a lead, and most people do not make it. The form
   goes on the page they are already reading.

   The whole thing assumes a DARK ground (white labels, translucent fields) —
   which is what .cta-strip already is, so it drops straight in.
   ═════════════════════════════════════════════════════════════════════════════ */
  
/* ── LEAD FORM ─────────────────────────────────────────────────────────────
   16px on every input is load-bearing, not taste: below it, iOS Safari zooms
   the viewport on focus, the layout reflows, and the buyer can no longer see
   the field he is typing in. contact.html:195 still has this bug at 14px.
   Controls are 52px tall because this buyer may be wearing work gloves. */
.lead-form { max-width:none; margin:0; text-align:left;
  flex:1; display:flex; flex-direction:column; }
/* `hidden` alone does NOT hide this form: the display:flex above is an author
   rule, and author styles beat the UA sheet's [hidden]{display:none} whatever
   the specificity. Without this the confirmation appeared BELOW a still-visible
   form. Guarding .lead-done too, in case its rule set ever grows a display. */
.lead-form[hidden], .lead-done[hidden] { display:none !important; }
.lead-form .hp { position:absolute; left:-9999px; width:1px; height:1px; opacity:0; }
.lead-time { font-size:13.5px; color:rgba(255,255,255,.75); margin:0 0 20px; text-align:center; }
.lead-form label { display:block; font-size:15px; font-weight:700; color:#fff; margin-bottom:7px; }
.lead-form .opt { font-weight:500; color:rgba(255,255,255,.6); }
.lead-form input, .lead-form textarea {
  width:100%; font-family:inherit; font-size:16px; min-height:58px; padding:17px 16px;
  border:2px solid rgba(255,255,255,.30); border-radius:10px; margin-bottom:18px;
  background:rgba(255,255,255,.10); color:#fff; outline:none;
  transition:border-color .15s, background .15s; scroll-margin-block:110px; }
.lead-form textarea { min-height:120px; resize:vertical; line-height:1.5; flex:1; }
.lead-form input::placeholder, .lead-form textarea::placeholder { color:rgba(255,255,255,.55); }
.lead-form input:focus, .lead-form textarea:focus, .lead-form select:focus {
  border-color:#fff; background:rgba(255,255,255,.18); }
.lead-form input[aria-invalid="true"] { border-color:#fca5a5; }
.lead-row { display:grid; grid-template-columns:1fr 1fr; gap:0 14px; }
@media(max-width:560px){ .lead-row { grid-template-columns:1fr; } }

.lead-submit { width:100%; min-height:58px; font-size:17px; justify-content:center;
  background:#fff !important; color:#6d28d9 !important; box-shadow:0 6px 24px rgba(0,0,0,.18); }
.lead-submit:hover { background:#f3eefd !important; }
.lead-submit[disabled] { opacity:.6; cursor:progress; }
.lead-err { margin-top:12px; font-size:14px; color:#fecaca; font-weight:600; }
/* Sat almost flush against the card and the photo. It is a separate, quieter
   option, so it needs room to read as one rather than as part of the form. */
.lead-or { margin-top:44px; font-size:14px; color:rgba(255,255,255,.7); }
@media(max-width:900px){ .lead-or { margin-top:32px; } }
.lead-or a { color:#fff; font-weight:700; }

.lead-done { max-width:620px; margin:26px auto 0; text-align:left;
  background:rgba(255,255,255,.08); border:2px solid rgba(255,255,255,.2);
  border-radius:14px; padding:28px 26px; }
.lead-done .lead-tick { width:44px; height:44px; border-radius:50%; background:#fff; color:#6d28d9;
  display:grid; place-items:center; font-size:24px; font-weight:900; margin-bottom:14px; }
.lead-done h3 { font-size:22px; font-weight:800; color:#fff; margin-bottom:10px; }
.lead-done p { color:rgba(255,255,255,.85); font-size:15px; margin-bottom:12px; }
.lead-done-note { font-size:14px !important; color:rgba(255,255,255,.7) !important; }
/* Solid white, not the ghost outline: this is the only action on the screen and it
   was disappearing into the purple. */
/* On white the button has to carry the colour itself. */
/* White on the purple ground; it is the only action on that screen. */
.lead-done-call { background:#fff !important; color:#6d28d9 !important;
  border:2px solid #fff !important; font-weight:800; padding:14px 26px; }
.lead-done-call:hover { background:#f3eefd !important; }
  
  
/* The form card. Plain white on the purple ground.
   No backdrop-filter, no gradients, no gloss layer: on a flat purple background the
   blur had nothing to refract, and every attempt to fake depth with tinted overlays
   ended up colouring the panel. A clean white card reads as a form and puts the
   contrast where it belongs, on the fields. */
.lead-glass {
  position:relative; padding:30px 30px 26px;
  /* Translucent, so the photograph behind the section shows through the panel.
     The blur keeps the text legible over whatever part of the photo it lands on. */
  background:rgba(255,255,255,.13);
  border:1px solid rgba(255,255,255,.26);
  border-radius:18px;
  box-shadow:0 20px 50px -16px rgba(23,4,60,.45);
  backdrop-filter:blur(10px);
  -webkit-backdrop-filter:blur(10px);
  /* Flex column so the textarea absorbs whatever slack is left when the form column
     is shorter than the photo beside it. A fixed height would only hold until the
     image changed. */
  display:flex; flex-direction:column;
}
@supports not (backdrop-filter:blur(2px)) {
  .lead-glass { background:rgba(76,29,149,.55); }   /* readable without the blur */
}
/* The panel keeps the photograph's height (align-items:stretch on .lead-split),
   so once the tall form is replaced by the short confirmation, that message would
   sit at the top with a column of empty glass beneath it. Centre it instead, so it
   lands where the form was rather than above it. */
.lead-glass.is-done { justify-content:center; }
.lead-glass .lead-form,
.lead-glass .lead-done { margin-top:0; max-width:none; }
.lead-glass .lead-done { background:none; border:0; padding:0; }
@media(max-width:560px){ .lead-glass { padding:24px 20px 22px; border-radius:14px; } }

/* Selects styled to be indistinguishable from the text inputs: same height, same
   border, same radius, same 16px. appearance:none removes the OS chrome so the
   custom chevron below is the only affordance, otherwise you get two arrows. */
.lead-form select {
  width:100%; font-family:inherit; font-size:16px; min-height:58px;
  padding:17px 42px 17px 16px; margin-bottom:18px;
  border:2px solid rgba(255,255,255,.30); border-radius:10px;
  background:rgba(255,255,255,.10); color:#fff; outline:none; cursor:pointer;
  -webkit-appearance:none; -moz-appearance:none; appearance:none;
  background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 16px center;
  transition:border-color .15s, background-color .15s; scroll-margin-block:110px; }

/* The dropdown list itself is OS-rendered, so its options need dark-on-white or
   they are invisible on Windows and Android. */
.lead-form select option { color:#171226; background:#fff; }
.lead-form select:invalid, .lead-form select option[value=""] { color:rgba(255,255,255,.6); }

.lead-row3 { display:grid; grid-template-columns:repeat(3,1fr); gap:0 12px; }
@media(max-width:520px){ .lead-row3 { grid-template-columns:1fr; } }
  
/* ── Inner-page placement ───────────────────────────────────────────────────
   The homepage runs this form beside a photograph at full content width. On a
   feature or blog page it sits alone inside the CTA strip, so it needs its own
   measure — wider than a text column, narrow enough that the two-up rows do not
   stretch into something that reads as a government form. */
.cta-lead { max-width:620px; margin:30px auto 0; text-align:left; }
.cta-lead .lead-glass { min-height:0; }
.cta-strip .lead-or { text-align:center; margin-top:26px; }


/* ═════════════════════════════════════════════════════════════════════════════
   SCREENSHOTS AND PLACEHOLDERS

   Every image slot on a generated page is declared in the page definition with a
   filename, dimensions and alt text. At build time the generator looks for that
   file in landing/img/. If it is there you get a real <img>; if it is not you get
   the box below, printing the exact filename and pixel size the page is waiting
   for. So the site is reviewable before a single screenshot exists, and dropping
   a correctly-named file into landing/img/ is the whole of "adding the image".

   Deliberately styled as an obvious placeholder — dashed, muted, labelled. A grey
   rectangle that looks like a real design element is one that ships.
   ═════════════════════════════════════════════════════════════════════════════ */
.shot { margin:36px 0 0; }
/* No drop shadow. The border is enough to define the edge, and a shadow here would
   put the plain screenshots out of step with the panelled mockups, which have none. */
.shot img { display:block; width:100%; height:auto; border-radius:14px;
  border:1px solid var(--card-border); }
.shot figcaption { font-size:13.5px; color:var(--faint); margin-top:12px; line-height:1.55;
  max-width:640px; }

.shot-ph { position:relative; display:flex; flex-direction:column; align-items:center;
  justify-content:center; gap:7px; text-align:center; padding:34px 26px;
  border:2px dashed var(--purple-border); border-radius:14px;
  background:repeating-linear-gradient(45deg, rgba(147,51,234,.035) 0 12px,
    rgba(147,51,234,.075) 12px 24px); }
.shot-ph-tag { font-size:10.5px; font-weight:800; letter-spacing:.14em; text-transform:uppercase;
  color:var(--purple); }
.shot-ph-file { font-family:ui-monospace, SFMono-Regular, Menlo, monospace; font-size:13.5px;
  font-weight:600; color:var(--ink); word-break:break-all; }
.shot-ph-spec { font-size:12.5px; color:var(--muted); font-weight:600; }
.shot-ph-alt { font-size:12.5px; color:var(--faint); max-width:52ch; line-height:1.5; }

/* ── Blog tiles ─────────────────────────────────────────────────────────────
   Replaces the stacked text rows. A tile with an image is scannable, and it is
   what a reader arriving on a blog index expects to find. */
/* Three to a row, stated rather than derived. auto-fill gave four narrow columns on a
   wide screen and left a two-post group looking half empty; an explicit three also means
   a single published post sits at one column width instead of stretching. */
.tile-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:22px; }
@media (max-width:1000px){ .tile-grid { grid-template-columns:repeat(2,minmax(0,1fr)); } }
@media (max-width:660px){ .tile-grid { grid-template-columns:1fr; } }
.tile { display:flex; flex-direction:column; text-decoration:none; background:#fff;
  border:1px solid var(--card-border); border-radius:16px; overflow:hidden;
  transition:border-color .2s, box-shadow .2s, transform .2s; }
.tile:hover { border-color:rgba(147,51,234,.32); box-shadow:0 14px 34px rgba(20,10,40,.12);
  transform:translateY(-3px); }
.tile-media { aspect-ratio:16/10; overflow:hidden; background:var(--card); }
.tile-media img { display:block; width:100%; height:100%; object-fit:cover; }
/* The placeholder has to fill the tile's fixed media box, so it drops the outer
   radius and border the standalone version carries. */
.tile-media .shot-ph { height:100%; border:0; border-bottom:2px dashed var(--purple-border);
  border-radius:0; padding:20px 18px; gap:5px; }
.tile-media .shot-ph-alt { display:none; }
.tile-body { padding:22px 24px 26px; display:flex; flex-direction:column; gap:9px; flex:1; }
.tile-tag { font-size:10.5px; font-weight:800; letter-spacing:.13em; text-transform:uppercase;
  color:var(--purple); }
.tile h3 { font-size:18px; font-weight:800; color:var(--ink); line-height:1.32; margin:0;
  letter-spacing:-.15px; transition:color .2s; }
.tile:hover h3 { color:#7c3aed; }
.tile p { font-size:14px; color:var(--muted); line-height:1.65; font-weight:500; margin:0; }
.tile-more { margin-top:auto; padding-top:6px; font-size:12px; font-weight:800;
  letter-spacing:.5px; text-transform:uppercase; color:#7c3aed; }
.prose .tile, .prose .tile:hover { text-decoration:none; }


/* ═════════════════════════════════════════════════════════════════════════════
   SPLIT ROWS

   A section with a picture sets copy and picture side by side and alternates the
   side down the page, rather than stacking the image underneath the text. Two
   reasons: an image below a 300-word block is past the point most readers stop,
   and a page of full-width text blocks has no rhythm to it.

   `.flip` swaps the order. build-landing.mjs assigns it to every second picture
   section automatically, so the alternation cannot drift as sections are added.
   ═════════════════════════════════════════════════════════════════════════════ */
.band.split { display:grid; grid-template-columns:minmax(0,1.02fr) minmax(0,.98fr);
  gap:60px; align-items:start; }
.band.split .split-copy { min-width:0; }
.band.split .split-copy .section-head { margin-bottom:26px; }
.band.split .split-copy .prose { max-width:none; font-size:16px; }
/* Sticky, offset by the 68px fixed header plus a little air. On a section with five
   sub-headings the picture would otherwise sit marooned in the middle of a tall
   column; this keeps it beside whatever the reader is actually on. */
.band.split .split-media { min-width:0; position:sticky; top:92px; }
@media (max-width:940px){ .band.split .split-media { position:static; } }
/* ...unless the picture is the taller of the two, which is the tall-phone-mockup beside
   three short paragraphs case. Then the copy is the one worth holding in place and the
   mockup is the one worth scrolling past. split-sticky-js.html measures each row and
   sets .is-media-tall; it only ever sets it above 940px, where the row is still two
   columns. Sticking the taller column does nothing useful: anything taller than the
   viewport pins immediately and then sits with its bottom off-screen. */
.band.split.is-media-tall .split-media { position:static; }
.band.split.is-media-tall .split-copy  { position:sticky; top:92px; }
@media (max-width:940px){ .band.split.is-media-tall .split-copy { position:static; } }
.band.split .split-media .shot { margin:0; }
.band.split.flip .split-copy { order:2; }
.band.split.flip .split-media { order:1; }
/* Below 940px the two columns cannot both hold their measure, so the row stacks and
   the picture goes first: it is the faster thing to read. */
@media (max-width:940px){
  .band.split { grid-template-columns:1fr; gap:30px; }
  .band.split .split-copy, .band.split.flip .split-copy { order:2; }
  .band.split .split-media, .band.split.flip .split-media { order:1; }
}

/* ── Picture beside the trial form ──────────────────────────────────────────
   Restores the homepage's lead-split so the CTA on a feature page is the same
   composition as the one on the homepage: photograph on the left, form on the
   right, stacking to a short banner above the fields on a phone. */
.cta-split { display:grid; grid-template-columns:minmax(0,1fr) minmax(0,1fr);
  gap:32px; align-items:stretch; max-width:var(--content); margin:30px auto 0; text-align:left; }
/* Both columns are direct grid children so align-items:stretch can actually match
   them. The "would rather ring" line sits outside the grid, as it does on the
   homepage: with it inside, the form column was glass plus a line of text while the
   photo column was only the photograph, and the two could never come out level. */
.cta-split .lead-glass { margin:0; }
.cta-visual { margin:0; border-radius:18px; overflow:hidden; height:100%;
  border:1px solid rgba(255,255,255,.22); box-shadow:0 18px 50px -12px rgba(0,0,0,.35); }
.cta-visual img { display:block; width:100%; height:100%; object-fit:cover; object-position:50% 38%; }
/* On the purple ground the light placeholder disappears, so it gets its own palette. */
.cta-visual .shot-ph { height:100%; border-color:rgba(255,255,255,.42); border-radius:0;
  background:repeating-linear-gradient(45deg, rgba(255,255,255,.07) 0 12px,
    rgba(255,255,255,.14) 12px 24px); }
.cta-visual .shot-ph-tag { color:#fff; }
.cta-visual .shot-ph-file { color:#fff; }
.cta-visual .shot-ph-spec { color:rgba(255,255,255,.86); }
.cta-visual .shot-ph-alt { color:rgba(255,255,255,.72); }
/* A device mockup beside the form, rather than a photograph. It arrives on a
   transparent background with its own edges, so the frame, the crop and the fill all
   come off: contain rather than cover, centred, and no border to draw a rectangle
   around empty space. */
.cta-visual.is-bare { border:0; box-shadow:none; background:none; overflow:visible;
  display:grid; place-items:center; }
.cta-visual.is-bare img { height:560px; width:auto; max-width:100%;
  object-fit:contain; }

@media (max-width:900px){
  .cta-split { grid-template-columns:1fr; gap:18px; }
  .cta-visual { min-height:0; }
  /* NOT a 180px cover banner any more. The photograph has the app composited into it as
     a phone down the right-hand side, and a 180px centre crop of a 4:5 portrait threw
     that half away — on the device the picture is of. height:auto renders the whole
     frame at its natural ratio, which is about 490px on a phone. It pushes the fields
     down, and that is the trade: the picture is the argument for filling them in. */
  .cta-visual img { height:auto; }
  /* A bare device mockup is worth reading, so it takes most of the viewport height
     rather than shrinking to a thumbnail of the device you are holding. */
  .cta-visual.is-bare img { height:min(72vh, 620px); }
  .cta-visual .shot-ph { padding:22px 18px; }
  .cta-visual .shot-ph-alt { display:none; }
}

/* ── Bare images ────────────────────────────────────────────────────────────
   A device mockup arrives on a transparent background with its own rounded edges
   and shadow already drawn, so it gets no border, radius or shadow of its own:
   those would draw a rectangle around empty space.

   Instead it sits on a soft purple panel. The mockup's own screen is mostly white
   and pale map, and on the page's white ground it was floating with nothing to hold
   it. The panel gives it an edge to sit against and picks up the brand colour
   without competing with the copy beside it.

   Height-capped rather than width-capped: an 852x1846 phone filling a 600px column
   would run to 1300px tall and swamp the text next to it. */
.shot-bare .shot-panel {
  background:var(--tint);
  border:1px solid rgba(147,51,234,.12);
  border-radius:26px;
  padding:34px 26px 26px;
  display:grid; place-items:center;
  position:relative; overflow:hidden;
}
/* The magnet grid from the banner, on the panel behind the mockup. Absolutely
   positioned so it does not become a second grid item and fight the centring, and
   pointer-events:none so it never eats a click. */
.shot-panel .sp-grid { position:absolute; inset:0; width:100%; height:100%;
  pointer-events:none; }
.shot-panel > img { position:relative; z-index:1; }
/* A DEFINITE height, not max-height. With height:auto the browser has no intrinsic
   size for a lazy-loaded image until it arrives, so the column collapsed to the height
   of the caption, the sticky media became a 140px stub with 300px of travel, and the
   whole row lurched as you scrolled. A fixed height reserves the space immediately and
   object-fit keeps the mockup undistorted. */
.shot-bare img { border:0; border-radius:0; box-shadow:none;
  height:720px; width:auto; max-width:100%; object-fit:contain;
  margin:0 auto; display:block; }

/* Caption reads as a label for the shot rather than a footnote, so it is dark and
   bold. The default figcaption grey disappeared under the panel. */
.shot-bare figcaption { text-align:center; max-width:440px; margin:16px auto 0;
  color:var(--ink); font-weight:650; font-size:14px; line-height:1.55; }

@media (max-width:940px){
  .shot-bare .shot-panel { padding:26px 18px 20px; border-radius:20px; }
  .shot-bare img { max-height:520px; }
}
