/* ---------------------------------------------------------------
 * Ishmael website — base stylesheet.
 *
 * Two-theme model mirroring the editor app: Catppuccin Latte (light)
 * and Mocha (dark). data-theme on <html> drives the swap; pre-paint
 * script in base.njk sets it before first frame.
 * ------------------------------------------------------------- */

/* ---- Fonts ---------------------------------------------------- */
@font-face {
  font-family: "Inter";
  src: url("/assets/fonts/InterVariable.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Inter";
  src: url("/assets/fonts/InterVariable-Italic.woff2") format("woff2-variations");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: "Literata";
  src: url("/assets/fonts/Literata.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: "Literata";
  src: url("/assets/fonts/Literata-Italic.woff2") format("woff2-variations");
  font-weight: 200 900;
  font-style: italic;
  font-display: swap;
}

/* ---- Theme tokens -------------------------------------------- */
:root {
  /* Catppuccin Latte — full named palette. Use the named tokens
     (--ctp-mauve etc.) when picking an accent for a one-off rule;
     reach for the role tokens (--accent, --text…) for anything
     that should track the brand globally. */
  --ctp-rosewater: #dc8a78;
  --ctp-flamingo:  #dd7878;
  --ctp-pink:      #ea76cb;
  --ctp-mauve:     #8839ef;
  --ctp-red:       #d20f39;
  --ctp-maroon:    #e64553;
  --ctp-peach:     #fe640b;
  --ctp-yellow:    #df8e1d;
  --ctp-green:     #40a02b;
  --ctp-teal:      #179299;
  --ctp-sky:       #04a5e5;
  --ctp-sapphire:  #209fb5;
  --ctp-blue:      #1e66f5;
  --ctp-lavender:  #7287fd;

  --bg: #eff1f5;
  --bg-elevated: #e6e9ef;
  --surface: #ccd0da;
  --rule: #bcc0cc;
  --text: #4c4f69;
  --text-dim: #6c6f85;
  --accent: var(--ctp-mauve);          /* matches the app's brand */
  --accent-hover: #7a2ee0;
  --link: var(--ctp-mauve);
  --link-hover: var(--ctp-blue);

  /* Soft lift under content images in prose. Deeper in Mocha, where a
     black shadow would otherwise vanish into the dark background. */
  --img-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 6px 18px rgba(0, 0, 0, 0.12);

  --font-ui: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-serif: "Literata", Georgia, "Times New Roman", serif;
  --max-measure: 38rem;

  color-scheme: light;
}

/* Mocha palette. Reached two ways:
   1. Explicit pick — data-theme="dark" on <html>, set by the toggle
      or restored by the pre-paint script from localStorage.
   2. JS-disabled OS-prefers-dark — :root:not([data-theme]) inside
      the prefers-color-scheme media query below.
   Token names mirror the Latte block so anything reaching for
   var(--ctp-mauve) etc. swaps automatically. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]):not([data-theme="dark"]) {
    --ctp-rosewater: #f5e0dc;
    --ctp-flamingo:  #f2cdcd;
    --ctp-pink:      #f5c2e7;
    --ctp-mauve:     #cba6f7;
    --ctp-red:       #f38ba8;
    --ctp-maroon:    #eba0ac;
    --ctp-peach:     #fab387;
    --ctp-yellow:    #f9e2af;
    --ctp-green:     #a6e3a1;
    --ctp-teal:      #94e2d5;
    --ctp-sky:       #89dceb;
    --ctp-sapphire:  #74c7ec;
    --ctp-blue:      #89b4fa;
    --ctp-lavender:  #b4befc;

    --bg: #1e1e2e;
    --bg-elevated: #181825;
    --surface: #313244;
    --rule: #45475a;
    --text: #cdd6f4;
    --text-dim: #bac2de;
    --accent-hover: #d4b8ff;
    --img-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 10px 26px rgba(0, 0, 0, 0.5);

    color-scheme: dark;
  }
}
:root[data-theme="dark"] {
  --ctp-rosewater: #f5e0dc;
  --ctp-flamingo:  #f2cdcd;
  --ctp-pink:      #f5c2e7;
  --ctp-mauve:     #cba6f7;
  --ctp-red:       #f38ba8;
  --ctp-maroon:    #eba0ac;
  --ctp-peach:     #fab387;
  --ctp-yellow:    #f9e2af;
  --ctp-green:     #a6e3a1;
  --ctp-teal:      #94e2d5;
  --ctp-sky:       #89dceb;
  --ctp-sapphire:  #74c7ec;
  --ctp-blue:      #89b4fa;
  --ctp-lavender:  #b4befc;

  --bg: #1e1e2e;
  --bg-elevated: #181825;
  --surface: #313244;
  --rule: #45475a;
  --text: #cdd6f4;
  --text-dim: #bac2de;
  --accent-hover: #d4b8ff;
  --img-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 10px 26px rgba(0, 0, 0, 0.5);

  color-scheme: dark;
}

/* ---- Base elements ------------------------------------------- */
*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  /* Reserve the scrollbar's space at all times. Otherwise, locking the
     page scroll while an image is zoomed (body.has-zoom { overflow:
     hidden }) removes a classic, space-taking scrollbar, widening the
     viewport and shifting the centred content — and the fixed theme
     toggle — sideways. With the gutter always reserved the viewport
     width is invariant, so nothing moves. Overlay scrollbars (macOS
     default) reserve no space and are unaffected either way. */
  scrollbar-gutter: stable;
}
html, body {
  margin: 0;
  padding: 0;
}

body {
  background: var(--bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: 1.05rem;
  line-height: 1.65;
  font-optical-sizing: auto;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
}

a {
  color: var(--link);
  /* Quiet links: colour carries them at rest, the underline appears
     on hover. */
  text-decoration: none;
}
a:hover {
  color: var(--link-hover);
  text-decoration: underline;
  text-underline-offset: 0.18em;
  text-decoration-thickness: 1px;
}

h1, h2, h3, h4 {
  font-family: var(--font-ui);
  color: var(--text);
  line-height: 1.25;
  letter-spacing: -0.01em;
}
h1 { font-size: 2.4rem; margin: 0 0 0.75rem; }
h2 { font-size: 1.6rem; margin: 2rem 0 0.75rem; }
h3 { font-size: 1.2rem; margin: 1.5rem 0 0.5rem; }
p  { margin: 0 0 1rem; }

/* Ease the whole type scale down on small screens — the desktop sizes
   read as oversized on a phone. Because every size is in rem, headings,
   prose, and UI all step down together and keep their proportions. Media
   query breakpoints resolve against the initial 16px, not this value, so
   they don't shift. */
@media (max-width: 40rem) {
  html { font-size: 90%; } /* 14.4px */
}
@media (max-width: 30rem) {
  html { font-size: 84.375%; }  /* 13.5px */
}

code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.92em;
  background: var(--bg-elevated);
  border: 1px solid var(--rule);
  border-radius: 3px;
  padding: 0.05em 0.35em;
}

/* ---- Theme toggle ------------------------------------------- */
/* Floating top-right pill. The sun/moon glyphs swap via CSS keyed
   off the data-theme attribute, so the only thing the JS does is
   flip that attribute. */
/* Site header — quiet chrome for a developer audience: brand mark,
   two links, the theme toggle. No sticky, no shadow, no CTA. */
.site-header {
  width: 100%;
  max-width: 64rem;
  margin: 0 auto;
  padding: 1.1rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-family: var(--font-ui);
}
.site-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-weight: 600;
  font-size: 1rem;
  color: var(--text);
  text-decoration: none;
}
.site-brand img {
  border-radius: 22%;
  display: block;
}
.site-nav {
  display: inline-flex;
  align-items: center;
  gap: 1.4rem;
  font-size: 0.9rem;
}
.site-nav a {
  color: var(--text-dim);
  text-decoration: none;
}
.site-nav a:hover {
  color: var(--text);
}
/* In-header variant: the toggle sits in the nav row, not fixed to
   the viewport corner. */
