/* ScopeLock homepage skin.
 *
 * Loaded ONLY by index.html, after styles.css. It deliberately does not touch styles.css:
 * that sheet is also served to approval.html, 404.html and every server-rendered policy
 * page, so a new palette or type scale in there would silently restyle the customer-facing
 * approval view and the legal pages.
 *
 * Every rule here targets an `sl-`prefixed class, or a class that exists nowhere in
 * index.html except inside the marketing sections (verified: .price-card and friends occur
 * only between the pricing section's open and close tags). The signed-in workspace, the
 * quick-variation dialog and the settings panels are therefore untouched and keep the
 * existing design system.
 *
 * No inline styles anywhere, by necessity as well as preference: the server sends
 * `style-src 'self'` with no 'unsafe-inline', so a style attribute would be blocked. The
 * animation work in home.js drives state classes and CSS custom properties through the
 * CSSOM, which CSP permits.
 */

/* ────────────────────────────────────────────────────── the base layer, copied ──
 * The marketing page does not load styles.css (135KB for a handful of rules this page
 * actually needs). These are the ones it depends on: the box-sizing reset, the scroll
 * and overflow-clip behaviour on html/body, the button/link resets (the same dependency
 * the .sl-frame-action-send comment further down names), the img max-width guard, the
 * h1/h2/h3/p and h1 type rules, dialog's base chrome, .visually-hidden, .site-header,
 * .button-small, .price-amount, and — added when <scope-lock-footer> shipped without a
 * shadow root — the site footer it renders (.site-footer, .footer-brand, .footer-links,
 * .footer-support, .footer-boundary, .footer-bottom, .brand, .brand-mark) plus the design
 * tokens those rules read.
 *
 * COPIED VERBATIM, byte-for-byte, from styles.css — not moved. styles.css keeps its own
 * copy because /app, 404.html, invite.html and approval.html still load it and still need
 * these rules; tests/stylesheet-integrity.test.mjs asserts the two copies stay identical.
 * index.html no longer links styles.css at all (see the note there), so this copy is not a
 * no-op sitting behind an identical cascade — it is the ONLY source of these rules on the
 * marketing page.
 *
 * The footer rules read custom properties that styles.css defines on `:root` — --ink,
 * --paper, --steel, --rule, --max, --lime — plus the Inter font stack, also on `:root`
 * there. None of that root block is otherwise needed here (brand.css supplies this page's
 * own --sl-* palette and fonts for every `sl-`prefixed rule), so rather than import the
 * whole thing this file declares only the subset the copied rules actually read. Skipping
 * it is not an option: a var() that resolves to nothing is invalid at computed-value time,
 * which drops the whole declaration (a `border-top: 1px solid var(--rule)` with no --rule
 * renders no border at all, not a black one) — so the footer would still come out visibly
 * broken even with every selector below correctly in place. This subset is not itself a
 * byte-identical copy of styles.css's `:root` (it is deliberately partial), so it is not
 * one of the pinned selectors in stylesheet-integrity.test.mjs; the rules that read it
 * are.
 *
 * `color-scheme: only light` is here for a different reason than the rest, and it is not
 * about the footer. It was on this page until styles.css stopped loading, and the note
 * beside it in that file is explicit that the <meta name="color-scheme"> tag in the HTML
 * does NOT stand in for the CSS property: the meta alone still permits Chrome for
 * Android's "Auto dark theme" to algorithmically invert the page, which is what turned the
 * workspace olive-on-brown. The landing page has been shipping without it since the split,
 * so it comes back with the rest of what that document lost. */

:root {
  color-scheme: only light;
  --ink: #102019;
  --paper: #f7f8f4;
  --steel: #526158;
  --rule: #d7ded7;
  --lime: #c7f45b;
  --max: 1248px;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}

* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: 24px;
  overflow-x: clip;
}

body {
  margin: 0;
  overflow-x: clip;
  background: var(--paper);
  color: var(--ink);
  font-size: 16px;
  line-height: 1.5;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

button,
input,
textarea,
select {
  font: inherit;
}

button,
a {
  -webkit-tap-highlight-color: transparent;
}

a,
button {
  color: inherit;
}

button {
  cursor: pointer;
}

img {
  max-width: 100%;
}

h1,
h2,
h3,
p {
  text-wrap: pretty;
}

h1 {
  margin: 0;
  font-size: clamp(50px, 4.8vw, 68px);
  font-weight: 820;
  letter-spacing: -0.055em;
  line-height: 0.99;
}

dialog {
  width: min(720px, calc(100% - 28px));
  max-height: calc(100dvh - 28px);
  overflow: auto;
  border: 1px solid #738078;
  border-radius: 16px;
  background: var(--paper);
  box-shadow: 0 28px 90px rgba(8, 18, 13, 0.32);
  color: var(--ink);
  padding: 0;
}

.button-small {
  min-height: 44px;
  border-radius: 9px;
  padding-inline: 18px;
  gap: 8px;
}

.price-card .price-amount {
  font-variant-numeric: tabular-nums lining-nums;
  letter-spacing: -0.03em;
}

.site-header {
  position: relative;
  z-index: 5;
  display: flex;
  min-height: 78px;
  align-items: center;
  gap: 28px;
  border-bottom: 1px solid var(--rule);
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 11px;
  color: var(--ink);
  font-size: 22px;
  font-weight: 850;
  letter-spacing: -0.045em;
  text-decoration: none;
  white-space: nowrap;
}

.brand-mark {
  position: relative;
  display: inline-grid;
  width: 30px;
  height: 30px;
  place-items: center;
  border: 4px solid var(--ink);
  border-bottom-color: var(--lime);
  border-left-color: var(--lime);
}

.brand-mark span {
  width: 10px;
  height: 6px;
  border-bottom: 3px solid var(--ink);
  border-left: 3px solid var(--ink);
  transform: rotate(-45deg) translateY(-1px);
}

.site-header nav a,
.site-footer nav a,
.footer-bottom a {
  text-decoration: none;
}

.site-footer {
  display: grid;
  grid-template-columns: 1.15fr 0.65fr 1fr;
  gap: 56px;
  padding: 56px 0 30px;
  border-top: 1px solid var(--rule);
}

/* Not a byte-identical copy: in styles.css this width/margin-inline behaviour comes from
 * a rule shared with six other selectors (.site-header, .hero, .steps, .workspace, .faq,
 * .pricing, .site-footer — styles.css:141-150), none of which exist in index.html's
 * markup except .site-header, which is overridden by .sl-header's own width: 100% further
 * down this file. Copying that six-selector group verbatim would add five dead selectors
 * this page never renders; this rule is just the .site-footer share of it, extracted, so
 * it is deliberately not one of the pinned selectors in stylesheet-integrity.test.mjs — the
 * .site-footer rule above it is.
 *
 * The old min(…)-box is gone: it centred the footer at 1248px while every section above
 * sat on brand.css's 1312px gutter model, so the footer's edges lined up with nothing.
 * chrome.css owns the canonical rule now (every public page loads it); this copy keeps
 * app.html — which loads this sheet but not chrome.css — on the same formula, and keeps
 * neutralising styles.css's six-selector box above, which /app still receives. */
.site-footer {
  width: 100%;
  margin-inline: 0;
  padding-inline: max(var(--sl-gutter), calc((100% - var(--sl-max)) / 2));
}

.footer-brand p,
.footer-boundary p,
.footer-support p {
  max-width: 390px;
  margin: 16px 0 0;
  color: var(--steel);
  line-height: 1.6;
}

/* Business identity block. Inherits colour, line-height and margin from .footer-brand p
   above so it reads as quiet secondary text — a trust signal, not a headline. */
.footer-brand .footer-operator {
  font-size: 13px;
}

.site-footer nav {
  display: grid;
  align-content: start;
  gap: 10px;
}

.footer-links > div {
  display: grid;
  gap: 10px;
}

.site-footer nav strong,
.footer-boundary strong,
.footer-support strong {
  margin-bottom: 4px;
  font-size: 13px;
}

.site-footer nav a {
  width: fit-content;
  color: var(--steel);
}

.site-footer nav a:hover,
.footer-bottom a:hover,
.footer-support a:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 4px;
}

.footer-support p {
  margin-top: 10px;
}

.footer-support a {
  color: var(--ink);
  font-weight: 750;
  overflow-wrap: anywhere;
}

.footer-boundary {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: minmax(150px, 0.35fr) minmax(0, 1.65fr);
  gap: 24px;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
}

.footer-boundary p {
  max-width: 820px;
  margin-top: 0;
}

.footer-bottom {
  display: flex;
  grid-column: 1 / -1;
  justify-content: space-between;
  padding-top: 24px;
  border-top: 1px solid var(--rule);
  color: var(--steel);
  font-size: 13px;
}

/* Footer & brand responsive tweaks — copied from styles.css's @media (max-width: 820px),
 * @media (max-width: 760px) and @media (max-width: 390px) blocks (styles.css:5121,
 * :5152 and :5742). Each is its own selector nested inside indentation, so none of these
 * satisfy extractRule's "selector immediately after a blank line" match in
 * stylesheet-integrity.test.mjs — they are not, and cannot be, in its pinned array. */
@media (max-width: 820px) {
  .site-footer {
    grid-template-columns: 1fr 1fr;
  }

  .footer-brand {
    grid-column: 1 / -1;
  }

  .footer-boundary,
  .footer-bottom {
    grid-column: 1 / -1;
  }
}

@media (max-width: 760px) {
  .site-footer {
    width: min(calc(100% - 28px), var(--max));
  }

  .footer-boundary {
    grid-template-columns: 1fr;
    gap: 8px;
  }

  .brand {
    font-size: 20px;
  }

  .brand-mark {
    width: 27px;
    height: 27px;
  }

  .site-footer {
    grid-template-columns: 1fr;
    gap: 32px;
    padding-top: 40px;
  }

  .footer-brand,
  .footer-bottom {
    grid-column: auto;
  }

  .footer-bottom {
    align-items: flex-start;
    flex-direction: column;
    gap: 10px;
  }
}

@media (max-width: 390px) {
  .brand {
    gap: 8px;
    font-size: 18px;
  }
}

/* Fonts and design tokens live in brand.css, which index.html loads before this sheet.
 * They are shared with approval.css and legal.css; duplicating @font-face and the palette
 * into each surface's stylesheet is how three surfaces drift apart. */
/* The page frame. `main` and the marketing sections carry their own full-bleed background
 * colours, so the body colour only shows behind the header and the workspace. */
body.sl-page {
  background: var(--sl-paper);
  color: var(--sl-ink);
}

/* ----------------------------------------------------------- primitives -- */
.sl-section {
  padding-block: var(--sl-section-y);
}

.sl-section-dark {
  background: var(--sl-ink);
  color: var(--sl-paper);
}

.sl-section-paper {
  background: var(--sl-paper);
}

.sl-section-alt {
  background: var(--sl-paper-alt);
  border-top: 1px solid var(--sl-rule);
  border-bottom: 1px solid var(--sl-rule);
}

/* Numbered section index — "01 — What it actually costs". */
.sl-index {
  margin: 0;
  font: 500 11px/1 var(--sl-mono-font);
  letter-spacing: 0.18em;
  color: var(--sl-ink-4);
  text-transform: uppercase;
}

.sl-section-dark .sl-index {
  color: var(--sl-yellow);
}

.sl-h2 {
  margin: 26px 0 0;
  font: 800 clamp(34px, 4.4vw, 62px) / 1.05 var(--sl-display-font);
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.sl-h2-tight {
  font-size: clamp(32px, 3.9vw, 54px);
}

.sl-lede {
  margin: 26px 0 0;
  max-width: 640px;
  font: 400 clamp(17px, 1.4vw, 20px) / 1.6 var(--sl-display-font);
  color: var(--sl-ink-2);
  text-wrap: pretty;
}

.sl-section-dark .sl-lede {
  color: var(--sl-dark-mute);
}

/* Handwritten margin notes. Decorative voice, never load-bearing information — every one
 * of them is aria-hidden in the markup. */
.sl-scrawl {
  margin: 0;
  font: 600 22px/1.3 var(--sl-hand-font);
  color: var(--sl-red);
  transform: rotate(-2deg);
}

.sl-mono-label {
  display: block;
  font: 500 10px/1 var(--sl-mono-font);
  letter-spacing: 0.16em;
  color: var(--sl-ink-5);
  text-transform: uppercase;
}

/* The hard-edged black button with a yellow offset block behind it — the design's primary
 * action treatment. Layered over styles.css's .button-primary rather than replacing it, so
 * the shared focus ring and disabled handling still apply. */
.sl-button {
  border-radius: 0;
  border: 0;
  background: var(--sl-ink);
  color: var(--sl-paper);
  font: 700 16px/1 var(--sl-display-font);
  padding: 20px 28px;
  box-shadow: 5px 5px 0 var(--sl-yellow);
  transition: transform 0.14s ease, box-shadow 0.14s ease;
}

.sl-button:hover:not(:disabled) {
  transform: translate(2px, 2px);
  box-shadow: 3px 3px 0 var(--sl-yellow);
  background: var(--sl-ink);
  color: var(--sl-paper);
}

.sl-button:active:not(:disabled) {
  transform: translate(5px, 5px);
  box-shadow: 0 0 0 var(--sl-yellow);
}

.sl-button-small {
  padding: 14px 18px;
  font-size: 14px;
  box-shadow: 4px 4px 0 var(--sl-yellow);
}

/* Inverted for the yellow closing panel. */
.sl-button-on-yellow {
  color: var(--sl-yellow);
  box-shadow: none;
}

.sl-button-on-yellow:hover:not(:disabled) {
  color: var(--sl-yellow);
  box-shadow: none;
  transform: translate(2px, 2px);
}

/* --------------------------------------------------------------- header -- */
.sl-header {
  width: 100%;
  margin-inline: 0;
  min-height: 0;
  padding-block: 14px;
  gap: 16px;
  background: var(--sl-paper);
  border-bottom: 1px solid var(--sl-rule);
  justify-content: space-between;
}

.sl-header .brand {
  gap: 10px;
  font: 800 20px/1 var(--sl-display-font);
  letter-spacing: -0.02em;
  color: var(--sl-ink);
}

.sl-brand-mark {
  display: block;
  width: 26px;
  height: 26px;
}

.sl-brand-tag {
  font: 500 10px/1 var(--sl-mono-font);
  letter-spacing: 0.14em;
  color: var(--sl-ink-4);
  border: 1px solid var(--sl-rule-strong);
  padding: 3px 5px;
}

/* Marketing nav between the mark and the sign-in action. Desktop-only: under 780px
 * these six links would push Sign in off screen, and every destination is also in the
 * footer, so hiding them loses no reachability on a phone. */
.sl-header-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.sl-header-nav a {
  color: var(--sl-ink-3);
  text-decoration: none;
  font: 500 14px/1 var(--sl-display-font);
  padding: 12px 10px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
}

.sl-header-nav a:hover {
  color: var(--sl-ink);
  text-decoration: underline;
  text-underline-offset: 4px;
}

@media (max-width: 779px) {
  .sl-header-nav {
    display: none;
  }
}

.sl-header-actions {
  display: flex;
  align-items: center;
  gap: 4px;
}

/* Sign in stays the quiet action. Start free is the loud one. */
.sl-header-signin {
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--sl-ink-3);
  font: 500 14px/1 var(--sl-display-font);
  padding: 12px 14px;
  min-height: 44px;
}

.sl-header-signin:hover {
  color: var(--sl-ink);
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* The header's one call to action. Same offset-shadow treatment as the hero button so it
 * reads as the same control, at header scale. */
.sl-header-cta {
  border: 0;
  border-radius: 0;
  background: var(--sl-ink);
  color: var(--sl-paper);
  font: 700 14px/1 var(--sl-display-font);
  padding: 12px 16px;
  min-height: 44px;
  box-shadow: 3px 3px 0 var(--sl-yellow);
  transition: transform 0.12s ease, box-shadow 0.12s ease;
}

.sl-header-cta:hover:not(:disabled) {
  transform: translate(2px, 2px);
  box-shadow: 1px 1px 0 var(--sl-yellow);
}

/* Under 400px the brand mark, Sign in and Start free stop fitting on one line together.
 * Start free is the one that has to survive, so Sign in folds to a link-sized target and
 * the padding comes off both. */
@media (max-width: 400px) {
  /* Trimming the padding was not enough on its own. `.brand` is nowrap, so at 320px it
   * held its 182px min-content width inside a 156px flex track and the AU chip printed
   * straight through the Sign in label. The chip is the only part of the header that is
   * decorative — it is aria-hidden, and the wordmark beside it already says the brand — so
   * it is what gives way, which buys back the ~32px the row was short. */
  .sl-brand-tag {
    display: none;
  }

  .sl-header-signin {
    padding-inline: 6px;
  }

  .sl-header-cta {
    padding-inline: 12px;
    box-shadow: 2px 2px 0 var(--sl-yellow);
  }
}

/* The workspace section now lives only in public/app.html — the signed-in app shell —
 * home.css still owns its styling because roughly a third of this sheet dresses the
 * signed-in UI, not the landing page (see the comment on app.html's stylesheet link).
 *
 * On /app a signed-out arrival (expired session, a forwarded billing link) must still see
 * something: the workspace's own signed-out block ("Variation register" / "Create an
 * account...") is exactly that fallback, so it must render, not hide. `:not(.sl-app-shell)`
 * scopes the old blanket hide to a document that no longer exists — public/app.html's
 * <body> carries `sl-app-shell` precisely so this rule never fires there. */
body:not(.sl-app-shell) .workspace.is-signed-out {
  display: none;
}

/* ----------------------------------------------------------------- hero -- */
.sl-hero {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: clamp(36px, 5vw, 72px);
  max-width: var(--sl-max);
  margin-inline: auto;
  padding: clamp(32px, 6vw, 84px) clamp(20px, 5vw, 64px) clamp(48px, 7vw, 96px);
  background: var(--sl-paper);
}

/* Centred, not left-parked. This is the hero's only column — the second one held the
 * composer's live document and went with it — so without the auto margins the headline and
 * the demo phone would sit hard against the left edge of a 1440px viewport with the rest of
 * the band empty. */
.sl-hero-copy {
  flex: 1 1 320px;
  min-width: 0;
  max-width: 620px;
  margin-inline: auto;
}

/* Centred, like everything else sharing this column: the phone figure and the demo CTA
 * below it are both centred (see their own comments), so a left-anchored h1 was the one
 * thing in the band not sitting on that axis. text-wrap: balance already evens out the
 * ragged line lengths that centring makes visible. */
#hero-title {
  margin: 0;
  font: 800 clamp(38px, 9vw, 72px) / 1.02 var(--sl-display-font);
  letter-spacing: -0.03em;
  text-wrap: balance;
  text-align: center;
  color: var(--sl-ink);
}

.sl-cta-row {
  display: flex;
  margin-top: 28px;
}

.sl-hero .sl-button {
  width: 100%;
  max-width: 360px;
  justify-content: center;
  gap: 12px;
  font-size: 17px;
  padding: 20px 28px;
}

/* ---------------------------------------------------------------- proof strip --
 * The band of figures under the hero CTA (see proof-strip.js). Every rule here is written
 * against markup that DOES NOT EXIST YET: the component ships with a null config and renders
 * nothing, so none of this is on screen until real numbers are published.
 *
 * Styled anyway, and deliberately, because the alternative is discovering the layout for the
 * first time on the day somebody fills in a number — which is the day it is least convenient
 * to find out that three figures do not fit across a 375px screen.
 *
 * Wraps rather than scrolls. One, two or three items all have to look intentional: the count
 * is whatever the config resolves to, not a fixed three, so a fixed grid would leave a hole
 * the moment only the signed count is known. */
.sl-proofbar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
}

.sl-proofbar-item {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* The figure carries the mono face for the same reason the licence number does: it is data,
 * and it is the part a sceptical reader stops on. */
.sl-proofbar-figure {
  font: 700 clamp(20px, 5.4vw, 24px) / 1.1 var(--sl-mono-font);
  letter-spacing: -0.01em;
  color: var(--sl-ink);
  font-variant-numeric: tabular-nums;
}

.sl-proofbar-note {
  max-width: 22ch;
  font: 500 12.5px/1.35 var(--sl-display-font);
  color: var(--sl-ink-3);
  text-wrap: pretty;
}

/* The tracked CTA sits inside the figure but outside the shell (see the markup comment
 * above the figure in index.html) — a page control below the device now, not chrome
 * flush against the bottom edge of a card. Cleared of the shell with margin-top instead
 * of the border-top this used to butt against, and still full-width to match the phone's
 * own footprint so the pair reads as one column. Same button language as the other
 * primary CTAs on the page — ink fill, the arrow glyph — rather than the drawn frame
 * styling the mockup used, which is being removed with it. */
.sl-hero-demo-cta {
  width: 100%;
  margin-top: 20px;
  border: 0;
  border-radius: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 16px 12px;
  font: 700 15px/1 var(--sl-display-font);
  color: var(--sl-paper);
  background: var(--sl-ink);
}

.sl-hero-demo-cta:focus-visible {
  outline: 3px solid var(--sl-yellow);
  outline-offset: -3px;
}

/* The email-confirmation banner, and all that is left of the hero variation flow's
 * stylesheet. The composer, its live document, the open-state hero collapse and the visual
 * column that carried it were removed with the markup; this stayed because a confirmation
 * link already sitting in somebody's inbox still renders into #hero-email-confirm. */
.sl-qs-confirm {
  margin: 0 0 20px;
  padding: 14px 18px;
  border-radius: 4px;
  border: 1px solid var(--sl-rule-strong);
  background: var(--sl-card);
  font: 500 15px/1.5 var(--sl-display-font);
  color: var(--sl-ink-2);
}

.sl-qs-confirm-ok {
  border-color: var(--sl-green);
  color: var(--sl-green);
}

.sl-qs-confirm-bad {
  border-color: var(--sl-red);
  color: var(--sl-red);
}

/* ------------------------------------------------------ the hero, on a desk --
 * Everything above is the phone layout, and it is also what shipped to desktops: headline,
 * phone, button in one centred stack, which on a wide monitor reads as a blown-up mobile
 * page with two thirds of the screen standing empty. From 1020px the same children split
 * into two columns — the words and the action on the left, the drawing on the right.
 * Layout only: the markup, and with it the reading order, the sticky-CTA observer's
 * selector and every landing-section assertion, is untouched.
 *
 * The phone spans the three left-hand rows, so the headline (align-self: end) and the CTA
 * (align-self: start) meet near its vertical middle without any magic offsets. The proof
 * strip ships empty and contributes nothing until real figures exist; the confirm banner
 * is last in the markup and takes a full-width row of its own on the rare day it shows. */
@media (min-width: 1020px) {
  .sl-hero-copy {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      "title   phone"
      "cta     phone"
      "proof   phone"
      "confirm confirm";
    column-gap: clamp(64px, 8vw, 140px);
  }

  #hero-title {
    grid-area: title;
    align-self: end;
    text-align: left;
  }

  .sl-hero-copy .sl-phone {
    grid-area: phone;
    margin: 0;
    align-self: center;
  }

  /* A button again, not a bar: full-width matched the phone's footprint when the pair
   * shared a column, but stretched across a 700px text column it reads as a banner. */
  .sl-hero-demo-cta {
    grid-area: cta;
    align-self: start;
    justify-self: start;
    width: auto;
    min-width: 340px;
    padding-inline: 32px;
    margin-top: 36px;
  }

  .sl-hero-copy scope-lock-proof-strip {
    grid-area: proof;
    align-self: start;
  }

  .sl-hero-copy .sl-qs-confirm {
    grid-area: confirm;
    margin: 32px 0 0;
  }
}


/* ---------------------------------------------------------- the phone --
 * Three frames stacked in one screen, cross-faded on a 7.5s loop: type it, they sign, it is
 * on record. Entirely CSS keyframes — there is no state to keep, so there is no reason for
 * this to wait on a script, and nothing here can fail at runtime.
 *
 * Every animation is decorative and every one is switched off under prefers-reduced-motion
 * at the foot of this file, where the frames resolve to the finished record.
 */
/* Centred under the headline rather than parked in a column beside it: the second hero
 * column went with the composer, so this is the only thing in the band. Same 310px the
 * drawing was always built at — every inside measurement, including .sl-phone-screen's fixed
 * 474px height, is tuned to it. */
.sl-phone {
  margin: 32px auto 0;
  width: min(310px, 88vw);
}

.sl-phone-shell {
  background: var(--sl-ink);
  border-radius: 40px;
  padding: 10px;
  box-shadow: 0 28px 60px rgba(23, 21, 15, 0.18);
}

.sl-phone-screen {
  position: relative;
  background: var(--sl-paper);
  border-radius: 31px;
  height: 474px;
  overflow: hidden;
}

.sl-frame {
  position: absolute;
  inset: 0;
  padding: 22px 18px;
  display: flex;
  flex-direction: column;
  gap: 14px;
  opacity: 0;
}

.sl-frame-a { animation: sl-frame-a 7.5s linear infinite; }

/* Frame A's decorative content, wrapped so the aria-hidden can sit on it rather than on
 * the whole figure — the send action inside frame A is a real button now and must stay
 * nameable. The wrapper re-creates the column and the 14px gap the frame used to apply to
 * these children directly, and takes the free space so the button keeps its margin-top
 * auto position at the foot of the screen. */
.sl-frame-body {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
}
.sl-frame-b { animation: sl-frame-b 7.5s linear infinite; gap: 13px; }
.sl-frame-c { animation: sl-frame-c 7.5s linear infinite; padding: 0; gap: 0; }

@keyframes sl-frame-a {
  0%, 34% { opacity: 1; }
  38%, 96% { opacity: 0; }
  100% { opacity: 1; }
}

@keyframes sl-frame-b {
  0%, 34% { opacity: 0; }
  38%, 62% { opacity: 1; }
  66%, 100% { opacity: 0; }
}

@keyframes sl-frame-c {
  0%, 62% { opacity: 0; }
  66%, 96% { opacity: 1; }
  100% { opacity: 0; }
}

.sl-frame p {
  margin: 0;
}

.sl-frame-kicker {
  font: 700 10px/1 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

.sl-frame-label {
  display: block;
  font: 400 9px/1 var(--sl-mono-font);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sl-ink-5);
}

.sl-frame-job {
  margin-top: 5px;
  font: 700 16px/1.2 var(--sl-display-font);
  letter-spacing: -0.01em;
  color: var(--sl-ink);
}

.sl-frame-site {
  margin-top: 3px;
  font: 400 12px/1.3 var(--sl-display-font);
  color: var(--sl-ink-4);
}

.sl-frame-scope {
  margin-top: 4px;
  border-bottom: 1px dashed var(--sl-rule-strong);
  padding-bottom: 7px;
  min-height: 36px;
}

/* The scope line types itself by revealing width, not by rewriting text — no script, and
 * the string is present in the DOM the whole time. */
.sl-typed {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  vertical-align: bottom;
  max-width: 210px;
  font: 600 22px/1.2 var(--sl-hand-font);
  color: var(--sl-pen);
  animation: sl-type 7.5s linear infinite;
}

@keyframes sl-type {
  0%, 5% { max-width: 0; }
  24%, 100% { max-width: 210px; }
}

.sl-caret-wrap {
  display: inline-block;
  opacity: 0;
  animation: sl-caret-show 7.5s linear infinite;
}

@keyframes sl-caret-show {
  0% { opacity: 0; }
  4%, 28% { opacity: 1; }
  30%, 100% { opacity: 0; }
}

.sl-caret {
  display: inline-block;
  font: 400 22px/1.2 var(--sl-hand-font);
  color: var(--sl-pen);
  animation: sl-blink 1s steps(1) infinite;
}

.sl-frame-price {
  animation: sl-price 7.5s linear infinite;
}

@keyframes sl-price {
  0%, 24% { opacity: 0; }
  28%, 100% { opacity: 1; }
}

.sl-frame-amount {
  margin-top: 4px;
  font: 800 28px/1 var(--sl-display-font);
  letter-spacing: -0.02em;
  color: var(--sl-ink);
}

.sl-frame-amount-inline {
  font-size: 24px;
  margin-top: 8px;
}

.sl-frame-amount-inline span {
  font: 400 12px/1 var(--sl-display-font);
  color: var(--sl-ink-4);
}

.sl-frame-gst {
  font: 400 11px/1.3 var(--sl-display-font);
  color: var(--sl-ink-4);
}

.sl-frame-action {
  margin-top: auto;
  text-align: center;
  font: 700 15px/1 var(--sl-display-font);
  padding: 16px 12px;
  color: var(--sl-paper);
}

/* The one live control in the demo. It is a <button>, so it needs the resets styles.css
 * gives the rest of the page's buttons plus the drawn look it inherited as a <p>.
 *
 * `sl-send-live` is what makes it safe: frame A is at opacity 0 for two-thirds of the
 * loop, and an invisible button is still clickable and still in the tab order.
 * visibility:hidden takes it out of both, in step with the fade. */