.theme-toggle--header {
  position: static;
}

.theme-toggle {
  position: fixed;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 10;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  padding: 0;
  border: 1px solid var(--rule);
  background: var(--bg-elevated);
  color: var(--text-dim);
  border-radius: 999px;
  cursor: pointer;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08);
}
.theme-toggle:hover {
  color: var(--text);
  border-color: var(--accent);
}
.theme-toggle-icon {
  width: 1rem;
  height: 1rem;
  display: none;
}
:root[data-theme="dark"] .theme-toggle-icon-sun  { display: block; }
:root[data-theme="light"] .theme-toggle-icon-moon { display: block; }
/* Pre-JS fallback if data-theme hasn't been set: follow the OS. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) .theme-toggle-icon-moon { display: block; }
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .theme-toggle-icon-sun  { display: block; }
}

main {
  flex: 1 1 auto;
  width: 100%;
  padding: 0 1.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* The widened figures let their soft window shadow bleed past the
     reading measure / grid columns (that's the point). Clip only the
     horizontal axis at the viewport edge so a shadow tail can never
     raise a horizontal scrollbar; overflow-y stays visible. The zoomed
     image is position:fixed (its containing block is the viewport, not
     main), so it isn't affected by this clip. */
  overflow-x: clip;
}

/* Prose blocks stay at the reading measure; full-bleed sections
   (e.g. .hero) opt into a wider container via their own rule. */
main > * {
  width: 100%;
  max-width: var(--max-measure);
}

/* ---- Hero / index ------------------------------------------- */
/* Two-column above-the-fold: book art on the left, copy + CTA on
   the right. Breaks out past the body reading measure to give the
   art room to breathe. Stacks vertically on narrow viewports. */
.hero {
  max-width: 64rem;
  display: flex;
  align-items: center;
  gap: 3rem;
  /* No 100vh band — developers scroll for facts, not for drama.
     Enough padding to breathe, then straight into the product. */
  padding: 3rem 0 2.5rem;
}
.hero-art {
  flex: 1 1 0;
  min-width: 0;
  width: 100%;
  display: flex;
  justify-content: center;
}
/* theme.js picks the matching src at load (and re-picks on toggle);
   the element is always visible, only the src swaps. Shown as soon as
   it decodes — no fade or slide-in. */
.hero-cover {
  width: 100%;
  max-width: 28rem;
  height: auto;
  display: block;
}
/* The Dinkus logo sits in the hero-art column where Ishmael shows
   its tall book cover — a square app mark reads better smaller and
   with app-icon corner rounding. */
.hero-logo {
  max-width: 9.5rem;
  border-radius: 12%;
}
.hero-copy {
  /* Copy takes a slightly wider share than the art (1.2 : 1) — in
     the two-column band the prose reads better with the extra
     measure, and the cover doesn't need it. */
  flex: 1.2 1 0;
  min-width: 0;
  /* Room for the closing quote to hang past the justified prose without
     meeting main's clipped viewport edge — in the two-column band the
     copy's right edge sits right at that gutter. Reset when stacked. */
  padding-right: 1.25rem;
}
.hero-copy h1 {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 2.6rem;
  letter-spacing: -0.02em;
  margin: 0 0 0.5rem;
}

/* One-line subtitle under the Ishmael wordmark. Set as a tracked,
   uppercase Inter descriptor — the publisher-spine treatment —
   so the mauve accent is reserved for the Download CTA. */
.tagline {
  font-family: var(--font-ui);
  font-style: normal;
  font-weight: 500;
  font-size: 0.85rem;
  line-height: 1.4;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--text-dim);
  /* Tight gap so the plain benefit line reads as part of the descriptor
     group, set apart from the novel-prose paragraph that follows. */
  margin: 0 0 0.9rem;
}
/* Plain, skimmable value proposition. Sans (not the serif novel prose
   below) and full contrast, so a visitor learns what Ishmael is at a
   glance before the poetic hero paragraph takes over. */
/* Terse fact row under the lede — the developer register: claims a
   reader can verify, set in the system mono. */
.hero-facts {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.8rem;
  color: var(--text-dim);
  margin: 0;
}
.hero-facts code {
  font-family: inherit;
  color: var(--text);
}
.hero-lede {
  font-family: var(--font-ui);
  font-size: 1.1rem;
  line-height: 1.5;
  color: var(--text);
  margin: 0 0 1.75rem;
  max-width: 30rem;
  text-wrap: pretty;
}
/* Set the hero copy as a page from a novel: justified, hyphenated,
   with an oversized drop cap on the opening letter. The site is the
   product demoing itself. */
.hero-tagline {
  font-size: 1.05rem;
  line-height: 1.5;
  color: var(--text);
  margin: 0 0 1.75rem;
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
  text-wrap: pretty;
  /* Anchor for the absolutely-positioned hanging quote below. */
  position: relative;
}
/* Opening quote hangs in the left margin, clear of the dropped "C".
   Taken out of flow (absolute) so it can't disturb the float — the cap
   then sits at the paragraph's left edge, aligned with the rest of the
   hero copy, and the quote sits just to its left in the margin. */
/* Oversized decorative quotation marks in the margins — ~300% of the
   quote's text, in the dim ink, framing the passage. Absolute (out of
   flow) so the prose still opens at the drop cap; pointer/selection off
   so they read as ornament, not text. */
.hero-quote,
.hero-quote-close {
  position: absolute;
  font-family: var(--font-ui);
  font-size: 3em;
  line-height: 1;
  color: var(--text-dim);
  pointer-events: none;
  user-select: none;
  -webkit-user-select: none;
}
.hero-quote {
  left: -0.62em; /* hang into the left margin, clear of the drop cap */
  top: -0.1em;
}
.hero-quote-close {
  right: -0.62em; /* mirror the opening mark's hang for equal spacing */
  bottom: -0.42em;
}
@media (max-width: 42rem) {
  /* Tighter hang so the marks don't overrun the slim phone margins. */
  .hero-quote,
  .hero-quote-close { font-size: 2.4em; }
  .hero-quote { left: -0.4em; }
  .hero-quote-close { right: -0.35em; }
}
.cta {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.6rem 1.1rem;
  background: var(--accent);
  color: var(--bg);
  border-radius: 5px;
  font-family: var(--font-ui);
  font-weight: 600;
  text-decoration: none;
}
.cta:hover {
  background: var(--accent-hover);
  color: var(--bg);
}
.cta-icon {
  height: 1.1em;
  width: auto;             /* viewBox isn't square; let aspect-ratio drive width */
  display: block;
  flex: 0 0 auto;
  /* Optical nudge — the Apple glyph's leaf sits above its bounding
     box, so a hair of lift centres it with the cap height. */
  margin-top: -0.08em;
}
.cta-trial {
  font-family: var(--font-ui);
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--text-dim);
  margin: 0.8rem 0 0;
}
.cta-help {
  font-family: var(--font-ui);
  font-size: 0.82rem;
  color: var(--text-dim);
  margin: 0.35rem 0 0;
}
/* Quiet launch-offer line under the CTA — a small accent chip plus a
   muted price/deadline; the whole line links to /pricing/. */
.launch-note {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin-top: 1.1rem;
  font-family: var(--font-ui);
  font-size: 0.85rem;
  color: var(--text-dim);
  text-decoration: none;
}
.launch-note:hover {
  color: var(--text);
}
.launch-note-chip {
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  padding: 0.24rem 0.6rem;
  border-radius: 999px;
  white-space: nowrap;
}
.launch-note-text strong {
  color: var(--text);
  font-weight: 600;
}
/* Struck-through regular price — the anchor that makes the launch price
   read as a discount. Stays muted (inherits the launch-note dim colour)
   so the live price beside it carries the emphasis. */
.launch-note-text s {
  text-decoration-thickness: 1px;
  margin-right: 0.1em;
}

/* ---- Positioning line ---------------------------------------- */
/* A quiet thesis between the product video and the feature deck: the
   three-way wedge (focus / novel structure / book typography) the
   features below then demonstrate. */
.manifesto {
  width: 100%;
  max-width: 44rem;
  text-align: center;
  padding: 1.5rem 0;
}
.manifesto p {
  font-family: var(--font-ui);
  font-size: 1.5rem;
  line-height: 1.4;
  color: var(--text);
  margin: 0;
  text-wrap: balance;
}

/* ---- FAQ ----------------------------------------------------- */
/* Objection-handling accordion near the foot of the page. Native
   <details>, so it needs no JS; each row reveals a short answer. Sits
   at the reading measure (it's a main > * child). */
.faq {
  width: 100%;
  margin-bottom: 3rem;
}
.faq-title {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 2.4rem;
  letter-spacing: -0.01em;
  text-align: center;
  margin: 0 0 2rem;
}
.faq-item {
  border-top: 1px solid var(--rule);
}
.faq-list .faq-item:last-child {
  border-bottom: 1px solid var(--rule);
}
.faq-item summary {
  /* Native marker off; the +/− is drawn below so it can flip on open. */
  list-style: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.1rem 0;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 1.02rem;
  color: var(--text);
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
  content: "+";
  flex: 0 0 auto;
  font-weight: 400;
  font-size: 1.4rem;
  line-height: 1;
  color: var(--text-dim);
  transition: transform 0.15s ease;
}
.faq-item[open] summary::after {
  content: "\2212"; /* minus */
  color: var(--accent);
}
.faq-item summary:hover { color: var(--accent); }
.faq-item p {
  margin: 0;
  padding: 0 0 1.2rem;
  font-family: var(--font-ui);
  font-size: 0.98rem;
  line-height: 1.55;
  color: var(--text-dim);
  max-width: 34rem;
}

/* ---- Closing CTA --------------------------------------------- */
/* Final call-to-action so a reader who scrolled the whole deck can
   convert without returning to the hero. Repeats the badge, the
   one-time-purchase reassurance, and the launch offer. */
.closer {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  /* Top padding stands in for the dinkus that used to separate the FAQ
     from this closer. */
  padding: 3.5rem 0 2rem;
}
.closer h2 {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 2.8rem;
  line-height: 1.1;
  letter-spacing: -0.01em;
  margin: 0 0 0.9rem;
}
.closer-sub {
  font-family: var(--font-ui);
  font-size: 1.02rem;
  line-height: 1.5;
  color: var(--text-dim);
  max-width: 30rem;
  margin: 0 0 1.9rem;
}
.closer .app-store-badge {
  margin-bottom: 0.9rem;
}
/* Privacy reassurance in the closer — a quiet trust line, a touch more
   present than the fine-print requirements above it. */
.closer-privacy {
  font-family: var(--font-ui);
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text-dim);
  max-width: 26rem;
  margin: 0.8rem 0 0;
}
/* Official "Download on the Mac App Store" badge — Apple art, black
   on the light theme and white on the dark theme (theme.js swaps the
   src). Sized by height per Apple's guidelines; don't restyle the art
   itself. */
.app-store-badge {
  display: inline-block;
  line-height: 0;
}
.app-store-badge-img {
  height: 3rem;
  width: auto;
  display: block;
}
/* When the badge sits inside a .page-body (the pricing panel), keep the
   prose content-image treatment off it — shadow, rounded corners, auto
   height, zoom cursor. It's chrome, not a content image. (The zoom
   lightbox already skips it, since the badge is wrapped in a link.) */
.page-body .app-store-badge-img {
  height: 3rem;
  width: auto;
  margin: 0 auto;
  border-radius: 0;
  box-shadow: none;
  cursor: pointer;
}
/* Pre-launch placeholder: the app isn't in the store yet, so the CTA
   is a muted, inert "Coming Soon" pill. */
.cta--disabled {
  background: var(--surface);
  color: var(--text-dim);
  cursor: not-allowed;
  pointer-events: none;
}

/* ---- Coming soon placeholder --------------------------------- */
/* Standalone pre-launch page (no footer/nav). Centres the Ishmael
   cover beside a short holding message. */