.sl-frame-action-send {
  width: 100%;
  border: 0;
  border-radius: 0;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--sl-ink);
  box-shadow: 4px 4px 0 var(--sl-yellow);
  animation: sl-press-send 7.5s linear infinite, sl-send-live 7.5s linear infinite;
}

@keyframes sl-send-live {
  0%, 36% { visibility: visible; }
  38%, 97% { visibility: hidden; }
  99%, 100% { visibility: visible; }
}

.sl-frame-action-send:focus-visible {
  outline: 3px solid var(--sl-yellow);
  outline-offset: 3px;
}

/* The button visibly depresses at the moment the variation is sent. */
@keyframes sl-press-send {
  0%, 29% { transform: none; box-shadow: 4px 4px 0 var(--sl-yellow); }
  30.5% { transform: translate(3px, 3px); box-shadow: 0 0 0 var(--sl-yellow); }
  32%, 100% { transform: none; box-shadow: 4px 4px 0 var(--sl-yellow); }
}

.sl-frame-action-approve {
  /* Ink, not green: the real /v/:token Approve button is ink-on-cream with
     a yellow drop-shadow (approval.css), never a green fill. */
  background: var(--sl-ink);
  box-shadow: 3px 3px 0 var(--sl-yellow);
  animation: sl-press-approve 7.5s linear infinite;
}

@keyframes sl-press-approve {
  0%, 59% { transform: none; }
  60.5% { transform: scale(0.97); }
  62%, 100% { transform: none; }
}

.sl-frame-divider {
  border-top: 1px solid var(--sl-rule);
  padding-top: 10px;
}

.sl-frame-hand {
  margin-top: 3px;
  font: 600 22px/1.2 var(--sl-hand-font);
  color: var(--sl-pen);
}

.sl-signpad {
  margin-top: 6px;
  border: 1px dashed var(--sl-rule-strong);
  background: var(--sl-card);
  height: 72px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
}

.sl-signpad .sl-frame-job,
.sl-signpad .sl-frame-site {
  margin-top: 0;
}

.sl-sign-ink,
.sl-sign-pen {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
}

.sl-sign-ink { stroke: var(--sl-ink); }
.sl-sign-pen { stroke: var(--sl-pen); }

/* ---- frame three: the finished record ---- */
.sl-record {
  margin: 16px 14px 0;
  background: var(--sl-card);
  border: 1px solid var(--sl-rule);
  flex: 1;
  display: flex;
  flex-direction: column;
  position: relative;
  overflow: hidden;
}

.sl-record-rail {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  background: var(--sl-ink);
  color: var(--sl-paper);
  padding: 11px 14px;
  font: 700 9px/1 var(--sl-mono-font);
  letter-spacing: 0.14em;
}

.sl-record-state {
  color: var(--sl-yellow);
  font-weight: 500;
}

.sl-record-head { padding: 14px 16px 0; }
.sl-record-head .sl-frame-job { margin-top: 0; }
.sl-record-body { padding: 10px 16px 0; }

.sl-record-signatures {
  margin-top: auto;
  padding: 0 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.sl-record-sig-first {
  border-top: 1px solid var(--sl-rule);
  padding-top: 9px;
}

.sl-record-caption {
  font: 400 8px/1.4 var(--sl-mono-font);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sl-ink-5);
}

.sl-record-stamp {
  position: absolute;
  right: 14px;
  top: 118px;
  margin: 0;
  border: 3px solid var(--sl-green);
  color: var(--sl-green);
  font: 700 15px/1 var(--sl-mono-font);
  letter-spacing: 0.18em;
  padding: 7px 10px;
  background: rgba(255, 253, 248, 0.85);
  transform: rotate(-13deg);
  animation: sl-stamp 7.5s linear infinite;
}

/* The entry scale is capped at 1.75 rather than 2.3 because a transform inflates the
 * element's border box even while it is fully transparent: at 2.3 the stamp measured 286px
 * wide against a 320px viewport and reported as an off-viewport element to any overflow
 * audit. `.sl-record` clips it, so it never actually panned the page — but "a parent happens
 * to clip it" is not something the next change to this card should have to know. The frames
 * that carry the scale are at opacity 0, so the slam reads the same. */
@keyframes sl-stamp {
  0%, 67% { opacity: 0; transform: rotate(-13deg) scale(1.75); }
  70.5%, 100% { opacity: 1; transform: rotate(-13deg) scale(1); }
}

.sl-record-foot {
  text-align: center;
  font: 400 12px/1.4 var(--sl-display-font);
  color: var(--sl-ink-3);
  padding: 10px 12px 14px;
  margin: 0;
}

/* ---- the three step labels under the phone ---- */
.sl-phone-steps {
  margin-top: 14px;
  display: flex;
  justify-content: center;
  gap: 18px;
  font: 700 10px/1 var(--sl-mono-font);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

.sl-phone-steps > span {
  display: flex;
  flex-direction: column;
  gap: 4px;
  align-items: center;
}

.sl-step-bar {
  width: 100%;
  height: 3px;
  background: var(--sl-yellow);
  opacity: 0;
}

.sl-step-bar-a { animation: sl-frame-a 7.5s linear infinite; }
.sl-step-bar-b { animation: sl-frame-b 7.5s linear infinite; }
.sl-step-bar-c { animation: sl-frame-c 7.5s linear infinite; }

/* ------------------------------------------------- 01 your own numbers --
 * Restored with the calculator section. Unchanged from the sheet the landing rebuild
 * removed, apart from the crew checkbox and the recommendation block at the foot of the
 * result card, which are new.
 */
.sl-calc {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 520px);
  gap: 80px;
  align-items: center;
}

.sl-calc-controls {
  display: flex;
  flex-direction: column;
  gap: 34px;
  max-width: 520px;
  margin-top: 44px;
}

/* A <p> wrapper, so the browser's default block margins have to go — left in, they doubled
 * the flex gap between sliders. */
.sl-calc-field {
  display: block;
  margin: 0;
}

.sl-calc-field-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin-bottom: 12px;
}

.sl-calc-field-name {
  font: 600 15px/1.3 var(--sl-display-font);
}

.sl-calc-field-value {
  font: 700 19px/1 var(--sl-mono-font);
  font-variant-numeric: tabular-nums;
}

/* A real range input rather than a pointer-only drag handle, so the calculator is keyboard
 * operable and announces its value. `--sl-p` is the filled percentage, written by home.js
 * through the CSSOM on input. */
.sl-range {
  --sl-p: 0;
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 34px;
  margin: 0;
  background: transparent;
  cursor: ew-resize;
  touch-action: pan-y;
}

.sl-range::-webkit-slider-runnable-track {
  height: 6px;
  background: linear-gradient(
    to right,
    var(--sl-track-fill, var(--sl-ink)) 0 calc(var(--sl-p) * 1%),
    var(--sl-rule) calc(var(--sl-p) * 1%) 100%
  );
}

.sl-range::-moz-range-track {
  height: 6px;
  background: var(--sl-rule);
}

.sl-range::-moz-range-progress {
  height: 6px;
  background: var(--sl-track-fill, var(--sl-ink));
}

.sl-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  margin-top: -7px;
  border: 2px solid var(--sl-ink);
  border-radius: 0;
  background: var(--sl-yellow);
}

.sl-range::-moz-range-thumb {
  width: 16px;
  height: 16px;
  border: 2px solid var(--sl-ink);
  border-radius: 0;
  background: var(--sl-yellow);
}

.sl-range:focus-visible {
  outline: 3px solid var(--sl-pen);
  outline-offset: 4px;
}

/* The share-you-eat slider fills red, because what it measures is a loss. */
.sl-range-loss {
  --sl-track-fill: var(--sl-red);
}

/* The crew question. A real checkbox at its platform size — 20px is above the 24px touch
 * target once the label is counted, and the whole label is the hit area. */
.sl-calc-check label {
  display: flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  font: 600 15px/1.3 var(--sl-display-font);
  min-height: 44px;
}

.sl-calc-check input {
  width: 20px;
  height: 20px;
  accent-color: var(--sl-ink);
  flex: none;
}

.sl-calc-check input:focus-visible {
  outline: 3px solid var(--sl-pen);
  outline-offset: 3px;
}

.sl-calc-result {
  background: var(--sl-card);
  border: 1px solid var(--sl-rule);
  padding: 48px 44px;
  box-shadow: 0 22px 50px rgba(23, 21, 15, 0.1);
}

.sl-calc-total {
  font: 800 clamp(52px, 5.6vw, 82px) / 1 var(--sl-display-font);
  letter-spacing: -0.045em;
  color: var(--sl-red);
  /* Explicit all-round margin: the default bottom margin on a <p> is 1em, which at 82px
   * opened an 82px hole between the figure and the line explaining it. */
  margin: 18px 0 0;
  font-variant-numeric: tabular-nums;
}

.sl-calc-total-note {
  font: 400 16px/1.5 var(--sl-display-font);
  color: var(--sl-ink-2);
  margin: 14px 0 0;
}

.sl-calc-rule {
  height: 1px;
  background: var(--sl-rule);
  margin: 32px 0;
  border: 0;
}

/* The recommendation. `min-height` holds the block's size across the four plan copies, so
 * dragging a slider through a threshold does not make the card jump. */
.sl-calc-rec-line {
  margin: 14px 0 0;
  min-height: 3.2em;
  font: 600 18px/1.45 var(--sl-display-font);
  color: var(--sl-ink);
  text-wrap: pretty;
}

.sl-calc-compare {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  margin-top: 18px;
  padding-top: 18px;
  border-top: 1px solid var(--sl-rule);
}

.sl-calc-compare dt {
  font: 400 15px/1.4 var(--sl-display-font);
  color: var(--sl-ink-3);
}

.sl-calc-compare dd {
  margin: 0;
  font: 700 22px/1 var(--sl-display-font);
  font-variant-numeric: tabular-nums;
}

/* Payback in one event. Reserved height for the same reason the recommendation line has it:
 * the four branches differ in length and the card must not resize under a dragging thumb. */
.sl-calc-payback {
  margin: 12px 0 0;
  min-height: 2.8em;
  font: 600 15px/1.4 var(--sl-display-font);
  color: var(--sl-ink-3);
}

/* A real <button> now rather than an <a> wearing the button treatment: it signs the
 * reader up carrying the figure they just produced, instead of scrolling them to a price
 * card. Full width, because it is the end of the card's argument. */
.sl-calc-rec-cta {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  margin-top: 22px;
  text-align: center;
}

/* The scroll-to-plan the button used to own, kept as the quiet second option so the
 * recommendation still has somewhere to point. home.js keeps its href, its label and the
 * highlight-on-click behaviour. */
.sl-calc-plan-line {
  margin: 12px 0 0;
  text-align: center;
}

.sl-calc-plan-link {
  font: 500 14px/1.4 var(--sl-display-font);
  color: var(--sl-ink-3);
  text-underline-offset: 3px;
}

.sl-calc-plan-link:hover {
  color: var(--sl-ink);
}

.sl-calc-honest {
  margin: 26px 0 0;
  font: 600 21px/1.35 var(--sl-hand-font);
  color: var(--sl-ink-4);
}

/* -------------------------------------------------------- testimonials --
 * Styled and ready so that removing `hidden` from the section is the whole launch;
 * the section ships hidden until real, permissioned quotes exist (see the markup). */
.sl-testimonials {
  padding-inline: clamp(20px, 5vw, 64px);
}

.sl-testimonial-grid {
  list-style: none;
  margin: 36px auto 0;
  padding: 0;
  max-width: var(--sl-max);
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 20px;
}

.sl-testimonial figure {
  margin: 0;
  height: 100%;
  padding: 24px;
  border: 1px solid var(--sl-rule-strong);
  background: var(--sl-paper);
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.sl-testimonial blockquote {
  margin: 0;
  flex: 1;
}

.sl-testimonial blockquote p {
  margin: 0;
  font: 600 19px/1.4 var(--sl-hand-font);
  color: var(--sl-ink);
}

.sl-testimonial figcaption {
  display: grid;
  grid-template-columns: 48px 1fr;
  grid-template-rows: auto auto;
  column-gap: 12px;
  align-items: center;
}

.sl-testimonial-photo {
  grid-row: 1 / 3;
  width: 48px;
  height: 48px;
  border-radius: 50%;
  object-fit: cover;
  border: 1px solid var(--sl-rule-strong);
}

.sl-testimonial-name {
  font: 700 15px/1.3 var(--sl-display-font);
  color: var(--sl-ink);
}

.sl-testimonial-trade {
  font: 500 11px/1.4 var(--sl-mono-font);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

/* ---------------------------------------------------------- 02 pricing --
 * The band scale first — the whole pay-as-you-go price list drawn as three zones on one
 * axis of variation value — then the worked examples, the two cards
 * and the founding offer. The ids, data-attributes and list containers are still the ones
 * app.js writes into (renderPublicPlan / renderPublicPaygPlan).
 *
 * The scale and examples are written mobile-first: the base rules are the
 * stacked 375px layout, and the single min-width query below is the horizontal scale.
 */
.sl-pricing-heading {
  max-width: 22ch;
}

.sl-band-scale {
  margin: 44px 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 12px;
}

/* One zone. On a phone it is a full-width row, fee left and range right; on the wide
 * scale it becomes a column with the edge tick at the top. */
.sl-band {
  position: relative;
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px 14px;
  flex-wrap: wrap;
  padding: 18px;
  border: 1px solid var(--sl-dark-rule);
}

/* The $ ticks repeat what the ranges already say, so they only earn their space on the
 * horizontal scale, where they mark the zone boundaries like a ruler. */
.sl-band .sl-band-edge {
  display: none;
}

.sl-band-fee {
  font: 800 26px/1 var(--sl-display-font);
  letter-spacing: -0.03em;
  font-variant-numeric: tabular-nums;
}

.sl-band-range {
  font: 500 12px/1.4 var(--sl-mono-font);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--sl-dark-mute);
}

/* The free band is the strongest fact in the section, so it takes the palette's emphasis
 * colour and the largest type — the first thing the eye lands on, on any width. */
.sl-band[data-band-zone="free"] {
  background: var(--sl-yellow);
  border-color: var(--sl-yellow);
  color: var(--sl-ink);
  padding-block: 28px;
}

.sl-band[data-band-zone="free"] .sl-band-fee {
  font-size: 38px;
  text-transform: uppercase;
}

.sl-band[data-band-zone="free"] .sl-band-range {
  color: var(--sl-ink-3);
}

.sl-band[data-band-zone="a"] {
  background: var(--sl-card);
  border-color: var(--sl-card);
  color: var(--sl-ink);
}

.sl-band[data-band-zone="a"] .sl-band-range {
  color: var(--sl-ink-4);
}

.sl-band[data-band-zone="b"] {
  color: var(--sl-paper);
}

.sl-band-note {
  margin: 18px 0 0;
  max-width: 70ch;
  font: 600 15px/1.6 var(--sl-display-font);
  color: var(--sl-dark-text);
}

/* The worked examples: a thin strip, one job per band. */
.sl-band-examples {
  margin: 36px 0 0;
  padding: 18px 0 0;
  border-top: 1px solid var(--sl-dark-rule);
  list-style: none;
  display: grid;
  gap: 12px;
}

.sl-band-examples li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  font: 400 15px/1.5 var(--sl-display-font);
  color: var(--sl-dark-mute);
}