.coming-soon {
  min-height: 100vh;
  max-width: 56rem;
  margin: 0 auto;
  padding: 4rem 1.5rem;
  display: flex;
  flex-wrap: wrap;
  gap: 3rem;
  align-items: center;
  justify-content: center;
}
.coming-soon-art {
  width: 100%;
  max-width: 20rem;
}
.coming-soon-copy {
  flex: 1 1 16rem;
  min-width: 0;
}
.coming-soon-copy h1 {
  font-family: var(--font-ui);
  font-size: 4rem;
  line-height: 1;
  margin: 0;
  color: var(--text);
}
.coming-soon-lead {
  font-family: var(--font-ui);
  font-size: 1.25rem;
  color: var(--text-dim);
  margin: 1.5rem 0 0;
}
.coming-soon-badge {
  display: inline-block;
  margin: 1.5rem 0 0;
  padding: 0.4rem 0.9rem;
  border-radius: 5px;
  background: var(--surface);
  color: var(--text);
  font-family: var(--font-ui);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.coming-soon-help {
  margin: 2rem 0 0;
  font-family: var(--font-ui);
  font-size: 0.9rem;
  color: var(--text-dim);
}
.coming-soon-help a { color: var(--accent); }
@media (max-width: 34rem) {
  .coming-soon { text-align: center; flex-direction: column; }
  .coming-soon-copy h1 { font-size: 3rem; }
}

/* Secondary CTA — quiet outline variant so it doesn't compete with
   the filled accent Download button. */
.cta--subtle {
  margin-top: 1rem;
  background: transparent;
  color: var(--text);
  border: 1px solid var(--surface);
  font-weight: 500;
}
.cta--subtle:hover {
  background: var(--bg-elevated);
  color: var(--text);
  border-color: var(--text-dim);
}
/* The .cta-icon top nudge is tuned for the Apple glyph; the book-open
   mark is already centred, so cancel it here. */
.cta--subtle .cta-icon {
  margin-top: 0;
}

@media (max-width: 42rem) {
  .hero {
    flex-direction: column;
    gap: 2rem;
    text-align: center;
  }
  /* Stacked + centred: drop the two-column quote gutter so the copy isn't
     lopsided (the mark is tightened here anyway). */
  .hero-copy {
    padding-right: 0;
  }
  /* Centre the lede block, not just its text — its 30rem max-width would
     otherwise leave it pinned left of the now-centred hero copy. */
  /* Terse fact row under the lede — the developer register: claims a
   reader can verify, set in the system mono. */
.hero-facts {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.8rem;
  color: var(--text-dim);
  margin: 0;
}
.hero-facts code {
  font-family: inherit;
  color: var(--text);
}
.hero-lede {
    margin-inline: auto;
  }
}

/* ---- Product showcase band ----------------------------------- */
/* Full device shot right after the hero: brand mark, then the real
   product, then the feature deck. Breaks past the prose measure. */
.showcase-band {
  max-width: 60rem;
  padding: 5rem 0 9rem;
  display: flex;
  justify-content: center;
  align-items: center;
}
/* Real MacBook Pro frame (transparent-screen PNG) with the video
   showing through the screen cut-out. The frame <img> sits in normal
   flow and sets the box; the video is absolutely placed over the
   cut-out and the opaque bezel masks its edges. Cut-out measured from
   the source art: left 9.44%, top 10.67%, 81.13% x 78.66% — expanded
   a touch here so the bezel always overlaps the video edge. */
.device-frame {
  position: relative;
  width: 100%;
  max-width: 54rem;
}
.device-frame-img {
  position: relative;
  display: block;
  width: 100%;
  height: auto;
  pointer-events: none;
  /* drop-shadow follows the laptop's alpha silhouette (box-shadow
     would draw a rectangle). Layered: a tighter contact shadow plus a
     broader ambient one for a visible-but-soft lift. */
  filter:
    drop-shadow(0 8px 14px rgba(0, 0, 0, 0.22))
    drop-shadow(0 30px 50px rgba(0, 0, 0, 0.34));
}
.device-frame-video {
  position: absolute;
  /* Fill the screen cut-out: left 9.44% / top 10.67% / 81.13% x 78.66%.
     Grown 2px in each dimension and re-centred (-1px) so the recorded
     window's outer edge tucks under the PNG bezel and is masked. `fill`
     maps the recording into the box. */
  left: calc(9.44% - 1px);
  top: calc(10.67% - 1px);
  width: calc(81.13% + 2px);
  height: calc(78.66% + 2px);
  object-fit: fill;
  background: #000;
}

/* ---- Dinkus -------------------------------------------------- */
/* Ornate scene-break ornament between sections. The PNG ships as
   a white-on-alpha shape and is used as a CSS mask, so the visible
   colour comes from background-color: var(--rule) — the theme's quiet
   divider tone — and tracks the theme automatically. */
.dinkus {
  width: 100%;
  max-width: 20rem;
  aspect-ratio: 1536 / 59;
  margin: 3rem auto;
  background-color: var(--rule);
  -webkit-mask: url(/assets/img/dinkus.png) center / contain no-repeat;
          mask: url(/assets/img/dinkus.png) center / contain no-repeat;
}

/* ---- Feature sections --------------------------------------- */
/* Each feature is a vertically-centred slab sized to its content, with
   generous padding so the page still reads like a deck — one idea per
   scroll. Headings carry the visual weight; body copy stays at the
   prose measure. */
.feature {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 4rem 0;
}

.feature h2 {
  font-family: var(--font-ui);
  font-weight: 300;
  font-size: 3.2rem;
  line-height: 1.1;
  letter-spacing: -0.005em;
  color: var(--text);
  margin: 0 0 1.5rem;
}
.feature p {
  font-size: 1.08rem;
  line-height: 1.5;
  color: var(--text);
  /* Standard block paragraphs — the book-style first-line indents
     went with the drop caps; developers read documentation, not
     novels. */
  margin: 0 0 1em;
  text-wrap: pretty;
}
.feature p:last-child {
  margin-bottom: 0;
}

/* Showcase variant — pairs the copy with a large product
   screenshot side-by-side so the whole slab fits in one viewport.
   Breaks the slab out to the wider hero measure (64rem); on narrow
   viewports it stacks. */
.feature--showcase {
  max-width: 64rem;
  display: grid;
  /* Copy takes the wider share (1.2 : 1) — the prose reads better
     with the extra measure and the shots hold up at the narrower
     column. Mirrored in the -reverse variant below. */
  grid-template-columns: 1.2fr 1fr;
  gap: 3rem;
  /* align-items:start top-aligns the two columns so the eyebrow sits
     level with the top of the screenshot in every section, regardless
     of copy length. align-content:center then centres that single row
     (the pair) vertically within the 90vh slab — .feature's
     justify-content is the grid's *horizontal* axis, so it can't do
     this. */
  align-items: start;
  align-content: center;
}
.feature--showcase .feature-text {
  max-width: none;
}
/* The screenshots carry a baked-in transparent window shadow (~4.3% of
   the image on each side). Widen each figure past its container by
   exactly that inset so the window itself fills the container and the
   soft shadow bleeds out over the borders — into the grid gap / page
   gutter — rather than sitting indented. The width/margin pair comes
   from the shadow fraction f: overhang = f/(1-2f), width =
   100%(1+2·overhang). Percentages resolve against whichever containing
   block the figure sits in, so this works in both the two-column and
   single-column layouts. */
.feature-shot {
  margin-block: 0;
  width: 109.33%;            /* f = 112/2624 → overhang 4.67% */
  margin-inline: -4.67%;
}
.feature-shot.reader-slideshow {
  width: 108.75%;            /* f = 112/2784 → overhang 4.375% */
  margin-inline: -4.375%;
}
/* Reverse variant — screenshot on the left, copy on the right. The
   figure comes first in the markup, so mirror the track sizes to keep
   the screenshot on the wider (1.1fr) side. */
.feature--showcase-reverse {
  grid-template-columns: 1fr 1.2fr;
}

@media (max-width: 48rem) {
  .feature--showcase,
  .feature--showcase-reverse {
    grid-template-columns: 1fr;
    /* Stacked: tighten the row gap between copy and screenshot. */
    gap: 1rem;
  }

  /* Stop alternating in a single column: every section should read as
     text then image. The reverse sections put the figure first in the
     DOM (so it lands in the left column on desktop), so lift the copy
     above it here. */
  .feature--showcase-reverse .feature-text {
    order: -1;
  }
}
/* The screenshot/video element is always visible; theme.js sets
   src to the cross-shaded variant (light page previews the dark
   editor and vice versa). Reader's data-src-on-* both point at
   the same light spread, so it stays light in both themes.
   Starts at opacity:0 and fades in once its src is decoded —
   slow connections see a soft reveal instead of a pop. The
   width/height attributes in the markup reserve aspect-ratio
   space so the layout doesn't shift when the bitmap arrives. */
.feature-shot-img {
  display: block;
  width: 100%;
  height: auto;
  opacity: 0;
  transition: opacity 300ms ease;
}
.feature-shot-img.is-loaded {
  opacity: 1;
}

/* Reader slideshow — all frames are stacked <img>s (loaded lazily via
   loading="lazy") and the cycle just toggles which one is visible, so
   no src swaps, cache re-fetches, or re-decodes per tick. The first
   frame stays in flow to hold the figure's height; the rest overlay
   it. Only the current frame takes pointer events, so click-to-zoom
   targets what you see. */
.reader-slideshow {
  position: relative;
}
.reader-frame {
  opacity: 0;
  transition: none;          /* hard cut, no crossfade */
  pointer-events: none;
}
.reader-frame:not(:first-child) {
  position: absolute;
  inset: 0;
}
.reader-frame.is-current {
  opacity: 1;
  pointer-events: auto;
  z-index: 1;
}
/* Reduced motion: show only the first frame, and don't even render the
   others (so they aren't fetched). */
@media (prefers-reduced-motion: reduce) {
  .reader-frame:not(.is-current) {
    display: none;
  }
}

/* Smart Editing follows the cross-shade pattern used by the other
   showcase sections — a light page previews the dark editor and
   vice versa. (No ID override here; the default cross-shade rules
   above already do this.) The video is a flat rectangle without the
   macOS window shadow baked in, so its frame is drawn in CSS: a rounded
   corner and a soft drop shadow. It gets the screenshot widening undone
   (no transparent margin to compensate) and its horizontal padding
   zeroed, so the video fills the column like the widened screenshots and
   its box-shadow bleeds out over the borders the same way. Vertical
   padding stays for shadow room and to align the eyebrow with the top. */
#smart-editing .feature-shot {
  width: 100%;
  margin-inline: 0;
  padding: 1.75rem 0;
}
#smart-editing .feature-shot-img {
  /* Percentage radius (not a fixed px) so the corner stays the same
     proportion at every size — it scales cleanly through the click-to-
     zoom morph instead of popping. Two values because the video box is
     wider than tall (~1.38:1): a single % makes 1.4%-of-width ≠
     1.4%-of-height, i.e. an elliptical corner. 1.93% ≈ 1.4% × 1.38 makes
     the vertical radius match the horizontal in px, so it's circular. */
  border-radius: 1.4% / 1.93%;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.45);
  /* Promote to its own layer so the rounded corners are clipped at
     the layer level — the click-to-zoom View Transition then snapshots
     the video with clean corners instead of leaking a hairline of
     video past the radius. Uses will-change (not a transform value) so
     it doesn't collide with the is-zoomed centering transform, which
     lives on a lower-specificity selector. */
  will-change: transform;
}
/* The video figure has 1.75rem of top padding (unlike the flush
   screenshots), so nudge this section's text down to match, keeping the
   eyebrow level with the video's top. */
#smart-editing .feature-text {
  padding-top: 1.75rem;
}

/* ---- Page (typographic article surface) ----------------------- */
/* For simple Markdown-authored pages (about, privacy, terms…).
   A single column of serif prose at the body reading measure,
   with a display heading, an optional italic lede, a drop cap on
   the opening paragraph, and quiet hierarchy below. */
.page {
  /* A touch wider than the global reading measure (--max-measure, which
     the homepage deck keeps) so content pages fit a few more words per
     line. Class specificity beats the main > * clamp. */
  max-width: 42rem;
  margin: 0 auto;
  padding: 5rem 0 6rem;
}
.page-header {
  margin-bottom: 3rem;
}
.page-eyebrow {
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 0.78rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 0.75rem;
}
.page-title {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 3.4rem;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--text);
  margin: 0 0 1rem;
}
.page-lede {
  font-family: var(--font-ui);
  font-style: italic;
  font-size: 1.2rem;
  line-height: 1.55;
  color: var(--text-dim);
  margin: 0;
}
.page-body {
  font-family: var(--font-ui);
  font-size: 1.1rem;
  line-height: 1.55;
  color: var(--text);
}
.page-body p {
  /* Standard block paragraphs with a blank-line rhythm — the
     novel-style indents went with the drop caps. */
  margin: 0 0 1em;
  text-wrap: pretty;
}
.page-body h2 {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 1.7rem;
  line-height: 1.25;
  letter-spacing: -0.005em;
  color: var(--text);
  margin: 2.5rem 0 0.75rem;
}
.page-body h3 {
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 1.25rem;
  margin: 2rem 0 0.5rem;
}
.page-body a {
  color: var(--text);
  text-decoration: none;
}
.page-body a:hover {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.18em;
}
.page-body ul,
.page-body ol {
  padding-left: 1.5rem;
  margin: 1.25rem 0;
}
.page-body li { margin: 0 0 0.4rem; }
.page-body blockquote {
  margin: 1.75rem 0;
  padding: 0 0 0 1.25rem;
  border-left: 2px solid var(--rule);
  font-style: italic;
  color: var(--text-dim);
}
.page-body hr {
  border: 0;
  margin: 3rem 0;
  text-align: center;
  font-family: var(--font-ui);
  color: var(--text-dim);
  letter-spacing: 0.5em;
}
.page-body hr::after {
  content: "* * *";
}
/* Content images in prose (blog posts): block-level, centred, and never
   wider than the reading measure. Above the zoom breakpoint they become
   click-to-zoom lightboxes — see the Click-to-zoom section below. */
.page-body img {
  display: block;
  max-width: 100%;
  height: auto;
  /* No vertical margin on the image itself — the spacing lives on the
     wrapping paragraph below. A margin here would collapse through that
     <p> and vanish when the image lifts to zoom (position:fixed), which
     the frame-freeze can't reserve, so the text below would jump up. */
  margin: 0 auto;
  border-radius: 6px;
  box-shadow: var(--img-shadow);
}
/* Spacing for a standalone image sits on its wrapping paragraph, not the
   image, so the frame keeps its full height when the image lifts out. */
.page-body p:has(> img) {
  margin: 2rem 0;
}
/* An autoplaying demo video gets the same frame treatment as a standalone
   image. It isn't wrapped in a spacing paragraph the way a markdown image
   is, so the vertical rhythm lives on the element itself. */