/* The arrow and the fee wrap as one unit, so a narrow measure never strands the fee on
 * its own line with the arrow dangling at the end of the one above. */
.sl-example-result {
  white-space: nowrap;
}

.sl-example-fee {
  font: 700 14px/1.2 var(--sl-mono-font);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--sl-paper);
  font-variant-numeric: tabular-nums;
}

.sl-band-examples li:first-child .sl-example-fee {
  color: var(--sl-yellow);
}

/* The horizontal scale. Zone width falls as value rises — the free band is the widest
 * thing in the section — and the shared borders between zones are the $500 and $5,000
 * edges, each labelled by the tick at the top of the zone it opens. */
@media (min-width: 760px) {
  .sl-band-scale {
    grid-template-columns: 46fr 34fr 20fr;
    gap: 0;
    align-items: stretch;
  }

  .sl-band {
    flex-direction: column;
    align-items: flex-start;
    justify-content: flex-end;
    gap: 10px;
    padding: 64px 20px 20px;
    border-right-width: 0;
  }

  .sl-band:last-child {
    border-right-width: 1px;
  }

  .sl-band .sl-band-edge {
    display: block;
    position: absolute;
    top: 16px;
    left: 20px;
    font: 500 11px/1 var(--sl-mono-font);
    letter-spacing: 0.08em;
    color: var(--sl-dark-mute);
  }

  .sl-band[data-band-zone="free"] .sl-band-edge,
  .sl-band[data-band-zone="a"] .sl-band-edge {
    color: var(--sl-ink-4);
  }

  /* The free band's extra breathing room is a MOBILE rule — there it is a taller row and
   * the padding is what makes it the biggest thing on the screen. On the horizontal scale
   * the zones share a baseline, so it takes the same padding as its neighbours or its
   * range label rides 8px above theirs and the row of labels stops reading as one line. */
  .sl-band[data-band-zone="free"] {
    padding-block: 64px 20px;
  }

  .sl-band[data-band-zone="free"] .sl-band-fee {
    font-size: clamp(40px, 3.6vw, 52px);
  }

}

/* Three columns for the worked examples only once each gets a real measure. Between
 * 760 and 900px they used to take three ~200px columns while the plan cards below had
 * already stacked — every example wrapped its price onto an orphan line and the strip
 * read as congestion, not as a scale. Stacked, they read as the job-site one-liners
 * they are. */
@media (min-width: 901px) {
  /* 901, not 900: the plan cards stack at `max-width: 900px`, and at exactly 900 both
   * rules would fire — three cramped example columns over single-column cards, the very
   * mismatch this breakpoint exists to prevent. */
  .sl-band-examples {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 18px;
  }
}

/* One card now that pay-as-you-go is stated entirely by the scale above. A single column
 * capped well under the section measure — a lone card stretched to 980px reads as a
 * banner, not an offer — and height:auto because the equal-height coupling in styles.css
 * existed to line this card's buttons up with a neighbour it no longer has. */
.sl-pricing .price-cards {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  max-width: 560px;
  gap: 30px;
  margin-top: 40px;
  align-items: start;
}

.sl-pricing .price-card {
  height: auto;
}

/* Column layout so the policy links can be pinned to the card's foot — the two cards are
 * the same height and the shorter one otherwise trailed a block of empty white. */
.sl-pricing .price-card {
  display: flex;
  flex-direction: column;
  background: var(--sl-card);
  color: var(--sl-ink);
  border: 0;
  border-radius: 0;
  padding: 32px 26px;
  box-shadow: none;
  /* The cards are anchor targets for the calculator's recommendation and carry
   * tabindex="-1" so the keyboard caret lands with the scroll. Without this the browser
   * paints its own focus ring on a whole card, which reads as an error state. The
   * recommendation highlight below is the visible feedback instead. */
  outline: none;
  scroll-margin-top: 24px;
  transition: box-shadow 0.2s ease;
}

/* The design's offset yellow block, kept on the free column — the entry point the page
 * most wants read — and reused for the most-popular card. */
.sl-pricing .price-card-free,
.sl-pricing .price-card-featured {
  box-shadow: 8px 8px 0 var(--sl-yellow);
}

/* Where the calculator's recommendation lands. A border and a glow, never a modal —
 * the reader asked a question and the answer is on the page they are already looking at. */
.sl-pricing .price-card.is-recommended {
  box-shadow: 0 0 0 3px var(--sl-pen), 8px 8px 0 var(--sl-yellow);
}

/* The band scale is the pay-as-you-go recommendation target now that the plan's card is
 * gone — it carries id="plan-payg" and tabindex="-1" in the markup. It sits on the dark
 * ground where the pen blue reads as almost black, so it takes the yellow outline
 * instead, and no browser focus ring: the highlight is the feedback. */
.sl-band-scale {
  outline: none;
  scroll-margin-top: 24px;
}

.sl-band-scale.is-recommended {
  box-shadow: 0 0 0 3px var(--sl-yellow);
}

@media (prefers-reduced-motion: reduce) {
  .sl-pricing .price-card {
    transition: none;
  }
}

.sl-pricing .price-card h3 {
  margin: 0;
  font: 500 11px/1 var(--sl-mono-font);
  letter-spacing: 0.16em;
  color: var(--sl-ink-5);
  text-transform: uppercase;
}

/* Smaller than the two-column set it replaces — four cards to a row cannot carry a 58px
 * figure. `tabular-nums` is load-bearing rather than typographic polish: the toggle swaps
 * "39" for "32.50" in place, and proportional digits would change the line's width and
 * nudge everything under it. */
.sl-pricing .price-point {
  display: block;
  margin-top: 14px;
  font: 800 clamp(32px, 2.9vw, 40px) / 1 var(--sl-display-font);
  letter-spacing: -0.04em;
  color: var(--sl-ink);
  font-variant-numeric: tabular-nums;
}

/* "A$49" is one figure at one size in the design, not a superscripted currency mark. */
.sl-pricing .price-currency {
  font-size: inherit;
}

.sl-pricing .price-cadence {
  display: block;
  margin-top: 8px;
  font: 500 17px/1.3 var(--sl-display-font);
  letter-spacing: 0;
  color: var(--sl-ink-4);
}

.sl-pricing .monthly-price-detail {
  margin: 12px 0 0;
  font: 400 15px/1.5 var(--sl-display-font);
  color: var(--sl-ink-4);
}

.sl-pricing .price-card ul {
  margin: 30px 0 0;
  padding: 0;
  list-style: none;
  border-top: 1px solid var(--sl-rule);
  padding-top: 26px;
  display: grid;
  gap: 12px;
}

.sl-pricing .price-card li {
  font: 400 15px/1.45 var(--sl-display-font);
  color: var(--sl-ink-2);
  padding-left: 20px;
  position: relative;
}

.sl-pricing .price-card li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.5em;
  width: 8px;
  height: 8px;
  background: var(--sl-yellow);
}

/* The not-included list has to read as an exclusion, not a feature. */
.sl-pricing .price-card-limits {
  border-top-style: dashed;
}

.sl-pricing .price-card-limits li {
  color: var(--sl-ink-4);
}

.sl-pricing .price-card-limits li::before {
  background: transparent;
  border-top: 2px solid var(--sl-ink-5);
  height: 0;
  top: 0.72em;
}

/* The :not() is load-bearing, not decoration. styles.css hides an empty action container
 * with `.price-card-actions[data-visible-actions="0"] { display: none }` — specificity
 * (0,2,0), exactly the same as a bare `.sl-pricing .price-card-actions`. On a tie the later
 * sheet wins, so an unguarded rule here re-showed the container and a Business subscriber
 * got an empty action block reserved inside the pay-as-you-go card. app.js writes that
 * attribute at runtime, so no signed-out screenshot shows it. */
/* `margin-top: auto` restores what styles.css asks for and this rule used to override with
 * a fixed 30px — the actions sit at the card's foot so the two cards' buttons line up
 * across the equal-height pair. It matters more now than it did: the pay-as-you-go card
 * lost its price headline and three priced bullets to the band scale above, so it is much
 * the shorter of the two, and a fixed margin left it ending early with 177px of dead white
 * beneath. The padding is the floor the margin no longer provides — `auto` collapses to 0
 * when a card has no spare height, which is every card at mobile widths. */
.sl-pricing .price-card-actions:not([data-visible-actions="0"]) {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: auto;
  padding-top: 30px;
}

/* "Solo adds:" / "Business adds:" / "Crew adds:". The list underneath is what the tier
 * gives you; this says who it is being added to. It replaces a five-item "No SMS delivery /
 * No branded email / …" list that used to sit on the entry-level column — identical facts,
 * read from the side that is buying rather than the side that is missing out. */
.sl-pricing .price-adds-label {
  margin: 26px 0 -14px;
  font: 500 10px/1 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

/* The refund promise, directly under the call to action it qualifies. Ink rather than the
 * muted note colour: a guarantee that reads as fine print is not doing its job, and this
 * one is carried by the Refunds & Cancellations policy. */
.sl-pricing .price-guarantee {
  margin: 14px 0 0;
  font: 600 14px/1.5 var(--sl-display-font);
  color: var(--sl-ink-2);
}

.sl-pricing .price-note {
  margin: 18px 0 0;
  font: 400 13px/1.6 var(--sl-display-font);
  color: var(--sl-ink-5);
}

.sl-pricing .price-note-emphasis {
  font-size: 15px;
  font-weight: 600;
  color: var(--sl-ink-2);
}

.sl-pricing .pricing-policy-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: auto;
  padding-top: 22px;
}

.sl-pricing .free-plan-status,
.sl-pricing .subscription-active {
  margin: 14px 0 0;
}

/* ------------------------------------------------------------- founding --
 * Directly beneath the Business card, carrying the live subscribe controls. Yellow ground
 * because it is the one genuinely scarce offer on the page and the palette's emphasis
 * colour is yellow — green is reserved for approved and success states.
 */
.sl-founding {
  margin-top: 30px;
  background: var(--sl-yellow);
  color: var(--sl-ink);
  padding: 38px 36px;
}

.sl-founding-head h3 {
  margin: 0;
  font: 500 11px/1 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-3);
}