.page-body video {
  display: block;
  max-width: 100%;
  height: auto;
  margin: 2rem auto;
  border-radius: 6px;
  box-shadow: var(--img-shadow);
}
/* A captioned image or video: <figure><img|video><figcaption>. The
   figcaption is UI type, quiet and centred under the frame. The figure
   owns the vertical rhythm, so the media resets its own margin. */
.page-body figure {
  margin: 2rem 0;
}
.page-body figure img,
.page-body figure video {
  margin: 0 auto;
}
.page-body figcaption {
  font-family: var(--font-ui);
  font-size: 0.85rem;
  line-height: 1.45;
  text-align: center;
  color: var(--text-dim);
  margin-top: 0.75rem;
}
.page-back {
  margin-top: 4rem;
  font-family: var(--font-ui);
  font-size: 0.85rem;
}
.page-back a {
  color: var(--text-dim);
  text-decoration: none;
}
.page-back a:hover { color: var(--accent); }
/* Let a linked eyebrow (the blog's "Blog" back-link) inherit the
   eyebrow's muted colour, brightening to the accent on hover. */
.page-eyebrow a {
  color: inherit;
  text-decoration: none;
}
.page-eyebrow a:hover { color: var(--accent); }

/* ---- Blog ---------------------------------------------------- */
/* The index is a list of dated entries on the same reading measure
   as .page: a UI-type date line, a serif display title, and an
   optional serif lede. Individual posts render through post.njk into
   the .page-body prose styles, with a .post-meta date under the title. */
.post-list {
  list-style: none;
  margin: 0;
  padding: 0;
}
.post-list-item {
  padding: 2.25rem 0;
  border-top: 1px solid var(--rule);
}
.post-list-item:first-child {
  border-top: 0;
  padding-top: 0;
}
.post-list-date,
.post-meta {
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 0.78rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-dim);
  margin: 0 0 0.6rem;
}
.post-list-title {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 1.9rem;
  line-height: 1.15;
  letter-spacing: -0.01em;
  margin: 0 0 0.5rem;
}
.post-list-title a {
  color: var(--text);
  text-decoration: none;
}
.post-list-title a:hover { color: var(--accent); }
.post-list-lede {
  font-family: var(--font-ui);
  font-style: italic;
  font-size: 1.1rem;
  line-height: 1.55;
  color: var(--text-dim);
  margin: 0;
}
/* On a post, the date sits between the title and the lede/body. */
.post .post-meta {
  margin-top: -0.25rem;
  margin-bottom: 1.5rem;
}

/* ---- Pricing panel ------------------------------------------- */
/* A centred card that sits inside the serif .page-body; the panel
   itself is UI type. Uses non-<p> elements so the page's drop-cap /
   justified-prose rules don't touch it. */
.pricing-panel {
  max-width: 22rem;
  margin: 2.75rem auto;
  padding: 2rem 2rem 2.25rem;
  text-align: center;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--bg-elevated);
  font-family: var(--font-ui);
}
.pricing-badge {
  display: inline-block;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  padding: 0.3rem 0.75rem;
  border-radius: 999px;
}
.pricing-price {
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 3.4rem;
  line-height: 1;
  color: var(--text);
  margin: 1.1rem 0 0.35rem;
}
.pricing-was {
  font-size: 0.95rem;
  color: var(--text-dim);
}
.pricing-was s {
  text-decoration-thickness: 1px;
}
.pricing-panel .app-store-badge {
  margin-top: 1.6rem;
}
.pricing-deadline {
  margin-top: 1.1rem;
  font-size: 0.9rem;
  color: var(--text-dim);
}

/* ---- Support form -------------------------------------------- */
/* Sits inside the serif .page-body, but the form itself is UI type.
   Fields adopt the elevated surface + hairline border used elsewhere;
   focus lifts the border to the accent with a soft ring. The submit
   button reuses the filled .cta pill. */
.support-form {
  margin-top: 2.5rem;
  font-family: var(--font-ui);
}
.support-form .form-row {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin-bottom: 1.1rem;
}
.support-form label {
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--text);
}
.support-form input,
.support-form textarea {
  font-family: var(--font-ui);
  font-size: 1rem;
  color: var(--text);
  background: var(--bg-elevated);
  border: 1px solid var(--surface);
  border-radius: 6px;
  padding: 0.6rem 0.7rem;
  width: 100%;
  box-sizing: border-box;
}
.support-form textarea {
  resize: vertical;
  line-height: 1.5;
}
.support-form input:focus,
.support-form textarea:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}
.support-form button[type="submit"] {
  border: 0;
  cursor: pointer;
  margin-top: 0.25rem;
}
.support-fallback {
  margin: 1.1rem 0 0;
  font-size: 0.9rem;
  color: var(--text-dim);
}

/* ---- Screenshots gallery ------------------------------------- */
/* Wide gallery (opts out of the narrow prose measure): two mode
   groups, each a responsive grid of shots that open full-size in a
   new tab. */
.shots {
  max-width: 72rem;
  margin: 0 auto;
  padding: 4rem 1.5rem 6rem;
}
.shots-header h1 {
  font-family: var(--font-ui);
  font-size: 3rem;
  line-height: 1.1;
  color: var(--text);
  margin: 0;
}
.shots-back {
  margin: 0.5rem 0 0;
  font-family: var(--font-ui);
  font-size: 0.85rem;
}
.shots-back a { color: var(--text-dim); text-decoration: none; }
.shots-back a:hover { color: var(--accent); }
.shots-group {
  margin-top: 3.5rem;
}
.shots-group h2 {
  font-family: var(--font-ui);
  font-weight: 500;
  font-size: 1.8rem;
  color: var(--text);
  margin: 0 0 1.5rem;
}
.shots-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 22rem), 1fr));
  gap: 1.25rem;
}
.shots-item {
  display: block;
  width: 100%;
  padding: 0;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--rule);
  background: var(--bg-elevated);
  cursor: zoom-in;
}
.shots-item img {
  display: block;
  width: 100%;
  height: auto;
}
.shots-item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Lightbox — a native <dialog> that fills the window and contains the
   full-size shot; the ::backdrop dims the page. Close with the button,
   a click on the dim area, or Escape (handled natively by <dialog>). */
.lightbox {
  position: fixed;
  inset: 0;
  width: 100vw;
  max-width: 100vw;
  height: 100vh;
  max-height: 100vh;
  margin: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-sizing: border-box;
  /* Clicking the left/right thirds to navigate can otherwise start a
     text/image selection or drag — suppress it across the viewer. */
  user-select: none;
  -webkit-user-select: none;
}
.lightbox[open] {
  display: flex;
  align-items: center;
  justify-content: center;
}
.lightbox::backdrop {
  /* Blur the page behind, plus a 30% wash of the theme background. */
  background: color-mix(in srgb, var(--bg) 30%, transparent);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
}
.lightbox-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
  /* Floating, undecorated — no border, radius, or shadow. */
}
.lightbox-close {
  position: fixed;
  top: 1rem;
  right: 1rem;
  width: 2.6rem;
  height: 2.6rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  cursor: pointer;
  z-index: 2; /* above the nav zones so the close target is never covered */
}
.lightbox-close:hover {
  background: rgba(0, 0, 0, 0.8);
}
.lightbox-close svg {
  width: 1.4rem;
  height: 1.4rem;
}
/* Prev / next nav zones — the outer thirds of the viewport page through
   the gallery (cursor keys do the same). Transparent hit areas flanking
   the image; a visible chevron marks each as clickable (mirroring the
   Manual's tap-zones), and an edge scrim fades in on hover/focus. */
.lightbox-nav {
  position: fixed;
  top: 0;
  bottom: 0;
  width: 33.333vw;
  display: flex;
  align-items: center;
  padding: 0 1.25rem;
  border: 0;
  background: transparent;
  cursor: pointer;
  z-index: 1;
  -webkit-tap-highlight-color: transparent;
}
.lightbox-prev { left: 0; justify-content: flex-start; }
.lightbox-next { right: 0; justify-content: flex-end; }
/* Chevron pill — kept faintly visible so the clickable thirds are
   discoverable, brightening on hover. Same 2.6rem circle as the close. */
.lightbox-chevron {
  position: relative;
  width: 2.6rem;
  height: 2.6rem;
  padding: 0.6rem;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  opacity: 0.6;
  transition: opacity 160ms ease, background-color 160ms ease;
}
.lightbox-nav:hover .lightbox-chevron,
.lightbox-nav:focus-visible .lightbox-chevron {
  opacity: 1;
  background: rgba(0, 0, 0, 0.8);
}

/* ---- Footer -------------------------------------------------- */
/* Restrained, four-column footer below the last feature slab.
   Brand + tagline on the left, three navigational columns on the
   right; stacks on narrow viewports. Visually separated from the
   page above by a hairline border and a slight elevation. */
.site-footer {
  width: 100%;
  margin-top: 4rem;
  padding: 5rem 1.5rem 2.5rem;
  border-top: 1px solid var(--rule);
  background: var(--bg-elevated);
}
.site-footer-inner {
  max-width: 64rem;
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr 1fr;
  gap: 3rem;
  align-items: start;
}
.site-footer-brand {
  max-width: 20rem;
}
.site-footer-name {
  font-family: var(--font-ui);
  font-weight: 400;
  font-size: 1.6rem;
  color: var(--text);
  margin: 0 0 0.5rem;
}
.site-footer-name a {
  color: inherit;
  text-decoration: none;
}
.site-footer-name a:hover {
  color: var(--accent);
}
.site-footer-blurb {
  font-family: var(--font-ui);
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--text-dim);
  margin: 0;
}
.site-footer-col {
  font-family: var(--font-ui);
}
.site-footer-heading {
  font-family: var(--font-ui);
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--text);
  margin: 0 0 1rem;
}
.site-footer-col ul {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}
.site-footer-col a {
  font-size: 0.88rem;
  color: var(--text-dim);
  text-decoration: none;
}
.site-footer-col a:hover {
  color: var(--accent);
}
.site-footer-bottom {
  max-width: 64rem;
  margin: 3rem auto 0;
  padding-top: 1.5rem;
  border-top: 1px solid var(--rule);
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.75rem;
}
.site-footer-copy,
.site-footer-built {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  color: var(--text-dim);
  margin: 0;
}
/* Copyright + "Cookie preferences" sit together on one side of the
   footer bottom row; the built-in line keeps the other. */
.site-footer-legal {
  display: flex;
  align-items: baseline;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.4rem 1rem;
}
/* Reopens the consent banner — a button (it's an action), styled to read
   as a quiet footer link. */
.site-footer-cookie {
  font-family: var(--font-ui);
  font-size: 0.78rem;
  color: var(--text-dim);
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  text-decoration: underline;
  text-decoration-color: var(--rule);
  text-underline-offset: 0.15em;
}
.site-footer-cookie:hover { color: var(--accent); }
@media (max-width: 48rem) {
  .site-footer-inner {
    grid-template-columns: 1fr 1fr;
    gap: 2.5rem 2rem;
  }
  .site-footer-brand {
    grid-column: 1 / -1;
  }
}
@media (max-width: 30rem) {
  .site-footer {
    padding: 3.5rem 1.5rem 2rem;
  }
  .site-footer-inner {
    grid-template-columns: 1fr;
    gap: 2rem;
  }
  .site-footer-bottom {
    flex-direction: column;
    text-align: center;
    margin-top: 2.25rem;
  }
}

/* ---- Click-to-zoom -------------------------------------------- */
/* Above the showcase stacking breakpoint, the screenshots become
   clickable lightboxes — driven by zoom.js + the View Transitions
   API. Below the breakpoint, leave them inert (the image already
   fills the main column). */
@media (min-width: 48rem) {
  .feature-shot-img,
  .page-body img,
  .page-body video,
  .device-frame { cursor: zoom-in; }
}
/* Avoid accidental selection/drag when clicking to zoom — a slow
   click or a small mouse drift would otherwise pick up the image
   as a draggable resource. */
.feature-shot-img,
.page-body img,
.page-body video,
.device-frame {
  user-select: none;
  -webkit-user-select: none;
  -webkit-user-drag: none;
  -webkit-touch-callout: none;
}
.feature-shot-img.is-zoomed,
.page-body img.is-zoomed,
.page-body video.is-zoomed {
  position: fixed;
  /* Centre in the viewport WITHOUT a transform, so the transform slot
     is free for the FLIP lift animation in zoom.js. inset:0 + auto
     margins centre a max-constrained, intrinsically-sized element on
     both axes. Auto width/height + both maxes keep the aspect ratio. */
  inset: 0;
  margin: auto;
  width: auto;
  height: auto;
  max-width: 92vw;
  max-height: 92vh;
  z-index: 1000;
  cursor: zoom-out;
}
/* The MacBook mockup zooms as a whole (frame + video). Unlike an <img>,
   a container with inset:0 + height:auto stretches to fill the viewport
   height (so it'd sit at the top, not centred) — so set BOTH width and
   height explicitly at the 2000:1333 frame ratio, fitted within 92vw ×
   92vh. Both definite, so inset:0 + margin:auto centres it on both axes;
   the frame <img> (width:100%) fills exactly. max-width:none overrides
   the in-flow 54rem cap. */
.device-frame.is-zoomed {
  position: fixed;
  inset: 0;
  margin: auto;
  width: min(92vw, calc(92vh * 2000 / 1333));
  height: min(92vh, calc(92vw * 1333 / 2000));
  max-width: none;
  z-index: 1000;
  cursor: zoom-out;
}
body.has-zoom { overflow: hidden; }

/* Invisible click shield while a zoom is open. Sits between the
   page chrome (theme toggle at z:10, etc.) and the zoomed image
   (z:1000), so any click on the page below the image hits the
   shield instead of the underlying control — the document click
   handler in zoom.js then closes the zoom. */