.sl-founding-line {
  margin: 14px 0 0;
  max-width: 30ch;
  font: 800 clamp(24px, 2.6vw, 34px) / 1.15 var(--sl-display-font);
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.sl-founding-meter {
  margin-top: 26px;
  max-width: 420px;
}

.sl-founding-count {
  margin: 0 0 10px;
  font: 700 16px/1.2 var(--sl-mono-font);
  font-variant-numeric: tabular-nums;
}

/* A native <progress>, restyled. `appearance: none` plus both vendor pseudo-elements is
 * what it takes to colour one consistently; the element keeps its own role and value for
 * assistive tech, which a div with a width does not. */
.sl-founding-bar {
  -webkit-appearance: none;
  appearance: none;
  display: block;
  width: 100%;
  height: 8px;
  border: 1px solid var(--sl-ink);
  background: transparent;
}

.sl-founding-bar::-webkit-progress-bar {
  background: transparent;
}

.sl-founding-bar::-webkit-progress-value {
  background: var(--sl-ink);
}

.sl-founding-bar::-moz-progress-bar {
  background: var(--sl-ink);
}

.sl-founding-list {
  margin: 26px 0 0;
  padding-left: 20px;
  max-width: 62ch;
}

.sl-founding-list li {
  margin-top: 8px;
  font: 400 15px/1.55 var(--sl-display-font);
  color: var(--sl-ink-2);
}

.sl-founding .founding-terms {
  margin: 20px 0 0;
  max-width: 70ch;
  font: 400 13px/1.6 var(--sl-display-font);
  /* Deliberately --sl-ink-3 rather than a lighter grey: this is the caveat that makes the
   * guarantee honest, and it sits on yellow, where a mute grey would fall under 4.5:1. */
  color: var(--sl-ink-3);
}

.sl-cancel-note {
  margin: 30px 0 0;
  max-width: 70ch;
  font: 600 17px/1.6 var(--sl-display-font);
  color: var(--sl-paper);
}

/* What is left of the break-even paragraph. One line, because the calculator above now
 * answers the same question with the reader's own numbers. */
.sl-crossover {
  margin: 14px 0 0;
  max-width: 70ch;
  font: 400 17px/1.6 var(--sl-display-font);
  color: var(--sl-dark-text);
}

/* The footnote the GST statement and the fair-use qualifier moved into. Small, but held at
 * --sl-dark-mute (7.19:1 on the ink ground) rather than anything dimmer — it carries the
 * tax treatment, which is not decoration. */
.sl-fineprint {
  margin: 22px 0 0;
  max-width: 78ch;
  font: 400 13px/1.7 var(--sl-display-font);
  color: var(--sl-dark-mute);
}

/* -------------------------------------------------------- pricing, on a desk --
 * The lone Business card is deliberately capped at 560px (see .price-cards above), which
 * on a phone is simply the column, but on a wide screen left it parked against the left
 * edge with the whole right half of the section empty — while four paragraphs of
 * reassurance queued up underneath it. From 1020px the section becomes a grid: the scale,
 * note and examples keep their full-width rows, and the card takes the left column with
 * that prose beside it on the right. Markup untouched; every id app.js binds to stays put.
 *
 * The 1fr final row is load-bearing: the card spans four rows, and without it the card's
 * extra height would be dealt out equally between the note rows, scattering four short
 * paragraphs down the full height of the card. With it, the slack all lands in the last
 * row and the notes stay clustered at the top, level with the card. */
@media (min-width: 1020px) {
  .sl-pricing {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    grid-template-rows: repeat(8, auto) 1fr;
    grid-template-areas:
      "index     index"
      "heading   heading"
      "scale     scale"
      "bandnote  bandnote"
      "examples  examples"
      "card      cancel"
      "card      crossover"
      "card      fineprint"
      "card      links";
    column-gap: clamp(64px, 7vw, 120px);
  }

  .sl-pricing .sl-index { grid-area: index; }
  .sl-pricing .sl-pricing-heading { grid-area: heading; }
  .sl-pricing .sl-band-scale { grid-area: scale; }
  .sl-pricing .sl-band-note { grid-area: bandnote; }
  .sl-pricing .sl-band-examples { grid-area: examples; }
  .sl-pricing .price-cards { grid-area: card; }

  /* Level with the card's own heading rather than its top edge: the card carries 40px of
   * margin and 32px of padding before its first line, and the note reads as its neighbour
   * only if the two first lines sit on roughly the same axis. */
  .sl-pricing .sl-cancel-note { grid-area: cancel; margin-top: 72px; }
  .sl-pricing .sl-crossover { grid-area: crossover; }
  .sl-pricing .sl-fineprint { grid-area: fineprint; }
  .sl-pricing .pricing-policy-links { grid-area: links; align-self: start; }
}

/* Founding-customer note and the plan-crossover paragraph, on the dark ground. */
.sl-pricing-notes {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 30px;
  margin-top: 30px;
}

.sl-pricing-note {
  border: 1px solid var(--sl-dark-rule);
  padding: 34px 32px;
}

.sl-pricing-note h3 {
  margin: 0;
  font: 500 11px/1 var(--sl-mono-font);
  letter-spacing: 0.16em;
  color: var(--sl-yellow);
  text-transform: uppercase;
}

.sl-pricing-note p,
.sl-pricing-note li {
  font: 400 16px/1.6 var(--sl-display-font);
  color: var(--sl-dark-text);
}

.sl-pricing-note p {
  margin: 16px 0 0;
}

.sl-pricing-note ul {
  margin: 16px 0 0;
  padding-left: 20px;
}

.sl-pricing-note li {
  margin-top: 8px;
}

.sl-pricing-note .founding-terms,
.sl-pricing-note .sl-fine {
  font-size: 13px;
  color: #7e7869;
}

.sl-pricing .pricing-comparison {
  margin: 34px 0 0;
  max-width: 900px;
  font: 400 15px/1.7 var(--sl-display-font);
  color: var(--sl-dark-mute);
  /* styles.css centres this paragraph; ranged-right against the rest of the section's flush
   * left edge it read as a caption rather than the pricing arithmetic. */
  text-align: left;
}

.sl-pricing .pricing-billing-error:empty {
  display: none;
}

/* -------------------------------------------------- sticky phone CTA --
 * Phone-only. public/index.html is the marketing document exclusively now (the
 * signed-in workspace lives at /app), so this bar shows unconditionally on it — no
 * `:has()` gate on a signed-out class is needed any more.
 *
 * The base rule is display:none so it costs nothing on desktop.
 * Everything that turns it on lives in the media query below. */
.sl-sticky-cta {
  display: none;
}

@media (max-width: 620px) {
  .sl-sticky-cta {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    /* Under the toast (90), which is a transient message that must sit over a
     * permanent bar. */
    z-index: 50;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 14px;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
    background: var(--sl-ink);
    border-top: 2px solid var(--sl-yellow);
    box-shadow: 0 -8px 26px rgba(23, 21, 15, 0.22);
    transition: transform 0.28s cubic-bezier(0.2, 0.7, 0.3, 1);
  }

  /* home.js tucks it while the hero's own CTA is on screen — two identical buttons one
   * above the other is noise. If home.js never runs the class is never added and the bar
   * simply stays up, which is the behaviour to fail into. */
  .sl-sticky-cta.is-tucked {
    transform: translateY(115%);
  }

  /* So the last of the footer is not permanently under the bar. Scoped off
   * public/app.html (`sl-app-shell`): that document never renders `.sl-sticky-cta` at
   * all, so reserving space for it there would just be dead padding under the app. */
  body:not(.sl-app-shell) {
    padding-bottom: 84px;
  }

  .sl-sticky-copy {
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
    min-width: 0;
  }

  .sl-sticky-copy strong {
    font: 700 14px/1.2 var(--sl-display-font);
    color: var(--sl-paper);
  }

  .sl-sticky-copy span {
    font: 400 11.5px/1.3 var(--sl-display-font);
    color: var(--sl-dark-mute);
  }

  .sl-sticky-cta .button {
    flex: none;
    border: 0;
    border-radius: 0;
    background: var(--sl-yellow);
    color: var(--sl-ink);
    font: 700 15px/1 var(--sl-display-font);
    padding: 14px 18px;
    min-height: 48px;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    box-shadow: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sl-sticky-cta {
    transition: none;
  }
}

/* ---------------------------------------------------------- responsive -- */
@media (max-width: 1100px) {
  .sl-calc {
    grid-template-columns: minmax(0, 1fr);
    gap: 48px;
  }

  .sl-calc-result {
    max-width: 560px;
  }
}

@media (max-width: 900px) {
  .sl-pricing .price-cards,
  .sl-pricing .sl-pricing-notes {
    grid-template-columns: minmax(0, 1fr);
  }

  .sl-calc-controls {
    margin-top: 34px;
  }
}

@media (max-width: 620px) {
  :root {
    --sl-gutter: 20px;
    --sl-section-y: 56px;
  }

  .sl-hero {
    /* The visual column used to lead here (column-reverse), which meant the first screen on
     * a phone was a looping mockup — now real demo footage — and the headline was below the
     * fold, so a visitor had to scroll before the page said what it was. Headline, promise
     * and CTA first; the demo is the proof that follows them, not the introduction. */
    flex-direction: column;
    align-items: stretch;
    gap: 36px;
  }

  .sl-hero .sl-button {
    max-width: none;
  }

  .sl-pricing .price-card {
    padding: 26px 20px;
  }

  .sl-pricing .price-card-actions .button {
    width: 100%;
  }

  .sl-founding {
    padding: 28px 20px;
  }

  .sl-founding-line {
    max-width: none;
  }

  .sl-calc-result {
    padding: 30px 22px;
  }

  /* The four recommendation copies differ by more than a line once the measure narrows,
   * so the desktop reservation is not enough to stop the card resizing. */
  .sl-calc-rec-line {
    min-height: 4.6em;
  }
}

/* ------------------------------------------------------ reduced motion --
 * Every animation on this page is decorative. Switched off here rather than merely slowed.
 * The hero's own animated three-frame phone loop that used to be the bulk of this block is
 * gone with the mockup it animated — what is left is the dialog open transition, the shell's
 * slow float (added with the phone-shell framing above) and the demo video, which does not
 * animate itself but changes how it is offered below. */
@media (prefers-reduced-motion: reduce) {
  .sl-page dialog[open] {
    animation: none;
  }

  /* The frames resolve to the finished record — the state that actually says what the
   * product does — rather than freezing on a half-typed draft. */
  .sl-frame,
  .sl-frame *,
  .sl-step-bar,
  .sl-record-stamp,
  .sl-typed,
  .sl-caret,
  .sl-caret-wrap {
    animation: none !important;
  }

  .sl-frame-a,
  .sl-frame-b {
    opacity: 0;
  }

  .sl-frame-c {
    opacity: 1;
  }

  .sl-step-bar-c {
    opacity: 1;
  }

  .sl-record-stamp {
    opacity: 1;
    transform: rotate(-13deg) scale(1);
  }
}

/* ================================================================ modals ==
 * The fifteen <dialog> elements in index.html, reskinned to the design's modal
 * language: hard edges, the paper/ink palette, mono eyebrows, Archivo headings, and the
 * yellow-offset primary action.
 *
 * Almost entirely CSS on markup that already had the right grammar. Every dialog in this
 * app is built from .dialog-header + .dialog-section/label/.small-note + .dialog-actions,
 * which is exactly the structure the design draws — so restyling those primitives moves all
 * fifteen at once and touches no id, no form field name and no app.js binding.
 *
 * Scoped `.sl-page dialog` for two reasons. It cannot leak into the workspace or the
 * register (no <dialog> outside the modals), and it clears the class-level rules in
 * styles.css: `.invoice-dialog` and friends are (0,1,0), which a bare `dialog` selector
 * would lose to no matter which sheet loads last. Every override below is strictly higher
 * specificity rather than equal-and-later, so re-ordering the two <link> tags cannot
 * silently undo it.
 *
 * The three dialogs the design does not draw — auth, quick variation and the detailed
 * create form — inherit the same skin from these primitives, so the app does not ship two
 * competing modal aesthetics.
 */

.sl-page dialog {
  border: 1px solid var(--sl-ink);
  border-radius: 0;
  background: var(--sl-paper);
  color: var(--sl-ink);
  box-shadow: 0 30px 70px rgba(23, 21, 15, 0.35);
  padding: 0;
  font-family: var(--sl-display-font);
}

.sl-page dialog::backdrop {
  background: rgba(23, 21, 15, 0.55);
  backdrop-filter: blur(3px);
}

/* Entrance is driven from [open] rather than a data-reveal attribute: a dialog animates
 * when it opens, not when it scrolls into view. (home.js's reveal sweep is document-wide
 * and would otherwise "reveal" a closed dialog's contents.) */
@keyframes sl-modal-in {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.985);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.sl-page dialog[open] {
  animation: sl-modal-in 0.3s cubic-bezier(0.2, 0.8, 0.3, 1) both;
}

/* -------------------------------------------------------- modal header -- */
.sl-page dialog .dialog-header {
  min-height: 0;
  align-items: flex-start;
  gap: 20px;
  padding: 30px 34px 24px;
  border-bottom: 1px solid var(--sl-rule);
  background: var(--sl-paper);
}

.sl-page dialog .dialog-header .eyebrow {
  margin: 0 0 14px;
  font: 500 10px/1 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

.sl-page dialog .dialog-header h2 {
  margin: 0;
  font: 800 clamp(22px, 2.2vw, 30px) / 1.05 var(--sl-display-font);
  letter-spacing: -0.03em;
  color: var(--sl-ink);
}

/* The × . Kept at a 44px target — it is the only way out of a modal on touch. */
.sl-page dialog .icon-button {
  flex: none;
  display: grid;
  place-items: center;
  min-width: 44px;
  min-height: 44px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--sl-ink-5);
  font: 400 26px/1 var(--sl-display-font);
}

.sl-page dialog .icon-button:hover {
  color: var(--sl-ink);
  background: var(--sl-paper-alt);
}

/* ------------------------------------------------------ modal sections -- */
.sl-page dialog .dialog-section {
  padding: 30px 34px 0;
  border-top: 0;
}

.sl-page dialog .dialog-section h3,
.sl-page dialog .settings-panel-heading h3 {
  margin: 0 0 16px;
  font: 700 19px/1.2 var(--sl-display-font);
  letter-spacing: -0.015em;
  color: var(--sl-ink);
}

/* The numbered step badge in the share modal — 01 / 02 / 03 in yellow, baseline-aligned
 * with the heading it belongs to. */
.sl-page dialog .dialog-section h3 {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 12px;
}

.sl-dstep {
  font: 800 20px/1 var(--sl-display-font);
  color: var(--sl-yellow);
}

.sl-dhint {
  font: 400 14px/1.3 var(--sl-display-font);
  color: var(--sl-ink-5);
}

.sl-page dialog .form-intro,
.sl-page dialog .settings-panel-heading p {
  margin: 0 0 20px;
  font: 400 15px/1.5 var(--sl-display-font);
  color: var(--sl-ink-3);
}

.sl-page dialog .small-note {
  font: 400 13px/1.55 var(--sl-display-font);
  color: var(--sl-ink-5);
}

/* --------------------------------------------------------- form fields --
 * Hard-edged, cream, one-pixel rule — the design's field treatment. Scoped to `dialog` so
 * the workspace's own forms keep the existing design system. */
/* 1.5 rather than the design's single line: these captions wrap on a 390px screen (the
 * upload caption takes two lines), and a unit line-height collides them. */
.sl-page dialog label {
  display: block;
  font: 500 10px/1.5 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

/* A checkbox label is a sentence with a control in it, not a field caption — it must stay
 * inline and stay in sentence case, or the mono-uppercase treatment above turns
 * "Enable automated reminders" into a shouted fragment beside a stray box. */
.sl-page dialog label.check-label,
.sl-page dialog label.invoice-select-all {
  display: flex;
  align-items: center;
  gap: 10px;
  font: 400 15px/1.4 var(--sl-display-font);
  letter-spacing: normal;
  text-transform: none;
  color: var(--sl-ink-2);
}

.sl-page dialog label.check-label input,
.sl-page dialog label.invoice-select-all input {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 0;
  accent-color: var(--sl-ink);
}

.sl-page dialog label input:not([type="color"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]),
.sl-page dialog label select,
.sl-page dialog label textarea,
.sl-page dialog .copy-row input,
.sl-page dialog .library-search input {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin-top: 8px;
  padding: 14px;
  border: 1px solid var(--sl-rule);
  border-radius: 0;
  background: var(--sl-card);
  color: var(--sl-ink);
  /* 16px, not 15: iOS Safari auto-zooms the page on focus for any input under
     16px, and there's no scroll-back-into-view handling for dialog fields to
     recover from that once the keyboard is also open. */
  font: 400 16px/1.4 var(--sl-display-font);
  text-transform: none;
  letter-spacing: normal;
}

.sl-page dialog label textarea {
  line-height: 1.5;
  resize: vertical;
}

/* A colour input paints its whole box with the chosen colour, so the field padding above
 * turned the branding swatches into two black bars. Sized as the design draws them: a
 * small square chip, not a full-width field. */
.sl-page dialog label input[type="color"] {
  display: block;
  width: 64px;
  height: 40px;
  margin-top: 8px;
  padding: 3px;
  box-sizing: border-box;
  border: 1px solid var(--sl-rule);
  border-radius: 0;
  background: var(--sl-card);
}

/* Dates, times and the approval link read as data, so they set in the mono face. */
.sl-page dialog label input[type="date"],
.sl-page dialog label input[type="datetime-local"],
.sl-page dialog .copy-row input {
  font: 500 15px/1.4 var(--sl-mono-font);
}

.sl-page dialog label input:focus-visible,
.sl-page dialog label select:focus-visible,
.sl-page dialog label textarea:focus-visible {
  outline: 2px solid var(--sl-ink);
  outline-offset: -2px;
}

.sl-page dialog ::placeholder {
  color: #a9a497;
}

/* A <small> inside a label is a hint about the field, not a second caption — it must drop
 * the mono-uppercase treatment it would otherwise inherit. */
.sl-page dialog label small:not(.field-error) {
  display: block;
  margin-top: 8px;
  font: 400 13px/1.5 var(--sl-display-font);
  letter-spacing: normal;
  text-transform: none;
  color: var(--sl-ink-5);
}

.sl-page dialog .field-error,
.sl-page dialog .form-error {
  display: block;
  margin-top: 8px;
  font: 500 13px/1.45 var(--sl-display-font);
  letter-spacing: normal;
  text-transform: none;
  color: var(--sl-red);
}

.sl-page dialog .form-error:empty,
.sl-page dialog .field-error:empty {
  display: none;
}

/* The small dialogs — invoice, reschedule, crew, business, save-to-library — put their
 * fields straight into the <form> with no .dialog-section wrapper, and relied on a `padding`
 * on `.invoice-dialog` / `.reschedule-dialog` for the inset. `.sl-page dialog { padding: 0 }`
 * legitimately beats those, so the inset has to be restated on the form's own children or
 * every field runs edge-to-edge into the modal border.
 *
 * Margin rather than padding so the ruled asides (the red correction warning, the library
 * caveat) sit inset with their border, as drawn. .quick-dialog is excluded: it has bespoke
 * chrome with its own measure. */
.sl-page dialog:not(.quick-dialog) form > label,
.sl-page dialog:not(.quick-dialog) form > p,
.sl-page dialog:not(.quick-dialog) form > small,
.sl-page dialog:not(.quick-dialog) form > .form-grid,
.sl-page dialog:not(.quick-dialog) form > section:not(.dialog-section) {
  margin-inline: 34px;
}

.sl-page dialog:not(.quick-dialog) form > label {
  margin-top: 22px;
}

.sl-page dialog:not(.quick-dialog) form > p,
.sl-page dialog:not(.quick-dialog) form > .form-grid,
.sl-page dialog:not(.quick-dialog) form > section:not(.dialog-section) {
  margin-top: 20px;
}

/* ------------------------------------------------------- modal actions -- */
.sl-page dialog .dialog-actions {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  gap: 12px;
  padding: 28px 34px 30px;
  margin-top: 30px;
  border-top: 1px solid var(--sl-rule);
}

.sl-page dialog .dialog-actions .button,
.sl-page dialog .dialog-actions .row-button {
  border-radius: 0;
  font-family: var(--sl-display-font);
}

.sl-page dialog .dialog-actions .button-primary {
  border: 0;
  background: var(--sl-ink);
  color: var(--sl-paper);
  font: 700 15px/1 var(--sl-display-font);
  padding: 15px 28px;
}

.sl-page dialog .dialog-actions .button-secondary,
.sl-page dialog .dialog-actions .row-button {
  border: 1px solid var(--sl-rule);
  background: transparent;
  color: var(--sl-ink-3);
  font: 600 15px/1 var(--sl-display-font);
  padding: 15px 20px;
}

.sl-page dialog .dialog-actions .button-secondary:hover,
.sl-page dialog .dialog-actions .row-button:hover {
  border-color: var(--sl-ink);
  color: var(--sl-ink);
}

/* Two groups, the way the design lays the footer out: one action off to the left, the rest
 * together on the right. `space-between` cannot do this — the share footer holds three
 * buttons and it strands "Done" in the middle of the row. */
.sl-page dialog .share-dialog-actions #duplicate-variation {
  margin-right: auto;
}

/* In-body buttons: upload, copy link, deliver. Flex-wrap rather than a fixed grid on
 * purpose — #send-sms is hidden on pay-as-you-go, and a two-column grid would leave a hole
 * where it used to be. */
.sl-page dialog .row-button,
.sl-page dialog .delivery-actions button,
.sl-page dialog .copy-row .button {
  border-radius: 0;
  font-family: var(--sl-display-font);
}

.sl-page dialog .delivery-actions {
  gap: 10px;
  margin-top: 16px;
}

.sl-page dialog .delivery-actions button {
  flex: 1 1 auto;
  min-height: 52px;
  border: 1px solid var(--sl-ink);
  background: var(--sl-card);
  color: var(--sl-ink);
  font: 700 15px/1 var(--sl-display-font);
}

.sl-page dialog .delivery-actions button.primary {
  border: 0;
  background: var(--sl-ink);
  color: var(--sl-paper);
  box-shadow: 4px 4px 0 var(--sl-yellow);
}

.sl-page dialog .copy-row .button-primary {
  border: 1px solid var(--sl-ink);
  border-radius: 0;
  background: var(--sl-card);
  color: var(--sl-ink);
  font: 700 14px/1 var(--sl-display-font);
}

/* The SMS-consent and mark-as-sent guidance, as the design's yellow-ruled aside. */
.sl-page dialog #sms-consent-note,
.sl-page dialog .plan-gate-note {
  border-left: 3px solid var(--sl-yellow);
  background: var(--sl-paper-alt);
  padding: 13px 15px;
  margin-top: 14px;
  color: var(--sl-ink-3);
}

/* The save-to-library caveat is a warning, and reads as one. */
.sl-page dialog .save-preset-warning {
  border-left: 3px solid var(--sl-red);
  border-radius: 0;
  background: #f3dcd7;
  color: #7a2a1c;
  padding: 14px 16px;
  font: 400 13px/1.6 var(--sl-display-font);
}

.sl-page dialog .plan-limit-notice,
.sl-page dialog .invoice-warning {
  border-radius: 0;
  border-left: 3px solid var(--sl-red);
  background: #f3dcd7;
  color: #7a2a1c;
}

.sl-page dialog .delivery-mode-banner {
  border-radius: 0;
  border-left: 3px solid var(--sl-yellow);
  background: var(--sl-paper-alt);
  color: var(--sl-ink-2);
  margin: 20px 0 0;
  font: 600 13px/1.5 var(--sl-display-font);
}

/* app.js writes these only when there is something to say. Empty, they were rendering as
 * a bare ruled bar across the panel. */
.sl-page dialog .delivery-mode-banner:empty,
.sl-page dialog .invoice-warning:empty,
.sl-page dialog .copy-status:empty,
.sl-page dialog .form-intro:empty,
.sl-page dialog .share-summary:empty,
.sl-page dialog .save-preset-preview:empty {
  display: none;
}

.sl-page dialog .save-preset-preview {
  border-radius: 0;
  border: 1px solid var(--sl-rule);
  background: var(--sl-card);
}

.sl-page dialog .share-summary {
  border-radius: 0;
  margin: 0 34px;
}

/* Semantic colour on the two invoice outcomes, as the design draws them: a correction is
 * marked at the top edge before you read a word of it, and recording payment is the one
 * genuinely good-news action in the set. */
.sl-page dialog#invoice-reopen-dialog {
  border-top: 5px solid var(--sl-red);
}

.sl-page dialog#invoice-reopen-dialog .dialog-header .eyebrow {
  color: var(--sl-red);
}