body.has-zoom::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 999;
  cursor: zoom-out;
  /* Focal blur. Because the FLIP lift keeps the live page on screen
     (no View Transition snapshot), a backdrop-filter here blurs the
     real page: it clamps at the viewport edge (no falloff) and moves
     with the page (nothing shifts at settle). The image sits above the
     shield (z:1000), so it stays sharp. This is the held value; the
     keyframes below ramp to/from it. */
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}
/* Ramp the blur in as the image lifts, out as it drops. CSS animations
   fire when an element appears (unlike transitions), so focus-in runs
   the moment the shield mounts on open. On close, zoom.js adds
   .zoom-closing (the shield lingers through the close morph) to swap in
   focus-out. Motion-gated — reduced motion keeps the static held blur. */
@media (prefers-reduced-motion: no-preference) {
  body.has-zoom::before {
    animation: zoom-focus-in 500ms cubic-bezier(0.2, 0, 0.05, 1) both;
  }
  body.has-zoom.zoom-closing::before {
    animation: zoom-focus-out 500ms cubic-bezier(0.2, 0, 0.05, 1) both;
  }
}
@keyframes zoom-focus-in {
  from { backdrop-filter: blur(0);    -webkit-backdrop-filter: blur(0); }
  to   { backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
}
@keyframes zoom-focus-out {
  from { backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); }
  to   { backdrop-filter: blur(0);    -webkit-backdrop-filter: blur(0); }
}

/* ---- Cookie consent banner ----------------------------------- */
/* Fixed card at the bottom, shown by consent.js only when the visitor
   hasn't chosen. Uses the elevated-surface + hairline treatment of the
   other cards so it reads as considered chrome, not an intrusion. */
.consent-banner {
  position: fixed;
  left: 1rem;
  right: 1rem;
  bottom: 1rem;
  z-index: 200;
  max-width: 36rem;
  margin-inline: auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.5rem;
  padding: 1rem 1.25rem;
  background: var(--bg-elevated);
  border: 1px solid var(--rule);
  border-radius: 12px;
  box-shadow: var(--img-shadow);
  font-family: var(--font-ui);
}
.consent-banner[hidden] { display: none; }
.consent-banner-text {
  flex: 1 1 15rem;
  margin: 0;
  font-size: 0.85rem;
  line-height: 1.5;
  color: var(--text-dim);
}
.consent-banner-text a {
  color: var(--text);
  text-decoration-color: var(--rule);
  text-underline-offset: 0.15em;
}
.consent-banner-text a:hover { color: var(--accent); }
.consent-banner-actions {
  display: flex;
  gap: 0.5rem;
  flex: 0 0 auto;
}
.consent-btn {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0.5rem 1.1rem;
  border-radius: 6px;
  border: 1px solid transparent;
  cursor: pointer;
}
.consent-btn--solid {
  background: var(--accent);
  color: var(--bg);
}
.consent-btn--solid:hover { background: var(--accent-hover); }
.consent-btn--ghost {
  background: transparent;
  color: var(--text);
  border-color: var(--surface);
}
.consent-btn--ghost:hover { border-color: var(--text-dim); }

/* Rein in the large display headings on phones. Beyond the root step-down
   near the top, the desktop-scale headers (hero wordmark, feature h2s,
   manifesto, FAQ/closer, page titles) otherwise eat too much of a small
   viewport — trim them further so more of each section fits. Placed last
   so it outranks the base heading sizes at equal specificity. */
@media (max-width: 40rem) {
  .hero-copy h1 { font-size: 3.5rem; }
  .feature h2 { font-size: 2.3rem; }
  .manifesto p { font-size: 1.25rem; }
  .faq-title { font-size: 1.9rem; }
  .closer h2 { font-size: 2.1rem; }
  .page-title { font-size: 2.5rem; }
}

/* ---- Try-in-browser demo (home page) --------------------------------
   The section ships hidden and is revealed on macOS browsers by
   try-demo.js, which also builds the lightbox. The frame is the app's
   dark editor regardless of site theme — it's a window into the
   product, not a themed site component. */
.try-browser {
  text-align: center;
  max-width: 38rem;
  margin: 0 auto;
  padding: 1rem 1.5rem;
}
/* Badge-style launch button — foreground fill like the App Store
   badge beside it, UI sans face, with the Ishmael figure as a
   monochrome mask (white-on-alpha PNG, same technique as the
   dinkus) tinted by currentColor. */
.try-launch {
  display: inline-flex;
  align-items: center;
  gap: 0.7em;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 1rem;
  padding: 0.5em 1.3em 0.5em 1em;
  border: none;
  border-radius: 0.65em;
  background: var(--text);
  color: var(--bg);
  cursor: pointer;
}
.try-launch:hover {
  opacity: 0.85;
}
/* In-article launcher (blog posts): breathing room around the badge,
   and the paragraph that follows starts a fresh run — flush left, not
   indented as if it continued the prose. */
.try-inline,
.page-body .try-inline {
  margin: 2.5rem 0;
}
.try-launch-icon {
  width: 1em;
  height: 2em;
  background: currentColor;
  -webkit-mask: url(/assets/img/ishmael-mask.png) center / contain no-repeat;
          mask: url(/assets/img/ishmael-mask.png) center / contain no-repeat;
}
.try-lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  /* The page BLURS behind the editor rather than darkening — the
     blur ramps in while the frame fades, one composed entrance. */
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  animation: try-lightbox-blur 240ms ease-out;
  display: grid;
  place-items: center;
  /* Extra top room so the frame clears the window-corner close
     cross. */
  padding: 3.25rem 3rem 2.5rem;
}
@keyframes try-lightbox-blur {
  from {
    -webkit-backdrop-filter: blur(0px);
    backdrop-filter: blur(0px);
  }
}
.try-lightbox-frame,
.try-lightbox-close {
  animation: try-lightbox-fade 240ms ease-out;
}
@keyframes try-lightbox-fade {
  from {
    opacity: 0;
  }
}
@media (prefers-reduced-motion: reduce) {
  .try-lightbox,
  .try-lightbox-frame,
  .try-lightbox-close {
    animation: none;
  }
}
.try-lightbox-frame {
  position: relative;
  width: 100%;
  height: 100%;
  border-radius: 0.75rem;
  overflow: hidden;
  background: #1e1e2e; /* app dark bg while the bundle loads */
  box-shadow: 0 24px 80px rgba(0, 0, 0, 0.5);
}
.try-lightbox-frame iframe {
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
}
/* Window-corner close, over the dimmed backdrop — clear of the
   editor's own title-bar icons inside the frame. */
.try-lightbox-close {
  position: fixed;
  top: 0.75rem;
  right: 0.75rem;
  z-index: 1;
  width: 2.25rem;
  height: 2.25rem;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.4);
  color: #cdd6f4;
  cursor: pointer;
  display: grid;
  place-items: center;
}
.try-lightbox-close:hover {
  background: rgba(0, 0, 0, 0.7);
  color: #fff;
}
.try-lightbox-close svg {
  width: 1rem;
  height: 1rem;
}
/* Lock page scroll while the lightbox is up. */
:root.try-lightbox-open,
:root.try-lightbox-open body {
  overflow: hidden;
}