.sl-page dialog#invoice-paid-dialog .dialog-actions .button-primary {
  background: var(--sl-green);
}

/* ---------------------------------------------- settings: dark sidebar --
 * The design puts the section nav in a dark rail down the left. The existing markup is a
 * horizontal tab strip followed by four sibling panels, so this is a grid placement rather
 * than a restructure: the tablist spans every row of column one, the header and the panels
 * stack in column two. role="tablist" and the feature-gated `hidden` tabs are untouched. */
/* [open] is load-bearing, not decoration. A closed <dialog> is hidden by `display: none`
 * in the UA stylesheet, and ANY author `display` beats it — so an unqualified
 * `display: grid` here rendered the whole settings panel inline at the foot of the page on
 * every load. Same reason the auth rule below is qualified. */
.sl-page dialog.settings-dialog[open] {
  width: min(980px, calc(100% - 28px));
  display: grid;
  grid-template-columns: 240px minmax(0, 1fr);
  /* Rows are declared EXPLICITLY so the rail can span them. `grid-row: 1 / -1` resolves
   * -1 against the last explicit row line, and with only implicit rows that collapsed the
   * rail into the header row — the dark column stopped a fifth of the way down.
   *
   * All four panels share row 2 because they are tab panels: app.js keeps exactly one
   * un-hidden, and a display:none sibling is not a grid item, so they cannot collide. */
  grid-template-rows: auto minmax(0, 1fr);
}

.sl-page dialog.settings-dialog .dialog-header {
  grid-column: 2;
  grid-row: 1;
}

.sl-page dialog.settings-dialog > section {
  grid-column: 2;
  grid-row: 2;
}

.sl-page dialog.settings-dialog .settings-tabs {
  grid-column: 1;
  grid-row: 1 / -1;
  display: flex;
  flex-direction: column;
  flex-wrap: nowrap;
  align-content: start;
  gap: 2px;
  padding: 30px 20px;
  border-bottom: 0;
  border-right: 1px solid var(--sl-ink);
  background: var(--sl-ink);
}

.sl-page dialog.settings-dialog .settings-tab {
  flex: 0 0 auto;
  /* 44px preserved: this is the nav on a touch screen. */
  min-height: 44px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--sl-dark-mute);
  font: 600 15px/1.3 var(--sl-display-font);
  text-align: left;
  padding: 11px 12px;
}

.sl-page dialog.settings-dialog .settings-tab:hover {
  color: var(--sl-paper);
}

.sl-page dialog.settings-dialog .settings-tab.is-active,
.sl-page dialog.settings-dialog .settings-tab[aria-selected="true"] {
  background: var(--sl-yellow);
  color: var(--sl-ink);
  font-weight: 700;
}

.sl-page dialog.settings-dialog > section {
  padding: 30px 34px;
}

.sl-page dialog.settings-dialog .settings-panel-heading .eyebrow {
  font: 500 10px/1 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

/* Plan and usage, as the design's bordered card with the yellow offset. */
.sl-page dialog #billing-section {
  border: 1px solid var(--sl-ink);
  background: var(--sl-card);
  box-shadow: 5px 5px 0 var(--sl-yellow);
  padding: 26px;
  margin-top: 24px;
}

.sl-page dialog .checkout-plan-summary {
  border-radius: 0;
  border: 1px solid var(--sl-rule);
  background: var(--sl-paper);
}

/* ------------------------------------------------------- saved library -- */
.sl-page dialog .library-controls {
  padding: 0 34px;
  margin-top: 4px;
}

.sl-page dialog .library-list {
  padding: 0 34px 30px;
  border-top: 0;
}

.sl-page dialog .library-list > * {
  border-radius: 0;
}

/* --------------------------------------------------------- responsive -- */
@media (max-width: 900px) {
  /* The dark rail becomes a strip again — a 240px sidebar plus a form does not fit. */
  .sl-page dialog.settings-dialog[open] {
    grid-template-columns: minmax(0, 1fr);
  }

  .sl-page dialog.settings-dialog .dialog-header,
  .sl-page dialog.settings-dialog > section {
    grid-column: 1;
  }

  .sl-page dialog.settings-dialog .settings-tabs {
    grid-column: 1;
    grid-row: auto;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 8px;
    padding: 14px 20px;
    border-right: 0;
    border-bottom: 1px solid var(--sl-ink);
  }

  .sl-page dialog.settings-dialog .settings-tab {
    flex: 1 1 90px;
    text-align: center;
  }
}

@media (max-width: 760px) {
  .sl-page dialog .dialog-header,
  .sl-page dialog .dialog-section,
  .sl-page dialog.settings-dialog > section {
    padding-left: 20px;
    padding-right: 20px;
  }

  .sl-page dialog .library-controls,
  .sl-page dialog .library-list {
    padding-left: 20px;
    padding-right: 20px;
  }

  /* Restates styles.css's mobile stacking, which these higher-specificity selectors
   * would otherwise win against: the primary action ends up on top, full width. */
  .sl-page dialog .dialog-actions,
  .sl-page dialog .share-dialog-actions {
    flex-direction: column-reverse;
    align-items: stretch;
    padding: 24px 20px 26px;
  }

  .sl-page dialog .dialog-actions .button,
  .sl-page dialog .dialog-actions .row-button {
    width: 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .sl-page dialog[open] {
    animation: none;
  }
}

/* ========================================== app: register + flows (2a-2g) ==
 * ScopeLock App.dc.html, the parts that live in index.html: the sign-in dialog (2a), the
 * quick-variation flow (2b-2d) and the variation register (2g).
 *
 * 2e (what the customer sees) is approval.html, a different surface on a different
 * stylesheet, and is deliberately not in here. 2f is not a page at all — it is the
 * post-approval state, which already exists as the share modal's signed-record section.
 *
 * Same discipline as the modals: restyle the grammar app.js already renders into, invent no
 * data, and keep every id and class the application binds to.
 */

/* --------------------------------------------------- 2a sign in / create --
 * The design's split panel: a dark reassurance rail beside the form. Placed with explicit
 * grid rows for the same reason as the settings rail — `1 / -1` needs explicit row lines to
 * resolve against. The four auth forms share one row because exactly one is ever un-hidden.
 *
 * The design's testimonial is NOT reproduced: it is labelled
 * "QUOTE PLACEHOLDER — SWAP FOR A REAL BETA USER" in the source, and a fabricated customer
 * quote on a signup screen is not a placeholder we can ship. The rail carries the one claim
 * that is actually true and already documented.
 */
@media (min-width: 900px) {
  .sl-page dialog#auth-dialog[open] {
    width: min(940px, calc(100% - 28px));
    display: grid;
    grid-template-columns: 340px minmax(0, 1fr);
    grid-template-rows: auto auto auto minmax(0, 1fr) auto;
  }

  .sl-auth-rail {
    grid-column: 1;
    grid-row: 1 / -1;
    display: flex;
    flex-direction: column;
    /* flex-start, not space-between: the design balanced the brand against a testimonial at
     * the foot of the rail, and with that placeholder quote left out `space-between` dropped
     * the headline to the bottom edge with a void above it. */
    justify-content: flex-start;
    gap: 56px;
    padding: 40px 34px;
    background: var(--sl-ink);
    color: var(--sl-paper);
    border-right: 1px solid var(--sl-ink);
    /* Ruled like the hero, at the design's 44px rhythm. */
    background-image: linear-gradient(rgba(255, 255, 255, 0.045) 1px, transparent 1px);
    background-size: 100% 44px;
  }

  .sl-page dialog#auth-dialog .dialog-header {
    grid-column: 2;
    grid-row: 1;
  }

  .sl-page dialog#auth-dialog .auth-tabs {
    grid-column: 2;
    grid-row: 2;
  }

  .sl-page dialog#auth-dialog .login-deletion-state {
    grid-column: 2;
    grid-row: 3;
  }

  .sl-page dialog#auth-dialog .auth-form {
    grid-column: 2;
    grid-row: 4;
  }

  .sl-page dialog#auth-dialog .auth-support-links {
    grid-column: 2;
    grid-row: 5;
  }
}

/* Below 900px the rail would push the password field off a phone screen, so the form stands
 * alone — which is all a returning user came for.
 *
 * An explicit max-width query, NOT a bare `.sl-auth-rail { display: none }`. A bare rule ties
 * the `display: flex` inside the min-width block on specificity and, sitting later in the
 * file, won at every width — the rail was hidden on desktop too. Two disjoint queries cannot
 * be resolved by source order. */
@media (max-width: 899px) {
  .sl-auth-rail {
    display: none;
  }
}

.sl-auth-rail-brand {
  display: flex;
  align-items: baseline;
  gap: 10px;
  font: 800 22px/1 var(--sl-display-font);
  letter-spacing: -0.02em;
}

.sl-auth-rail-brand span {
  font: 500 10px/1 var(--sl-mono-font);
  letter-spacing: 0.14em;
  color: var(--sl-ink-5);
  border: 1px solid var(--sl-dark-rule);
  padding: 4px 6px;
}

.sl-auth-rail h2 {
  margin: 0;
  font: 800 clamp(26px, 2.6vw, 34px) / 1.05 var(--sl-display-font);
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.sl-auth-rail p {
  margin: 20px 0 0;
  font: 400 16px/1.6 var(--sl-display-font);
  color: var(--sl-dark-mute);
}

/* The tab pair, as the design's hard-edged split button. */
.sl-page dialog#auth-dialog .auth-tabs {
  display: flex;
  gap: 0;
  border: 1px solid var(--sl-ink);
  margin: 24px 34px 0;
  padding: 0;
  background: var(--sl-paper);
}

.sl-page dialog#auth-dialog .auth-tabs .filter {
  flex: 1;
  min-height: 48px;
  border: 0;
  border-radius: 0;
  background: var(--sl-paper);
  color: var(--sl-ink);
  font: 700 14px/1 var(--sl-display-font);
}

.sl-page dialog#auth-dialog .auth-tabs .filter + .filter {
  border-left: 1px solid var(--sl-ink);
}

.sl-page dialog#auth-dialog .auth-tabs .filter.active,
.sl-page dialog#auth-dialog .auth-tabs .filter[aria-selected="true"] {
  background: var(--sl-ink);
  color: var(--sl-paper);
}

.sl-page dialog#auth-dialog .auth-policy-note,
.sl-page dialog#auth-dialog .auth-support-links {
  margin-inline: 34px;
}

.sl-page dialog#auth-dialog .auth-policy-note {
  margin-top: 20px;
  font: 400 14px/1.6 var(--sl-display-font);
  color: var(--sl-ink-4);
}

.sl-page dialog#auth-dialog .auth-support-links {
  display: flex;
  flex-wrap: wrap;
  gap: 18px;
  padding: 0 0 28px;
  font: 500 13px/1 var(--sl-display-font);
}

/* ------------------------------------------- 2b-2d quick variation flow -- */
.sl-page dialog.quick-dialog .quick-header {
  padding: 24px 26px 20px;
}

/* Three segments that fill as you advance, replacing the numbered list chrome. */
.sl-page dialog.quick-dialog .quick-progress {
  display: flex;
  gap: 5px;
  margin: 0;
  padding: 18px 26px 0;
  list-style: none;
  counter-reset: none;
}

.sl-page dialog.quick-dialog .quick-progress li {
  flex: 1;
  min-width: 0;
  height: 4px;
  padding: 0;
  border: 0;
  border-radius: 0;
  background: var(--sl-rule);
  /* The step names are still the accessible label for each segment; only the visual is a
   * bar. Clipped rather than removed so the list keeps its text alternative. */
  overflow: hidden;
  text-indent: -9999px;
  white-space: nowrap;
}

.sl-page dialog.quick-dialog .quick-progress li[aria-current="step"],
.sl-page dialog.quick-dialog .quick-progress li.is-complete {
  background: var(--sl-ink);
}

/* Each <li> also holds a numbered badge span that styles.css draws as a circle. text-indent
 * hides the step's text but not that span, which was still rendering as a bubble on top of
 * the bar. */
.sl-page dialog.quick-dialog .quick-progress li span {
  display: none;
}

.sl-page dialog.quick-dialog .quick-save-status {
  margin: 14px 26px 0;
  font: 500 10px/1.5 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

/* A parenthetical inside a field caption — "optional for email or link" — reads as part of
 * the label, not as a second shouted caption. It is marked up as <em>, not a span. */
.sl-page dialog label span,
.sl-page dialog label em {
  text-transform: none;
  letter-spacing: normal;
  color: var(--sl-ink-5);
  font-weight: 400;
  font-style: normal;
}

/* The bottom value is not 0: it is the breathing room that keeps the last field of a step
 * off the sticky action bar, plus the travel that field needs to clear the bar once the
 * bar has ridden up over an on-screen keyboard. See the .quick-panel note in styles.css. */
.sl-page dialog.quick-dialog .quick-panel {
  padding: 22px 26px calc(24px + var(--kb-inset, 0px));
}

.sl-page dialog.quick-dialog .quick-panel-heading .eyebrow {
  font: 500 10px/1.5 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
  margin: 0 0 10px;
}

.sl-page dialog.quick-dialog .quick-panel-heading h3 {
  margin: 0 0 6px;
  font: 800 clamp(24px, 3vw, 30px) / 1.1 var(--sl-display-font);
  letter-spacing: -0.03em;
}

.sl-page dialog.quick-dialog .quick-panel-heading p {
  margin: 0 0 22px;
  font: 400 15px/1.5 var(--sl-display-font);
  color: var(--sl-ink-3);
}

.sl-page dialog.quick-dialog .quick-panel label {
  margin-top: 16px;
}

/* The step footer sits on the paper, ruled off, with the forward action dominant. */
.sl-page dialog.quick-dialog .quick-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 26px 26px;
  margin-top: 24px;
  border-top: 1px solid var(--sl-rule);
  background: var(--sl-paper);
}

.sl-page dialog.quick-dialog .quick-actions > div {
  display: flex;
  gap: 12px;
  flex: 1;
  justify-content: flex-end;
}

/* The compact bar, restated on the brand layer because the rule above outranks the structural
 * one in styles.css and would otherwise keep the 16/26/26 padding and the 24px margin whatever
 * styles.css asked for. This is also where the keyboard case gets its safe-area handling back:
 * the padding above is a flat three-value shorthand, so on `.sl-page` — which is every page a
 * tradie sees — the env()/--kb-inset arithmetic in styles.css has never applied at all.
 *
 * Why any of it: with the keyboard up, the bar is 95px of the 365px still visible on the
 * handset in the recording, and the 24px margin above it takes another. Trimmed to 8px padding
 * and 44px targets it is 61px, and Discard draft — hidden by styles.css, which is allowed to
 * because it selects on an id — takes its whole row with it.
 *
 * Back drops to its content width so the primary keeps the rest of the row: at half a 412px
 * screen "Send for approval" wraps to two lines and puts 15px back on the bar it just saved. */
.sl-page dialog.quick-dialog .quick-actions[data-compact] {
  gap: 10px;
  margin-top: 12px;
  padding: 8px 18px calc(8px + max(0px, env(safe-area-inset-bottom, 0px) - var(--kb-inset, 0px)));
}

.sl-page dialog.quick-dialog .quick-actions[data-compact] .button-primary,
.sl-page dialog.quick-dialog .quick-actions[data-compact] .button-secondary {
  min-height: 44px;
  padding: 12px 16px;
}

.sl-page dialog.quick-dialog .quick-actions[data-compact] .button-secondary {
  width: auto;
  flex: 0 0 auto;
}

/* The design draws this footer side by side on a 390px canvas, and styles.css instead stacks
 * it column-reverse with full-width 48px targets and a safe-area inset. The existing
 * behaviour is kept deliberately: on a real phone a full-width primary action with the
 * home-bar inset accounted for beats a cramped two-up row, and forcing the row back left
 * `.quick-actions > .text-button { width: 100% }` in play, which stretched "Discard draft" to
 * 336px and shoved the forward button off screen. Only the appearance is restyled here. */
@media (max-width: 760px) {
  .sl-page dialog.quick-dialog .quick-actions .text-button {
    font: 500 11px/1 var(--sl-mono-font);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    color: var(--sl-ink-4);
    text-decoration: none;
    min-height: 48px;
  }

  /* width:100% restates styles.css's mobile intent, which my higher-specificity appearance
   * rules were otherwise leaving at the button's content width. */
  .sl-page dialog.quick-dialog .quick-actions .button-primary,
  .sl-page dialog.quick-dialog .quick-actions .button-secondary {
    width: 100%;
    min-height: 48px;
    box-shadow: none;
  }

  /* The card button goes full width at the same breakpoint as the action bar it matches.
   * Its offset shadow is deliberately NOT cleared here: the rule above tries to, but the
   * same-specificity appearance rule further down the sheet wins on source order, so Send
   * for approval keeps its shadow on a phone in practice. Matching what actually renders
   * beats matching what that rule intended — the two buttons sit inches apart. */
  .sl-page dialog.quick-dialog .quick-card-required .button-primary {
    width: 100%;
    min-height: 48px;
  }
}

/* The pay-as-you-go card prompt, on the brand rather than on the workspace palette
 * styles.css gives it. It ships rounded, pale-green and lime-buttoned — the older
 * application treatment — and it renders directly above the black-and-yellow Send for
 * approval bar, so the two read as two different products stacked on one screen.
 * Squared, ruled in yellow on paper-alt: the same informational banner treatment as
 * .delivery-mode-banner above, because being asked for a card at a paid band is the
 * product working, not a warning. */
.sl-page dialog.quick-dialog .quick-card-required {
  border: 0;
  border-radius: 0;
  border-left: 3px solid var(--sl-yellow);
  background: var(--sl-paper-alt);
  gap: 10px;
  padding: 20px 22px;
}

.sl-page dialog.quick-dialog .quick-card-required:focus-visible {
  outline: 2px solid var(--sl-ink);
  outline-offset: 2px;
}

.sl-page dialog.quick-dialog .quick-card-required strong {
  font: 800 17px/1.3 var(--sl-display-font);
  letter-spacing: -0.01em;
  color: var(--sl-ink);
}

.sl-page dialog.quick-dialog .quick-card-required p {
  font: 400 14px/1.6 var(--sl-display-font);
  color: var(--sl-ink-3);
}

.sl-page dialog.quick-dialog .quick-card-required .form-error {
  font: 600 13px/1.5 var(--sl-display-font);
  color: var(--sl-red);
}

/* Deliberately identical to Send for approval directly beneath it. The card button is a
 * step in the same flow, not a second-tier action, and matching it is what makes the
 * prompt read as part of the screen instead of an interruption. */
.sl-page dialog.quick-dialog .quick-card-required .button-primary,
.sl-page dialog.quick-dialog .quick-actions .button-primary {
  border: 0;
  border-radius: 0;
  background: var(--sl-ink);
  color: var(--sl-paper);
  font: 700 16px/1 var(--sl-display-font);
  padding: 18px 24px;
  box-shadow: 4px 4px 0 var(--sl-yellow);
}

.sl-page dialog.quick-dialog .quick-actions .button-secondary {
  border: 1px solid var(--sl-rule);
  border-radius: 0;
  background: transparent;
  color: var(--sl-ink-3);
  font: 600 15px/1 var(--sl-display-font);
  padding: 18px 20px;
}

/* -------------------------------------------- 2g the variation register -- */
.sl-page .workspace {
  background: var(--sl-paper);
  border-top: 1px solid var(--sl-rule);
  padding-block: var(--sl-section-y);
}

.sl-page .workspace .eyebrow {
  font: 500 11px/1.5 var(--sl-mono-font);
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

.sl-page .workspace #workspace-title {
  margin: 20px 0 0;
  font: 800 clamp(30px, 3.4vw, 40px) / 1.05 var(--sl-display-font);
  letter-spacing: -0.03em;
  color: var(--sl-ink);
}

.sl-page .workspace #workspace-description {
  margin: 12px 0 0;
  font: 400 17px/1.5 var(--sl-display-font);
  color: var(--sl-ink-3);
}

/* Stat tiles: hairline-separated cells on a single rule, as drawn. Three, not the design's
 * four — "typical time to yes" is not a figure this product computes, and inventing a
 * metric to fill a grid cell is how a dashboard starts lying. */
.sl-page .workspace .stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 1px;
  background: var(--sl-rule);
  border: 1px solid var(--sl-rule);
  margin-top: 34px;
}

.sl-page .workspace .stats article {
  border: 0;
  border-radius: 0;
  background: var(--sl-card);
  padding: 24px 26px;
  box-shadow: none;
}

.sl-page .workspace .stats small {
  font: 500 10px/1.5 var(--sl-mono-font);
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--sl-ink-5);
}

.sl-page .workspace .stats strong {
  display: block;
  margin-top: 14px;
  font: 800 clamp(28px, 3vw, 38px) / 1 var(--sl-display-font);
  letter-spacing: -0.035em;
  color: var(--sl-ink);
  font-variant-numeric: tabular-nums;
}

/* Approved value is the good number, and reads as one. */
.sl-page .workspace .stats article:first-child strong {
  color: var(--sl-green);
}

.sl-page .workspace .stats span {
  display: block;
  margin-top: 8px;
  font: 400 13px/1.4 var(--sl-display-font);
  color: var(--sl-ink-4);
}

/* Filters as the design's underlined tab strip on a heavy rule. */
.sl-page .workspace .register-panel {
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  margin-top: 40px;
  padding: 0;
}

.sl-page .workspace .register-toolbar {
  align-items: center;
  padding: 0 0 14px;
  border-bottom: 2px solid var(--sl-ink);
  background: transparent;
}

.sl-page .workspace .filter-group {
  gap: 26px;
}

.sl-page .workspace .filter-group .filter {
  min-height: 44px;
  border: 0;
  border-radius: 0;
  background: transparent;
  color: var(--sl-ink-4);
  font: 500 15px/1 var(--sl-display-font);
  padding: 0 0 14px;
  margin-bottom: -16px;
  border-bottom: 3px solid transparent;
}

.sl-page .workspace .filter-group .filter.active,
.sl-page .workspace .filter-group .filter[aria-pressed="true"] {
  color: var(--sl-ink);
  font-weight: 700;
  border-bottom-color: var(--sl-yellow);
}

/* Register rows as the design's table: hairline-ruled, no card, a status stripe down the
 * left edge. app.js renders .variation-row unchanged. */
.sl-page .workspace .variation-list {
  display: block;
  gap: 0;
}

.sl-page .workspace .variation-row {
  border: 0;
  border-top: 1px solid var(--sl-rule);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  padding: 20px 0 20px 18px;
  align-items: center;
}

.sl-page .workspace .variation-row:last-child {
  border-bottom: 1px solid var(--sl-rule);
}

/* :has() keys the stripe off the status app.js already prints, so no extra attribute is
 * needed on the row. A row whose status is not one of these simply has no stripe. */
.sl-page .workspace .variation-row:has(.status-approved) {
  box-shadow: inset 4px 0 0 var(--sl-green);
  background: var(--sl-card);
}

.sl-page .workspace .variation-row:has(.status-declined) {
  box-shadow: inset 4px 0 0 var(--sl-red);
}

.sl-page .workspace .variation-row:has(.status-sent),
.sl-page .workspace .variation-row:has(.status-viewed) {
  box-shadow: inset 4px 0 0 var(--sl-yellow);
}

.sl-page .workspace .variation-number {
  font: 600 13px/1 var(--sl-mono-font);
  letter-spacing: 0.04em;
  color: var(--sl-ink-3);
}

.sl-page .workspace .variation-job strong {
  font: 700 17px/1.2 var(--sl-display-font);
  letter-spacing: -0.01em;
  color: var(--sl-ink);
}

.sl-page .workspace .variation-job span {
  display: block;
  margin-top: 5px;
  font: 400 14px/1.4 var(--sl-display-font);
  color: var(--sl-ink-4);
}

.sl-page .workspace .variation-amount {
  font: 700 18px/1 var(--sl-display-font);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.sl-page .workspace .variation-time {
  font: 400 14px/1.4 var(--sl-display-font);
  color: var(--sl-ink-4);
}

/* Status as a mono flag rather than a pill — the stripe already carries the colour. */
.sl-page .workspace .variation-status .status {
  border: 0;
  border-radius: 0;
  background: transparent;
  padding: 0;
  font: 700 11px/1.4 var(--sl-mono-font);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.sl-page .workspace .variation-status .status i {
  display: none;
}

.sl-page .workspace .variation-status .status-approved {
  color: var(--sl-green);
}

.sl-page .workspace .variation-status .status-declined {
  color: var(--sl-red);
}

.sl-page .workspace .variation-status .status-sent,
.sl-page .workspace .variation-status .status-viewed {
  color: #8a6e00;
}

.sl-page .workspace .variation-actions .row-button {
  border-radius: 0;
  border: 1px solid var(--sl-rule);
  background: var(--sl-card);
  color: var(--sl-ink-3);
  font: 600 13px/1 var(--sl-display-font);
}

.sl-page .workspace .variation-actions .row-button.primary {
  border-color: var(--sl-ink);
  background: var(--sl-ink);
  color: var(--sl-paper);
}

.sl-page .workspace .empty-row {
  border: 1px dashed var(--sl-rule);
  border-radius: 0;
  background: var(--sl-card);
}

.sl-page .workspace .empty-row strong {
  font: 700 19px/1.2 var(--sl-display-font);
  letter-spacing: -0.015em;
}

/* styles.css gives .text-button `display: inline-block` with `min-height: 44px` and an
 * underline. The text then sits at the top of the 44px box with its underline a third of the
 * way down, which next to adjacent text reads as a strike-through rather than a link.
 * inline-flex centres the label in the same 44px target, so the underline sits where an
 * underline should. */
.sl-page .workspace .text-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 0;
  font: 500 11px/1 var(--sl-mono-font);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
  /* No persistent underline. In a 44px inline-block box the underline landed mid-word and
   * read as a strike-through — "Refresh" looked disabled. The design sets these secondary
   * actions as plain mono labels, so the underline moves to hover where it belongs. */
  text-decoration: none;
}

.sl-page .workspace .text-button:hover {
  color: var(--sl-ink);
  text-decoration: underline;
  text-underline-offset: 4px;
}

/* The signed-in account bar, squared off to match. */
.sl-page .workspace .workspace-account-bar {
  border-radius: 0;
  border: 1px solid var(--sl-rule);
  background: var(--sl-card);
  box-shadow: none;
}

.sl-page .workspace .workspace-account-bar .row-button,
.sl-page .workspace .workspace-create-actions .button {
  border-radius: 0;
}

/* EVERY primary button in the workspace, not just the ones in the markup.
 *
 * This was scoped to .workspace-create-actions and missed #register-workspace — the
 * "Start free — no card" call to action that app.js renders into the empty register for a
 * signed-out visitor. It kept styles.css's `background: var(--lime)` and came out
 * chartreuse-on-black in the middle of the paper-and-yellow page.
 *
 * Scoping to the container rather than to specific buttons is the point: app.js renders
 * several buttons into this section at runtime, and a rule that names them individually is
 * a rule that will be wrong again the next time one is added. */
.sl-page .workspace .button-primary {
  border: 0;
  border-radius: 0;
  background: var(--sl-ink);
  color: var(--sl-paper);
  font: 700 15px/1 var(--sl-display-font);
  box-shadow: 4px 4px 0 var(--sl-yellow);
}

.sl-page .workspace .button-primary:hover:not(:disabled) {
  background: var(--sl-ink);
  color: var(--sl-paper);
  transform: translate(2px, 2px);
  box-shadow: 2px 2px 0 var(--sl-yellow);
}

.sl-page .workspace .button-secondary {
  border: 1px solid var(--sl-rule);
  border-radius: 0;
  background: transparent;
  color: var(--sl-ink-3);
  font: 600 15px/1 var(--sl-display-font);
}

/* The signed-out onboarding block sits where the register will be. */
.sl-page .workspace .empty-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 14px 20px;
  margin-top: 20px;
}

.sl-page .workspace .empty-row p {
  margin: 10px 0 0;
  font: 400 16px/1.6 var(--sl-display-font);
  color: var(--sl-ink-3);
}

/* The skip link is the first thing a keyboard user ever sees on this page, and it was still
 * on the old lime. It is only visible on focus, which is exactly why it went unnoticed. */
.sl-page .skip-link {
  border-radius: 0;
  background: var(--sl-yellow);
  color: var(--sl-ink);
  font: 700 14px/1 var(--sl-display-font);
}

/* Any other .filter in the workspace — the dashboard's Daily/Weekly interval pair, and
 * whatever else a feature flag reveals. The register's own .filter-group is styled above as
 * an underlined tab strip; these are standalone toggles, so they take the design's
 * hard-edged segmented treatment instead.
 *
 * Broad on purpose, for the same reason as the primary-button rule: these controls appear
 * behind feature flags and in app-rendered markup, so naming them one at a time guarantees
 * missing the next one. The Daily/Weekly pair was still on the old lime because of exactly
 * that — it sits outside .filter-group and is hidden until the dashboard feature is on. */
.sl-page .workspace .filter:not(.filter-group .filter) {
  min-height: 44px;
  border: 1px solid var(--sl-rule);
  border-radius: 0;
  background: var(--sl-card);
  color: var(--sl-ink-3);
  font: 600 14px/1 var(--sl-display-font);
}

.sl-page .workspace .filter:not(.filter-group .filter).active,
.sl-page .workspace .filter:not(.filter-group .filter)[aria-pressed="true"] {
  border-color: var(--sl-ink);
  background: var(--sl-yellow);
  color: var(--sl-ink);
  font-weight: 700;
}

/* ═══════════════════════════════════════════════════ 01 offline capture ══
 *
 * The marquee offline section. Dark band, matching the pricing section below it — the
 * design's two dark bands are its emphasis device, and this is the other claim on the page
 * worth that weight.
 *
 * The two phones reuse .sl-phone-shell / .sl-phone-screen from the hero, but NOT .sl-frame:
 * that class is absolutely positioned at opacity 0 and driven by the hero's keyframes.
 * These screens are static, so they get .sl-offline-frame instead — same typography
 * primitives (.sl-frame-kicker, .sl-frame-label, .sl-frame-job), ordinary flow layout.
 */

.sl-offline .sl-lede {
  max-width: 720px;
}

/* ── the three-beat flow ── */
.sl-offline-flow {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin: 40px 0 0;
  padding: 0;
  list-style: none;
  counter-reset: none;
}

.sl-offline-flow li {
  position: relative;
  flex: 1 1 200px;
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: 12px;
  align-items: center;
  padding: 16px 18px;
  border: 1px solid var(--sl-dark-rule);
  background: rgba(255, 255, 255, 0.03);
}

.sl-offline-flow strong {
  font: 700 15px/1.2 var(--sl-display-font);
  color: var(--sl-paper);
}

.sl-offline-flow span:last-child {
  grid-column: 2;
  font: 400 12px/1.4 var(--sl-display-font);
  color: var(--sl-dark-mute);
}

.sl-offline-flow-mark {
  grid-row: 1 / 3;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--sl-yellow);
  color: var(--sl-ink);
  font-size: 14px;
  line-height: 1;
}

/* The arrows between the beats. Pure decoration, so they are drawn with ::after on the
   first two items rather than sitting in the markup as characters a screen reader reads.
   Absolutely positioned against the li, which is why that rule carries position: relative. */
.sl-offline-flow li:not(:last-child)::after {
  content: "→";
  position: absolute;
  inset-inline-end: -13px;
  top: 50%;
  transform: translateY(-50%);
  color: var(--sl-yellow);
  font-size: 15px;
  line-height: 1;
}

/* ── the two phones ── */
.sl-offline-demo {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 32px;
  margin-top: 56px;
}

.sl-offline-phone {
  margin: 0;
  width: min(300px, 84vw);
}

.sl-offline-cap {
  margin-bottom: 12px;
  font: 500 10px/1.4 var(--sl-mono-font);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sl-dark-mute);
  text-align: center;
}

.sl-offline-bars {
  color: var(--sl-yellow);
  letter-spacing: 0;
}

.sl-offline-bars-none {
  color: var(--sl-red);
}

.sl-offline-screen {
  height: auto;
  min-height: 430px;
}

.sl-offline-frame {
  display: flex;
  flex-direction: column;
  gap: 13px;
  padding: 22px 18px;
}

.sl-offline-frame p {
  margin: 0;
}

.sl-offline-scope {
  margin-top: 5px;
  padding-bottom: 7px;
  border-bottom: 1px dashed var(--sl-rule-strong);
  font: 600 15px/1.35 var(--sl-hand-font);
  font-size: 17px;
  color: var(--sl-pen);
}

.sl-offline-photos {
  display: flex;
  align-items: center;
  gap: 6px;
}

.sl-offline-photos span {
  width: 30px;
  height: 30px;
  border-radius: 4px;
  border: 1px solid var(--sl-rule-strong);
  background: linear-gradient(135deg, #d8d3c6, #eceadf);
}

.sl-offline-photos em {
  font: 400 10px/1 var(--sl-mono-font);
  font-style: normal;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--sl-ink-5);
}

.sl-offline-badge {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: auto;
  padding: 9px 11px;
  border-radius: 6px;
  font: 700 11px/1.3 var(--sl-mono-font);
  letter-spacing: 0.04em;
}

.sl-offline-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

.sl-offline-badge-queued {
  border: 1px solid #e6b457;
  background: #fff5df;
  color: #784b05;
}

.sl-offline-badge-sent {
  border: 1px solid var(--sl-green);
  background: #eaf6ef;
  color: var(--sl-green);
}

.sl-offline-badge-pdf {
  border: 1px dashed var(--sl-rule-strong);
  color: var(--sl-ink-4);
  font-size: 10px;
}

.sl-offline-note {
  padding: 10px 11px;
  border-inline-start: 3px solid var(--sl-green);
  background: #f3f8f4;
}

.sl-offline-note-quiet {
  border-inline-start-color: var(--sl-rule-strong);
  background: var(--sl-paper-alt);
}

.sl-offline-note-head {
  font: 700 10px/1 var(--sl-mono-font);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--sl-ink-4);
}

.sl-offline-note-body {
  margin-top: 5px;
  font: 400 12px/1.45 var(--sl-display-font);
  color: var(--sl-ink-2);
}

.sl-offline-arrow {
  display: grid;
  justify-items: center;
  gap: 6px;
  margin: 0;
  font-size: 30px;
  line-height: 1;
  color: var(--sl-yellow);
}

.sl-offline-arrow span {
  font: 500 9px/1 var(--sl-mono-font);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sl-dark-mute);
}

/* ── proof and technical detail ── */
.sl-offline-proof {
  display: grid;
  gap: 22px;
  margin-top: 56px;
  padding-top: 34px;
  border-top: 1px solid var(--sl-dark-rule);
}

.sl-offline-stat {
  margin: 0;
  max-width: 640px;
  font: 400 15px/1.6 var(--sl-display-font);
  color: var(--sl-dark-mute);
}

.sl-offline-stat strong {
  color: var(--sl-paper);
  font-weight: 700;
}

.sl-offline-facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 10px 28px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.sl-offline-facts li {
  position: relative;
  padding-inline-start: 20px;
  font: 400 14px/1.5 var(--sl-display-font);
  color: var(--sl-dark-mute);
}

.sl-offline-facts li::before {
  content: "✓";
  position: absolute;
  inset-inline-start: 0;
  color: var(--sl-yellow);
  font-weight: 700;
}

.sl-offline-quote {
  margin: 34px 0 0;
  max-width: 640px;
  padding-inline-start: 20px;
  border-inline-start: 3px solid var(--sl-yellow);
}

.sl-offline-quote[hidden] {
  display: none;
}

.sl-offline-quote blockquote {
  margin: 0;
}

.sl-offline-quote p {
  margin: 0;
  font: 600 22px/1.4 var(--sl-hand-font);
  color: var(--sl-paper);
}

.sl-offline-quote figcaption {
  margin-top: 10px;
  font: 500 10px/1 var(--sl-mono-font);
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--sl-dark-mute);
}

/* ── phones ──
 * One phone, not two side by side. The second phone and the arrow are removed and the
 * remaining phone carries the whole story: the queued badge is joined by the sent one, so
 * a reader on a 360px screen still sees the before AND the after without scrolling
 * sideways through two 300px mockups. */
@media (max-width: 860px) {
  .sl-offline-demo {
    flex-direction: column;
    gap: 20px;
    margin-top: 40px;
  }

  .sl-offline-arrow {
    font-size: 24px;
    transform: rotate(90deg);
  }

  .sl-offline-arrow span {
    display: none;
  }

  .sl-offline-phone-after .sl-offline-screen {
    min-height: 0;
  }
}

@media (max-width: 560px) {
  .sl-offline-flow {
    margin-top: 30px;
  }

  .sl-offline-flow li {
    flex-basis: 100%;
  }

  /* Stacked, so a horizontal arrow in the right margin points at nothing. */
  .sl-offline-flow li:not(:last-child)::after {
    content: none;
  }

  .sl-offline-proof {
    margin-top: 40px;
  }
}
