/* fonts.css (generated — tools/fetch-fonts.mjs) */
/* Brand webfonts, served from our own origin.

   GENERATED by marketing/tools/fetch-fonts.mjs — do not hand-edit.
   Re-run it when the stack in tokens.css changes; the weights below
   are the ones the CSS actually asks for, not everything published.

   Bebas Neue, DM Sans and DM Mono are all SIL Open Font License 1.1,
   which is what permits us to serve the files ourselves. See OFL.txt
   in this folder, which ships with them.

   The unicode-ranges are Google's own and are load-bearing: they are
   what stops a page of plain English fetching the latin-ext cut. */

@font-face {
    font-family: 'Bebas Neue';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/assets/fonts/bebas-neue-400-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: 'Bebas Neue';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/assets/fonts/bebas-neue-400-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'DM Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/assets/fonts/dm-mono-400-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: 'DM Mono';
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url('/assets/fonts/dm-mono-400-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('/assets/fonts/dm-sans-300-700-latin-ext.woff2') format('woff2');
    unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
    font-family: 'DM Sans';
    font-style: normal;
    font-weight: 300 700;
    font-display: swap;
    src: url('/assets/fonts/dm-sans-300-700-latin.woff2') format('woff2');
    unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ─────────────────────────────────────────────────────────────────
   Base, navigation and footer.

   Ported from TrailsIQ-Theme/assets/css/frontend.css, minus the parts
   that only meant something inside WordPress: the `.admin-bar` offsets
   (there is no admin bar), the Gutenberg `.entry-content` reset (there
   are no blocks), and the editor styles.

   Everything a customer can SEE is unchanged, deliberately. The point
   of the move is to leave WordPress, not to redesign — a visual change
   smuggled in during a platform migration is impossible to attribute
   later when somebody asks why the site looks different.
   ───────────────────────────────────────────────────────────────── */

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

/* scroll-padding-top is what makes an in-page anchor usable at all
   here: .tiq-nav is `position: fixed`, so without it a jump to #foo
   parks the heading behind the bar and the reader lands mid-sentence
   with no sign anything is missing. On html rather than per-target,
   so the next anchor added anywhere on the site gets it for free.

   The reduced-motion pair goes with it. Smooth scrolling has been
   declared site-wide since the first commit and, until the mobile-apps
   page grew a jump link, nothing could reach it — an anchor is what
   turns that declaration into actual movement on the screen. */
html { scroll-behavior: smooth; scroll-padding-top: var(--nav-h); -webkit-text-size-adjust: 100%; }
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
}

img, svg { max-width: 100%; height: auto; display: block; }

a { color: inherit; }

body {
    font-family: var(--font-body);
    background: var(--offwhite);
    color: var(--ink);
    line-height: 1.65;
    font-size: var(--text-med);
    overflow-x: hidden;
}

h1, h2, h3 {
    font-family: var(--font-display);
    font-weight: 400;
    letter-spacing: 0.04em;
    line-height: 1;
}
h1 { font-size: clamp(36px, 6vw, 72px); }
h2 { font-size: clamp(28px, 4vw, 52px); }
h3 { font-size: clamp(22px, 3vw, 36px); line-height: 1.1; }
h4 { font-family: var(--font-body); font-size: 17px; font-weight: 500; }
h5 { font-family: var(--font-body); font-size: 15px; font-weight: 500; }

.tiq-container {
    max-width: var(--content-width);
    margin-inline: auto;
    padding-inline: clamp(20px, 4vw, 48px);
}

/* Skip link — not in the theme, and it should have been. A fixed nav
   with a hamburger puts several tab stops in front of the content on
   every page. */
.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: 300;
    background: var(--accent);
    color: var(--white);
    padding: 12px 20px;
    text-decoration: none;
    border-radius: 0 0 var(--radius) 0;
}
.skip-link:focus { left: 0; }

/* ── Navigation ─────────────────────────────────────────────────── */

.tiq-nav {
    position: fixed; top: 0; left: 0; right: 0; z-index: 200;
    background: var(--forest);
    display: flex; align-items: center; justify-content: space-between;
    padding: 0 clamp(16px, 4vw, 48px);
    height: var(--nav-h);
    transition: box-shadow 0.2s;
}
.tiq-nav.scrolled { box-shadow: 0 2px 20px rgba(0, 0, 0, 0.3); }

.nav-logo {
    display: flex; align-items: center; gap: 10px;
    text-decoration: none; color: var(--white);
    flex-shrink: 0;
}
.nav-logo-text {
    font-family: var(--font-body);
    font-size: 18px; font-weight: 700; letter-spacing: -0.025em;
    color: var(--white);
}
.nav-logo-text em { font-style: normal; color: var(--accent); }

.nav-links {
    display: flex; align-items: center; gap: 28px;
    list-style: none; margin-left: auto;
}
.nav-links a {
    text-decoration: none; color: rgba(255, 255, 255, 0.72);
    font-size: 14px; font-weight: 400; letter-spacing: 0.02em;
    transition: color 0.2s; white-space: nowrap;
}
.nav-links a:hover,
.nav-links a[aria-current="page"] { color: var(--white); }

.nav-cta {
    display: flex; align-items: center; gap: 10px;
    margin-left: clamp(20px, 3vw, 40px);
}
.btn-nav-solid {
    background: var(--accent); color: var(--white); text-decoration: none;
    font-size: 14px; font-weight: 500; padding: 9px 18px;
    border-radius: var(--radius); transition: background 0.2s;
    white-space: nowrap;
}
.btn-nav-solid:hover { background: #aa7624; }

.nav-hamburger {
    display: none; flex-direction: column; justify-content: space-between;
    width: 26px; height: 18px;
    background: none; border: none; cursor: pointer; padding: 0;
    margin-left: 8px;
}
.nav-hamburger span {
    display: block; width: 100%; height: 2px;
    background: var(--white); border-radius: 2px;
    transition: transform 0.25s, opacity 0.25s;
}
.nav-hamburger.open span:nth-child(1) { transform: translateY(8px) rotate(45deg); }
.nav-hamburger.open span:nth-child(2) { opacity: 0; }
.nav-hamburger.open span:nth-child(3) { transform: translateY(-8px) rotate(-45deg); }

.nav-mobile-menu {
    position: fixed; inset: var(--nav-h) 0 0 0; z-index: 199;
    background: var(--forest);
    transform: translateX(100%);
    transition: transform 0.3s ease;
    overflow-y: auto;
    display: flex; flex-direction: column;
}
.nav-mobile-menu.open { transform: none; }
.nav-mobile-inner { padding: 32px 24px; display: flex; flex-direction: column; gap: 8px; }
.nav-mobile-links { list-style: none; }
.nav-mobile-links li { border-bottom: 1px solid rgba(255, 255, 255, 0.07); }
.nav-mobile-links a {
    display: block; padding: 16px 0;
    text-decoration: none; color: rgba(255, 255, 255, 0.85);
    font-size: 18px; letter-spacing: 0.02em; transition: color 0.2s;
}
.nav-mobile-links a:hover { color: var(--white); }
.nav-mobile-cta { margin-top: 28px; text-align: center; }

/* ── Buttons ────────────────────────────────────────────────────── */

.btn-primary {
    background: var(--accent); color: var(--white);
    text-decoration: none; font-size: 15px; font-weight: 500;
    padding: 14px 28px; border-radius: var(--radius);
    display: inline-block; transition: background 0.2s;
}
.btn-primary:hover { background: #aa7624; }

.btn-secondary {
    background: transparent; color: var(--ink);
    text-decoration: none; font-size: 15px; font-weight: 500;
    padding: 13px 27px; border-radius: var(--radius);
    border: 1px solid rgba(26, 26, 24, 0.2);
    display: inline-block; transition: border-color 0.2s;
}
.btn-secondary:hover { border-color: var(--ink); }

/* ── Page shell ─────────────────────────────────────────────────── */

.site-main { padding-top: var(--nav-h); }

.page-section { padding-block: clamp(var(--space-m), 8vw, var(--space-xl)); }
.page-section--tint { background: var(--white); }

.section-head { max-width: 720px; margin-bottom: var(--space-s); }
.section-head p { color: var(--mid); margin-top: 16px; }

.eyebrow {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--moss);
    margin-bottom: 12px;
}

/* ── Footer ─────────────────────────────────────────────────────── */

.tiq-footer {
    /* The mountains are absolutely positioned against this. Without a
       positioned ancestor they anchor to the initial containing block
       and land somewhere else entirely — which is what "the footer has
       no mountains" looked like. */
    position: relative;
    background: var(--ink);
    padding: clamp(40px, 6vw, 64px) 0 clamp(24px, 4vw, 36px);
}
.footer-grid {
    /* Brand column plus four link columns since Guides was added —
       the topic pages had been in no menu at all, which left nothing
       on the site linking to them. */
    display: grid; grid-template-columns: 2fr 1fr 1fr 1.2fr 1fr;
    gap: 40px; margin-bottom: 40px;
}
.footer-logo { margin-bottom: 12px; }
.footer-tagline {
    font-size: 13px; color: rgba(255, 255, 255, 0.4);
    line-height: 1.65; font-weight: 300; max-width: 240px;
}
.footer-store-badges-label {
    margin-top: 18px; font-size: 11px; letter-spacing: 0.1em;
    text-transform: uppercase; color: rgb(255 255 255);
}
.footer-store-badges { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.footer-store-badges img { height: 32px; }

.footer-col h4 {
    font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase;
    color: rgba(255, 255, 255, 0.35); margin-bottom: 18px;
    font-family: var(--font-mono); font-weight: 400;
}
.footer-links { list-style: none; display: flex; flex-direction: column; gap: 10px; }
.footer-links a {
    text-decoration: none; font-size: 14px;
    color: rgba(255, 255, 255, 0.55); transition: color 0.2s;
}
.footer-links a:hover { color: var(--white); }

.footer-bottom {
    border-top: 1px solid rgba(255, 255, 255, 0.06);
    padding-top: 24px;
    display: flex; flex-wrap: wrap; gap: 12px; justify-content: space-between;
    font-size: 13px; color: rgba(255, 255, 255, 0.35);
}

/* ── Responsive ─────────────────────────────────────────────────── */

@media (max-width: 900px) {
    .footer-grid { grid-template-columns: 1fr 1fr; gap: 32px; }
}

@media (max-width: 820px) {
    .nav-links { display: none; }
    .nav-hamburger { display: flex; }
}

@media (max-width: 560px) {
    .footer-grid { grid-template-columns: 1fr; }
}

/* Social — the theme's circular icon buttons. */
.footer-social { display: flex; gap: 12px; margin-top: 22px; }
.footer-social-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    color: var(--white);
    background: rgba(255, 255, 255, 0.08);
    transition: background-color .2s ease, color .2s ease, transform .2s ease;
}
.footer-social-link:hover,
.footer-social-link:focus-visible { background: var(--accent); transform: translateY(-1px); }
.footer-social-link svg { width: 20px; height: 20px; display: block; }

/* ── Breadcrumb ───────────────────────────────────────────────────
   Rendered on every page but the home page, and paired with the
   BreadcrumbList in the JSON-LD — see layout.js for why the two ship
   together rather than the markup existing alone.

   Deliberately quiet, and it sits at the END of the page rather than
   above the hero — see layout.js. A crumb trail that competes with an
   H1 for attention has misunderstood its job, and above a dark hero
   it also drew a pale strip that matched neither the nav above it nor
   the hero below. */
.tiq-crumbs {
    padding: 22px 0;
    font-size: var(--text-small);
    color: var(--light);
    border-top: 1px solid rgba(26, 26, 24, 0.08);
}

/* THE CRUMB CONTINUES THE BAND ABOVE IT, and that is about the
   footer's mountains rather than about taste.
   ────────────────────────────────────────────────────────────────
   The footer paints its silhouette in its OWN colour and draws it
   upward into whatever sits above — that is what makes the peaks read
   as the footer intruding rather than as decoration on a seam. It
   assumes one solid backdrop up there.

   A pale crumb strip between the closing band and the footer put a
   second colour inside that backdrop, so the mountains crossed a line
   partway up and stopped looking like one shape.

   Taking the closing band's colour puts the seam back where the
   design already had one. The rule keys off what the page actually
   ENDS with, so a page closing some other way — pricing, which
   finishes on a light band — keeps the light treatment rather than
   rendering dark text on a dark strip.

   `main:has(> …:last-child)` rather than an adjacent-sibling rule:
   the closing band is inside <main>, so the crumb's previous sibling
   is the </main>, and `.cta-band + .tiq-crumbs` matches nothing at
   all. It failed silently and looked exactly like a specificity
   problem. */
main:has(> .cta-band:last-child) + .tiq-crumbs,
main:has(> .page-section--dark:last-child) + .tiq-crumbs {
    background: var(--forest);
    border-top: 0;
    color: rgba(255, 255, 255, 0.55);
}
main:has(> .cta-band:last-child) + .tiq-crumbs a,
main:has(> .page-section--dark:last-child) + .tiq-crumbs a { color: rgba(255, 255, 255, 0.72); }
main:has(> .cta-band:last-child) + .tiq-crumbs a:hover,
main:has(> .page-section--dark:last-child) + .tiq-crumbs a:hover { color: var(--white); }
main:has(> .cta-band:last-child) + .tiq-crumbs li + li::before,
main:has(> .page-section--dark:last-child) + .tiq-crumbs li + li::before { color: rgba(255, 255, 255, 0.3); }
main:has(> .cta-band:last-child) + .tiq-crumbs [aria-current="page"],
main:has(> .page-section--dark:last-child) + .tiq-crumbs [aria-current="page"] { color: rgba(255, 255, 255, 0.5); }
.tiq-crumbs ol {
    display: flex; flex-wrap: wrap; align-items: center;
    gap: 8px; list-style: none; margin: 0; padding: 0;
}
.tiq-crumbs li + li::before {
    content: "/";
    margin-right: 8px;
    color: rgba(26, 26, 24, 0.25);
}
.tiq-crumbs a { color: var(--mid); text-decoration: none; }
.tiq-crumbs a:hover { color: var(--moss); text-decoration: underline; }
.tiq-crumbs [aria-current="page"] { color: var(--light); }


/* theme.css (ported) */
/* Ported from the WordPress theme by
 * marketing/wordpress/extract-theme-css.mjs — do not hand-edit.
 * Re-run that script against the theme to refresh.
 */
/* ── Section utilities ──────────────────────────────────────────────────────── */
.section-label {
  font-family: 'DM Mono', monospace;
  font-size: 11px; letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--moss); font-weight: 500; margin-bottom: 16px;
  display: block;
}
.section-label.light { color: var(--fern); }
.section-title {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(32px, 4.5vw, 56px);
  line-height: 1.0; letter-spacing: 0.04em;
  color: var(--forest); margin-bottom: 20px;
}
.section-title.light { color: var(--white); }
.section-body {
  font-size: 17px; color: var(--mid); max-width: 540px;
  line-height: 1.75; font-weight: 300;
}
.section-body.light { color: rgba(255,255,255,0.65); }
/* ── Hero block ─────────────────────────────────────────────────────────────── */
.tiq-hero {
  min-height: 100svh;
  background: var(--forest);
  display: flex; flex-direction: column;
  justify-content: flex-end;
  padding: calc(var(--nav-h) + 60px) clamp(20px, 5vw, 80px) clamp(48px, 7vw, 80px);
  position: relative; overflow: hidden;
}
/* Overlay colour is parameterised via --hero-overlay-base on the
   parent .tiq-hero. The block writes that variable inline when the
   operator picks a colour; default Moss matches the original look.
   color-mix bakes in the 65% transparency so the operator only has
   to pick a colour, not a full rgba(). */
.tiq-hero { --hero-overlay-base: #4a6741; }
.tiq-hero h1 {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(52px, 9vw, 124px);
  font-weight: 600;
  line-height: 0.92; letter-spacing: 0.03em;
  color: var(--white); margin-bottom: 28px;
  max-width: 800px; position: relative; z-index: 1;
}
.tiq-hero h1 em { font-style: normal; color: var(--sage); }
/* Hero + phone variant ───────────────────────────────────────────────────── */
.tiq-hero--with-phone { justify-content: center; }
.tiq-hero--with-phone .hero-layout {
  display: grid;
  grid-template-columns: 1fr minmax(0, auto);
  gap: clamp(32px, 5vw, 80px);
  align-items: center;
  width: 100%;
  position: relative; z-index: 1;
}
.tiq-hero--with-phone .hero-layout[data-phone-pos="left"] {
  grid-template-columns: minmax(0, auto) 1fr;
}
.tiq-hero--with-phone .hero-layout[data-phone-pos="left"] .hero-visual { order: 0; }
.tiq-hero--with-phone .hero-layout[data-phone-pos="left"] .hero-text   { order: 1; }
.tiq-hero--with-phone .hero-text { min-width: 0; }
.tiq-hero--with-phone .hero-text h1 { max-width: 100%; }
.tiq-hero--with-phone .hero-visual {
  display: flex;
  justify-content: center;
  align-items: center;
  min-width: 0;
}
.tiq-hero--with-phone .hero-visual .tiq-smartphone-wrap {
  padding: 0;
  width: var(--tiq-phone-w);
  flex-shrink: 0;
}
/* Group (main + second phone) inside the hero */
.tiq-hero--with-phone .hero-visual .tiq-smartphone-group {
  padding: 0;
  width: auto;
}
@media (max-width: 1024px) {
  .tiq-hero--with-phone .tiq-smartphone-wrap {
    --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 260px);
  }
  .tiq-hero--with-phone .hero-visual .tiq-smartphone-group .tiq-smartphone-wrap {
    --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 200px);
  }
  .tiq-hero--with-phone .hero-layout { gap: 40px; }
}
@media (max-width: 768px) {
  .tiq-hero--with-phone {
    min-height: 0;
    padding-top: calc(var(--nav-h) + 40px);
    padding-bottom: 60px;
  }
  .tiq-hero--with-phone .hero-layout,
  .tiq-hero--with-phone .hero-layout[data-phone-pos="left"] {
    grid-template-columns: 1fr;
    gap: 40px;
  }
  .tiq-hero--with-phone .hero-layout[data-phone-pos="left"] .hero-visual,
  .tiq-hero--with-phone .hero-layout[data-phone-pos="left"] .hero-text { order: initial; }
  .tiq-hero--with-phone .hero-visual { justify-content: center; }
  .tiq-hero--with-phone .tiq-smartphone-wrap {
    --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 240px);
  }
  .tiq-hero--with-phone .hero-visual .tiq-smartphone-group .tiq-smartphone-wrap {
    --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 160px);
  }
  .tiq-hero--with-phone .hero-scroll-hint { display: none; }
}
@media (max-width: 480px) {
  .tiq-hero--with-phone .tiq-smartphone-wrap {
    --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 200px);
  }
  .tiq-hero--with-phone .hero-visual .tiq-smartphone-group .tiq-smartphone-wrap {
    --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 130px);
  }
}
/* ── Features grid block ────────────────────────────────────────────────────── */
.tiq-features-grid {
  background: var(--forest);
  padding: clamp(60px, 8vw, 100px) clamp(20px, 4vw, 48px);
}
.features-header {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 40px; margin-bottom: 56px; align-items: end;
}
.features-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
}
.feature-card {
  background: rgba(255,255,255,0.04);
  border: 1px solid rgba(255,255,255,0.07);
  padding: clamp(24px, 3vw, 36px) clamp(20px, 2.5vw, 32px);
  transition: background 0.25s; position: relative; overflow: hidden;
}
.feature-card::before {
  content: ''; position: absolute; top: 0; left: 0;
  width: 3px; height: 0; background: var(--accent); transition: height 0.3s;
}
.feature-card:hover { background: rgba(255,255,255,0.07); }
.feature-card:hover::before { height: 100%; }
.feature-icon {
  width: 44px; height: 44px; background: rgba(200,98,42,0.15);
  border-radius: var(--radius); display: flex; align-items: center;
  justify-content: center; font-size: 20px; margin-bottom: 20px;
  border: 1px solid rgba(200,98,42,0.25);
  color: #d97a3c;
}
.feature-icon .tiq-icon-svg { width: 22px; height: 22px; display: block; }
.feature-title { font-size: 20px; font-weight: 500; color: var(--white); margin-bottom: 10px; }
.feature-desc,
.entry-content .feature-desc { font-size: 15px; color: rgba(255,255,255,0.5); line-height: 1.7; font-weight: 300; }
/* Optional per-feature CTA. Three styles drop into the dark
   feature-card background:
   - primary: filled brand orange, full pill button
   - ghost:   subtle outlined pill, brand orange on hover
   - link:    text + arrow, no chrome — useful when many cards
              ship a CTA and you want them to read as quiet links
              instead of competing buttons. */
.feature-button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 18px;
  padding: 9px 16px;
  border-radius: 999px;
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  transition: background 0.18s, border-color 0.18s, color 0.18s, transform 0.18s;
  align-self: flex-start;
  -webkit-tap-highlight-color: transparent;
}
.feature-button:hover { transform: translateY(-1px); }
.feature-button--primary {
  background: #dd9c34;
  color: #1a1a1a;
  border: 1px solid #dd9c34;
}
.feature-button--primary:hover {
  background: #c98a2c;
  border-color: #c98a2c;
  color: #1a1a1a;
}
.feature-button--ghost {
  background: transparent;
  color: rgba(255,255,255,0.85);
  border: 1px solid rgba(255,255,255,0.18);
}
.feature-button--ghost:hover {
  border-color: #dd9c34;
  color: #dd9c34;
}
.feature-button--link {
  padding: 0;
  border-radius: 0;
  background: transparent;
  border: none;
  color: #dd9c34;
  font-weight: 600;
}
.feature-button--link:hover {
  color: #f1b864;
}
.feature-button--link .feature-button-arrow {
  transition: transform 0.18s;
}
.feature-button--link:hover .feature-button-arrow {
  transform: translateX(3px);
}
/* Make the card a column so the button can sit at the bottom of
   each cell aligned with its peers — keeps the grid visually
   tidy when card text lengths differ. */
.feature-card { display: flex; flex-direction: column; }
.feature-card .feature-desc { flex: 1; }
.steps-header { text-align: center; margin-bottom: 64px; }
.steps-header .section-body { margin-inline: auto; }
/* Columns follow the COUNT, and a remainder centres.
   Four was hard-coded, so a five-step sequence put the fifth alone in
   the left column under the first — which reads as a layout that broke
   rather than a step that follows. `steps()` sets --steps-n, and flex
   is what makes the wrapped remainder centre; grid has no way to say
   that, and left-orphaning is the thing being fixed. */
.steps-row {
  --steps-cols: var(--steps-n, 4);
  --steps-gap: 0px;
  display: flex; flex-wrap: wrap; justify-content: center;
  gap: var(--steps-gap); position: relative;
}
.steps-row > .step {
  flex: 0 0 calc((100% - (var(--steps-cols) - 1) * var(--steps-gap))
                 / var(--steps-cols));
}
.steps-row::before {
  content: ''; position: absolute;
  top: 31px; left: 10%; right: 10%;
  height: 1px;
  background: linear-gradient(to right, transparent, var(--moss), var(--moss), transparent);
  opacity: 0.35;
}
.step {
  padding: 0 clamp(12px, 2vw, 24px);
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
.step-num {
  width: 62px; height: 62px; border-radius: 50%;
  background: var(--forest); color: var(--white);
  font-family: 'Bebas Neue', sans-serif; font-size: 24px; letter-spacing: 0.04em;
  display: flex; align-items: center; justify-content: center;
  margin-bottom: 24px; position: relative; z-index: 1;
  border: 3px solid var(--sand); box-shadow: 0 0 0 1px var(--moss);
  flex-shrink: 0;
}
.step-title { font-size: 15px; font-weight: 500; color: var(--forest); margin-bottom: 10px; }
.step-desc  { font-size: 13px; color: var(--mid); line-height: 1.7; font-weight: 300; }
.pricing-header .section-body { margin-inline: auto; }
/* ── CTA section ────────────────────────────────────────────────────────────── */
.tiq-cta-section {
  background: var(--forest);
  padding: clamp(64px, 10vw, 120px) clamp(20px, 4vw, 48px);
  text-align: center; position: relative; overflow: hidden;
  /* Same overlay-as-variable pattern as the hero. Default Moss; the
     block writes --cta-overlay-base inline when an operator picks a
     colour. */
  --cta-overlay-base: #4a6741;
}
.tiq-cta-section::before {
  content: ''; position: absolute; inset: 0;
  background: radial-gradient(ellipse 60% 70% at 50% 50%, color-mix(in srgb, var(--cta-overlay-base), transparent 60%) 0%, transparent 70%);
  pointer-events: none;
}
/* Heading is class-based so the operator-picked tag (H1–H4)
   inherits the same look — the legacy h2 rule is kept as a
   fallback for already-saved blocks that don't yet carry the
   .cta-heading class. The variants below mirror the heading
   block styles registered for core/heading (tiq-display,
   tiq-eyebrow, tiq-light) so a CTA heading matches the rest
   of the site by default. */
.tiq-cta-section .cta-heading,
.tiq-cta-section h2 {
  position: relative; z-index: 1;
  margin-bottom: 20px;
  color: var(--white);
}
/* Display style — the original oversized Bebas Neue marquee.
   Matches .wp-block-heading.is-style-tiq-display. */
.tiq-cta-section .cta-heading--display,
.tiq-cta-section h2 {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(40px, 7vw, 88px);
  letter-spacing: 0.04em;
  line-height: 0.95;
  font-weight: normal;
}
/* Regular style — quieter heading, matches the surrounding page
   so a CTA used inline mid-article doesn't dominate. Uses DM Sans
   to align with the rest of the body type. */
.tiq-cta-section .cta-heading--regular {
  font-family: 'DM Sans', sans-serif;
  font-size: clamp(28px, 4vw, 44px);
  letter-spacing: -0.01em;
  line-height: 1.15;
  font-weight: 600;
}
/* Eyebrow style — DM Mono uppercase tag, brand-orange.
   Matches .wp-block-heading.is-style-tiq-eyebrow. Useful when
   the CTA heading is meant to read as a kicker / pre-heading
   rather than the dominant line in the section. */
.tiq-cta-section .cta-heading--eyebrow {
  font-family: 'DM Mono', monospace;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--accent);
  line-height: 1.4;
  margin-bottom: 14px;
}
/* Content width — caps the heading / subtitle / note so the text
   doesn't sprawl across very wide screens. The Layout panel
   writes data-width="narrow|medium|wide|full" onto the section.
   Buttons (.cta-actions) keep their own natural sizing so a
   wide button row stays readable even with narrow text. */
.tiq-cta-section[data-width="narrow"] .cta-heading,
.tiq-cta-section[data-width="narrow"] .cta-sub,
.tiq-cta-section[data-width="narrow"] .cta-note {
  max-width: 600px;
  margin-left: auto;
  margin-right: auto;
}
.tiq-cta-section[data-width="medium"] .cta-heading,
.tiq-cta-section[data-width="medium"] .cta-sub,
.tiq-cta-section[data-width="medium"] .cta-note {
  max-width: 800px;
  margin-left: auto;
  margin-right: auto;
}
.tiq-cta-section[data-width="wide"] .cta-heading,
.tiq-cta-section[data-width="wide"] .cta-sub,
.tiq-cta-section[data-width="wide"] .cta-note {
  max-width: 1000px;
  margin-left: auto;
  margin-right: auto;
}
/* Full = no cap on heading/note, but the subtitle keeps its
   existing 480px reading-width so long subheads don't become
   walls of text. */
.tiq-cta-section[data-width="full"] .cta-heading,
.tiq-cta-section[data-width="full"] .cta-note {
  max-width: none;
}
.cta-sub,
.entry-content .cta-sub {
  font-size: clamp(15px, 2vw, 18px); color: rgba(255,255,255,0.6);
  max-width: 480px; margin: 0 auto 40px;
  font-weight: 300; line-height: 1.7;
  position: relative; z-index: 1;
}
.cta-actions {
  display: flex; justify-content: center; gap: 14px; flex-wrap: wrap;
  position: relative; z-index: 1;
}
.cta-note,
.entry-content .cta-note {
  font-size: 13px; color: rgba(255,255,255,0.35);
  margin-top: 20px; position: relative; z-index: 1;
}
/* ── Pill tags ──────────────────────────────────────────────────────────────── */
.pill-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 24px; }
.pill {
  background: var(--mist); color: var(--pine);
  border-radius: 99px; font-size: 13px; font-weight: 500;
  padding: 5px 14px;
}
/* Linked pill — same shape as the plain pill but interactive. Reset
   the underline that the entry-content prose rules would otherwise
   apply and lean into a soft background swap on hover instead. */
a.pill,
a.pill--link {
  text-decoration: none;
  transition: background-color .2s ease, color .2s ease, transform .2s ease;
}
a.pill:hover,
a.pill--link:hover {
  background: var(--moss);
  color: #fff;
  text-decoration: none;
  transform: translateY(-1px);
}
.entry-content .tiq-hero p,
.entry-content .tiq-cta-section p { max-width: none; }
/* Five across needs the full container. Between here and the 960px
   two-column rule a long sequence goes three up — which wraps 5 as
   3 + 2 with the pair centred under the middle, rather than as a
   column of leftovers. --steps-mid is set by steps() alongside the
   count: a four-step row still goes four across here, and most of
   them are four. */
@media (max-width: 1120px) {
  .steps-row { --steps-cols: var(--steps-mid, var(--steps-n, 4)); }
}
@media (max-width: 960px) {
  .features-header { grid-template-columns: 1fr; gap: 24px; }
  .features-grid    { grid-template-columns: repeat(2, 1fr); }
  .steps-row        { --steps-cols: 2; --steps-gap: 48px; }
  .steps-row::before { display: none; }
}
@media (max-width: 640px) {
  /* Features */
  .features-grid { grid-template-columns: 1fr; gap: 1px; }
  /* Steps */
  .steps-row { --steps-cols: 1; --steps-gap: 36px; }
  /* CTA */
  .cta-actions { flex-direction: column; align-items: center; }
  .cta-actions .btn-primary,
  .cta-actions .btn-ghost {
    text-align: center; justify-content: center;
    align-self: stretch;
  }
  /* Per-button "Hide on mobile" toggle — keeps the button on
     desktop, hides it under the mobile breakpoint so a busy
     multi-CTA section can collapse cleanly on phones. */
  .cta-actions .is-mobile-hidden { display: none !important; }
}
.maint-icon .tiq-icon-svg { width: 20px; height: 20px; display: block; }
.stp-section-title {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(36px, 5.5vw, 60px);
  color: var(--ink); line-height: 1; letter-spacing: 0.04em;
}
.stp-section-title.light { color: var(--white); }
.stp-tag-pill {
  background: rgba(0,0,0,0.04); color: var(--mid);
  font-family: 'DM Mono', monospace; font-size: 11px; font-weight: 700;
  letter-spacing: 0.1em; text-transform: uppercase;
  padding: 5px 12px; border-radius: 99px;
  border: 1px solid rgba(0,0,0,0.08);
}
/* ── Section dividers ───────────────────────────────────────────────────────── */

/* Any block that has a divider must be position:relative.
   The JS filter adds this as an inline style, but we also cover it via class
   for future-proofing and the editor preview. */
.section-divider,
.stp-section-divider {           /* keep .stp-* for backwards-compat */
  position: absolute;
  bottom: 0; left: 0;
  width: 100%;
  overflow: hidden;
  line-height: 0;
  pointer-events: none;
  z-index: 2;                    /* sits above section background, below content */
}
.section-divider svg,
.stp-section-divider svg {
  display: block;
  width: 100%;
}
@media (max-width: 768px) {
  .section-divider svg,
  .stp-section-divider svg {
    height: 50% !important;
  }
}
/* ─── Smartphone Mockup — two-phone group ─────────────────────────────────── */
.tiq-smartphone-group {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: clamp(16px, 3vw, 40px);
  padding: 20px 0;
  width: 100%;
  flex-wrap: wrap;
}
.tiq-smartphone-group[data-align="left"]  { justify-content: flex-start; }
.tiq-smartphone-group[data-align="right"] { justify-content: flex-end;   }
/* DOM order is [main, secondary]; row-reverse puts secondary first. */
.tiq-smartphone-group[data-second-pos="left"] { flex-direction: row-reverse; }
.tiq-smartphone-group .tiq-smartphone-wrap {
  padding: 0;
  width: var(--tiq-phone-w);
  flex-shrink: 0;
}
@media (max-width: 640px) {
  .tiq-smartphone-group {
    gap: 12px;
  }
  .tiq-smartphone-group .tiq-smartphone-wrap {
    --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 42vw);
  }
}
/* ─── Smartphone Mockup Block ──────────────────────────────────────────────── */
/* All frame dimensions scale proportionally from --tiq-phone-w
   (reference ratios taken from a 300×620 phone with 16px padding,
   46px outer radius, 32px bezel radius). JS sets --tiq-phone-w-user
   inline; the stylesheet derives --tiq-phone-w from it so media queries
   can freely cap it without fighting inline styles.                         */
.tiq-smartphone-wrap {
  --tiq-phone-w: var(--tiq-phone-w-user, 300px);
  --tiq-pad:    calc(var(--tiq-phone-w) * 16 / 300);
  --tiq-rad:    calc(var(--tiq-phone-w) * 46 / 300);
  --tiq-brad:   calc(var(--tiq-phone-w) * 32 / 300);
  --phone-body:  #1c1c24;
  --phone-edge:  #2e2e3e;
  --phone-shine: #3a3a50;
  --screen-bg:   #000;
  width: 100%;
  display: flex;
  justify-content: center;
  padding: 20px 0;
}
.tiq-smartphone-wrap[data-align="left"]  { justify-content: flex-start; }
.tiq-smartphone-wrap[data-align="right"] { justify-content: flex-end; }
.tiq-smartphone-wrap[data-frame="light"] {
  --phone-body:  #d6d6dc;
  --phone-edge:  #ececf0;
  --phone-shine: #ffffff;
}
/* NO aspect-ratio, deliberately — the picture decides the shape.
   ────────────────────────────────────────────────────────────────
   A fixed 300/620 frame fits exactly one phone. Feed it a simulator
   capture (1179x2556) and it letterboxes; feed it an iPhone SE shot
   (750x1334) and it letterboxes harder, in a completely different
   proportion. The old answer was a black strip across the top, which
   made SE screenshots legible by pushing them down and made every
   OTHER screenshot look like it had a bar on it.

   The frame is now a bezel around whatever it is given: the media is
   width: 100% with height: auto, so the screen is exactly the
   picture's own aspect and the phone is as tall as it needs to be. A
   Pro Max shot renders a tall phone and an SE shot a stubby one,
   which is what those two devices actually look like. Nothing is
   cropped, nothing is letterboxed, and no strip is needed on either.

   Phones sit on `align-items: flex-end` in a group, so two of
   different heights line up at the bottom rather than floating. */
.tiq-smartphone {
  position: relative;
  width: 100%;
  max-width: var(--tiq-phone-w);
  background: linear-gradient(160deg,
    var(--phone-shine) 0%,
    var(--phone-edge)  30%,
    var(--phone-body)  60%,
    #141420 100%);
  border-radius: var(--tiq-rad);
  box-shadow:
    0 0 0 1px rgba(255,255,255,0.07),
    0 0 0 2px rgba(0,0,0,0.8),
    inset 0 1px 0 rgba(255,255,255,0.12),
    inset 0 -1px 0 rgba(0,0,0,0.5),
    0 40px 80px rgba(0,0,0,0.7),
    0 0 60px rgba(110,231,247,0.06);
  padding: var(--tiq-pad);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.tiq-smartphone-wrap[data-frame="light"] .tiq-smartphone {
  box-shadow:
    0 0 0 1px rgba(0,0,0,0.08),
    inset 0 1px 0 rgba(255,255,255,0.6),
    inset 0 -1px 0 rgba(0,0,0,0.1),
    0 40px 80px rgba(0,0,0,0.25);
}
.tiq-smartphone:hover {
  transform: translateY(-4px) rotate(-0.4deg);
}
/* Side buttons */
.tiq-smartphone__btn {
  position: absolute;
  background: linear-gradient(180deg, var(--phone-shine), var(--phone-edge));
}
.tiq-smartphone__btn--vol-up,
.tiq-smartphone__btn--vol-down {
  left: calc(var(--tiq-phone-w) * -4 / 300);
  width: calc(var(--tiq-phone-w) * 4 / 300);
  height: calc(var(--tiq-phone-w) * 40 / 300);
  border-radius: 2px 0 0 2px;
  box-shadow: -1px 0 3px rgba(0,0,0,0.5);
}
.tiq-smartphone__btn--vol-up   { top: calc(var(--tiq-phone-w) * 130 / 300); }
.tiq-smartphone__btn--vol-down { top: calc(var(--tiq-phone-w) * 185 / 300); }
.tiq-smartphone__btn--power {
  right: calc(var(--tiq-phone-w) * -4 / 300);
  top:   calc(var(--tiq-phone-w) * 155 / 300);
  width: calc(var(--tiq-phone-w) * 4 / 300);
  height: calc(var(--tiq-phone-w) * 60 / 300);
  border-radius: 0 2px 2px 0;
  box-shadow: 1px 0 3px rgba(0,0,0,0.5);
}
/* Screen bezel */
.tiq-smartphone__bezel {
  position: relative;
  width: 100%;
  background: #0a0a0a;
  border-radius: var(--tiq-brad);
  overflow: hidden;
  box-shadow: inset 0 0 20px rgba(0,0,0,0.8);
}
/* ── The classic frame: an SE has a forehead and a chin ───────────
   ────────────────────────────────────────────────────────────────
   An iPhone SE is not a short modern iPhone. Its screen is 16:9 and
   it carries a real bezel above and below, with a round home button
   in the chin. Wrapping an SE capture in an edge-to-edge frame makes
   a phone that exists nowhere — stubby, all screen, no chin — and it
   reads as a rendering mistake rather than a device.

   So both are drawn on every phone and shown only when the picture
   in it is classic-aspect. `data-classic` is stamped by the build
   from the dimensions it measured, and corrected in the browser when
   an upload replaced the picture after the page was built — see
   src/client/phoneFrame.js.

   Everything scales from --tiq-phone-w like the rest of the frame,
   so one number still drives the whole mockup. */
.tiq-smartphone__brow,
.tiq-smartphone__chin {
    display: none;
    position: relative;
    flex: 0 0 auto;
    background: #000;
}
.tiq-smartphone-wrap[data-classic="true"] .tiq-smartphone__brow {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: calc(var(--tiq-phone-w) * 10 / 300);
    height: calc(var(--tiq-phone-w) * 46 / 300);
}
.tiq-smartphone-wrap[data-classic="true"] .tiq-smartphone__chin {
    display: flex;
    align-items: center;
    justify-content: center;
    height: calc(var(--tiq-phone-w) * 62 / 300);
}
/* Earpiece slit and the front camera beside it. Small, dark, and not
   quite black — a pure-black detail on a pure-black bezel is
   invisible, which is the same as not drawing it. */
.tiq-smartphone__speaker {
    width: calc(var(--tiq-phone-w) * 46 / 300);
    height: calc(var(--tiq-phone-w) * 5 / 300);
    border-radius: 999px;
    background: #1c1c22;
}
.tiq-smartphone__frontcam {
    width: calc(var(--tiq-phone-w) * 7 / 300);
    height: calc(var(--tiq-phone-w) * 7 / 300);
    border-radius: 50%;
    background: #14141c;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.05);
}
/* The home button: a ring rather than a filled circle, which is what
   it looks like on the device and what stops it reading as a dot. */
.tiq-smartphone__home {
    width: calc(var(--tiq-phone-w) * 42 / 300);
    height: calc(var(--tiq-phone-w) * 42 / 300);
    border-radius: 50%;
    background: #0a0a0a;
    box-shadow:
        inset 0 0 0 1px rgba(255, 255, 255, 0.14),
        0 0 0 1px rgba(0, 0, 0, 0.8);
}
/* A classic frame squares off its screen corners: the rounded display
   is a modern feature, and rounding an SE's screen under a chin looks
   like a mistake in the other direction. */
.tiq-smartphone-wrap[data-classic="true"] .tiq-smartphone__bezel {
    display: flex;
    flex-direction: column;
}

/* The `__topbar` and `__island` rules that used to sit here are gone
   with the bar itself. Neither element was ever rendered by
   components.js — the bar people saw was the screen's top inset, and
   the dark pill above the clock is part of the SIMULATOR CAPTURE, not
   chrome this stylesheet drew. Drawing an island over a screenshot
   that already contains one is what forced the strip into existence,
   and an iPhone SE has no island to draw at all. */
/* Screen content — starts below the top status bar.
   Top corners are square (flat under the bar); bottom corners get rounded
   by the bezel's overflow:hidden clip so the phone silhouette is preserved. */
/* Flush to the bezel, top included. The inset here WAS the black bar
   — the bezel's own background showing through above the screen. Both
   kinds of capture already contain their real status bar, so there is
   nothing for a drawn strip to stand in for. */
.tiq-smartphone__screen {
  position: relative;
  background: var(--screen-bg);
  overflow: hidden;
  border-radius: 0;
  line-height: 0;
}
/* height: auto is what makes any capture fit. `object-fit: contain`
   in a fixed box was the letterbox; at natural height there is no box
   to fit into and no bars to fill. */
.tiq-smartphone__media,
.tiq-smartphone__bg {
  width: 100%;
  height: auto;
  display: block;
  border: 0;
  background: var(--screen-bg);
}
.tiq-smartphone__placeholder {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  background: linear-gradient(160deg,#0f0f1a 0%, #060610 100%);
  color: rgba(255,255,255,0.55);
  font-family: 'DM Mono', monospace;
  font-size: 11px;
  text-align: center;
  padding: 16px;
}
.tiq-smartphone__placeholder span { font-size: 36px; }
/* Home bar */
.tiq-smartphone__home-bar {
  position: absolute;
  bottom: calc(var(--tiq-phone-w) * 8 / 300);
  left: 50%;
  transform: translateX(-50%);
  width:  calc(var(--tiq-phone-w) * 100 / 300);
  height: calc(var(--tiq-phone-w) * 4 / 300);
  background: rgba(255,255,255,0.25);
  border-radius: 3px;
  z-index: 20;
}
/* Glare overlay */
.tiq-smartphone__glare {
  position: absolute;
  top:    calc(var(--tiq-phone-w) * 16 / 300);
  left:   calc(var(--tiq-phone-w) * 16 / 300);
  right: 60%;
  bottom: 50%;
  background: linear-gradient(135deg, rgba(255,255,255,0.04) 0%, transparent 100%);
  border-radius: var(--tiq-brad) 8px 0 0;
  pointer-events: none;
  z-index: 30;
}
/* ── Trail Conditions feature block ─────────────────────────────────────────
   Two-column pitch section for the AI-powered Trail Conditions feature.
   Left side: sales copy + pill tags + icon bullet list + CTA.
   Right side: decorative mockup of the live dashboard card. Offwhite
   background like other light marketing sections. */

.tiq-trail-conditions {
  background: var(--offwhite);
  padding: clamp(60px, 8vw, 100px) clamp(20px, 4vw, 48px);
}
.tiq-trail-conditions__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(40px, 6vw, 80px);
  align-items: center;
  max-width: 1200px;
  margin: 0 auto;
}
.tiq-trail-conditions__content .pill-tags { margin: 18px 0 28px; }
.tiq-trail-conditions__cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 28px;
  padding: 14px 26px;
  background: var(--accentlt, #ef970c);
  color: #fff;
  font-size: 15px;
  font-weight: 600;
  text-decoration: none;
  border-radius: 10px;
  transition: background 0.2s, transform 0.2s;
}
.tiq-trail-conditions__cta:hover {
  background: var(--accent, #dd9b36);
  transform: translateY(-1px);
}
/* Feature bullet list — icon + title + desc */
.tiq-tc-features {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.tiq-tc-feature {
  display: flex;
  align-items: flex-start;
  gap: 14px;
}
.tiq-tc-feature__icon {
  flex-shrink: 0;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: linear-gradient(135deg, var(--mist, #e8f0e5) 0%, #fff 100%);
  color: var(--moss, #4a6741);
  display: flex;
  align-items: center;
  justify-content: center;
}
.tiq-tc-feature__icon .tiq-icon-svg { width: 22px; height: 22px; display: block; }
.tiq-tc-feature__title {
  display: block;
  font-size: 15px;
  font-weight: 600;
  color: var(--forest, #1a2e1a);
  margin-bottom: 3px;
  line-height: 1.35;
}
.tiq-tc-feature__desc {
  display: block;
  font-size: 13.5px;
  color: var(--mid, #475569);
  line-height: 1.55;
}
/* ── Decorative mockup of the live Trail Conditions card ──
   Built entirely in CSS, no live data. Designed to feel like a real
   screenshot of the dashboard at a glance. */
.tiq-trail-conditions__visual {
  position: relative;
  background: linear-gradient(160deg, var(--forest, #1a2e1a) 0%, var(--pine, #2d4a2d) 100%);
  border-radius: var(--radiuslg, 18px);
  padding: clamp(28px, 4vw, 48px);
  min-height: 380px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.tiq-trail-conditions__visual::before {
  /* Soft accent glow behind the card */
  content: '';
  position: absolute;
  top: -40%; right: -30%;
  width: 80%; height: 120%;
  background: radial-gradient(ellipse at top right, rgba(239,151,12,0.22) 0%, transparent 60%);
  pointer-events: none;
}
@media (max-width: 900px) {
  .tiq-trail-conditions__grid { grid-template-columns: 1fr; }
  .tiq-trail-conditions__visual { min-height: 300px; padding: 28px 20px; }
}
/* ── Feature Showcase (customizable twin of Trail Conditions) ──────────────
   Same layout, but the visual column is an InnerBlocks area rather than
   the baked-in Trail Conditions mockup. Strips the forest gradient +
   glow so whatever block the author drops in looks natural. All colours
   are overrideable from the block inspector via inline style. */
.tiq-feature-showcase.tiq-trail-conditions { background: transparent; }
/* "Hidden (text only)" visualLayout — collapses the grid to a single
   centred text column. The visual column itself is hidden via
   display:none so its InnerBlocks data survives a flip back to
   Left/Right without re-entry. */
.tiq-feature-showcase--no-visual .tiq-trail-conditions__grid {
  grid-template-columns: 1fr;
  max-width: 720px;
}
.tiq-feature-showcase--no-visual .tiq-trail-conditions__visual {
  display: none;
}
.tiq-feature-showcase .tiq-trail-conditions__visual {
  background: transparent;
  padding: 0;
  min-height: 0;
  overflow: visible;
}
.tiq-feature-showcase .tiq-trail-conditions__visual::before { display: none; }
/* WordPress wraps InnerBlocks children in an extra div; make that fill
   the column on desktop so images / mockups stretch naturally. */
.tiq-feature-showcase .tiq-trail-conditions__visual > .block-editor-inner-blocks,
.tiq-feature-showcase .tiq-trail-conditions__visual > .block-editor-inner-blocks > .block-editor-block-list__layout,
.tiq-feature-showcase .tiq-trail-conditions__visual > div {
  width: 100%;
}
/* Stack child blocks vertically inside the visual column — affects both
   the frontend save output and the editor's block-list wrapper. Without
   this, the editor's default flex row can collapse siblings side-by-side. */
.tiq-feature-showcase .tiq-trail-conditions__visual,
.tiq-feature-showcase .tiq-trail-conditions__visual > .block-editor-inner-blocks,
.tiq-feature-showcase .tiq-trail-conditions__visual > .block-editor-inner-blocks > .block-editor-block-list__layout {
  display: flex;
  flex-direction: column;
  gap: 16px;
}
/* Show each screen when its step class is active on .tiq-fa */
.tiq-fa--step-types   .tiq-fa__add-screen--types,
.tiq-fa--step-confirm .tiq-fa__add-screen--confirm,
.tiq-fa--step-form    .tiq-fa__add-screen--form {
  transform: translateY(0);
}
/* While the "Confirm location" sheet is open, hide every real trail pin
   so only the user's blue GPS dot (and any just-dropped new pin) is
   visible — the intent is "drop a report at this spot". */
.tiq-fa--step-confirm .tiq-fa__pin:not(.tiq-fa__new-pin) {
  display: none;
}
/* ── Flexible Page template ─────────────────────────────────────────
 * Applied via page-flex.php (Template Name: "Flexible Page"). Lets
 * full-bleed trailsiq/* sections (hero, features-grid, cta,
 * pricing, testimonials, trail-conditions, feature-showcase) run
 * edge-to-edge while plain prose blocks (paragraph, heading, list,
 * quote, separator, plain image) sit in the same 760px reading
 * column the Content Page template uses.
 *
 * Title rendering is conditional on the _tiq_show_title meta —
 * see page-flex.php and assets/js/page-meta.js. When shown, the
 * title sits in the reading column with top padding to clear the
 * fixed nav. When hidden, the page is responsible for clearing
 * the nav itself (typically via a hero block's own padding).
 */
.tiq-page-flex__header {
  padding: calc(var(--nav-h) + 56px) clamp(20px, 5vw, 32px) 0;
  background: var(--offwhite);
}
/* ── Force-break full-bleed trailsiq sections on the Flex template ───
 * Belt-and-braces: regardless of any wrapping group / container with
 * inherited width on the Flexible Page template, any known trailsiq
 * full-bleed section (or anything explicitly tagged `alignfull`)
 * breaks out to the viewport edges via the standard 50%-50vw trick.
 * This covers the case where an author wrapped content in a
 * constrained group AND dropped a hero inside — without this rule
 * the hero would inherit the 760px clamp from the group.
 */
.tiq-page-flex .alignfull,
.tiq-page-flex .tiq-hero,
.tiq-page-flex .tiq-stats-bar,
.tiq-page-flex .tiq-features-grid,
.tiq-page-flex .tiq-cta-section,
.tiq-page-flex .tiq-pricing-section,
.tiq-page-flex .tiq-testimonials-section,
.tiq-page-flex .tiq-trail-conditions,
.tiq-page-flex .tiq-feature-showcase,
.tiq-page-flex .tiq-what-is,
.tiq-page-flex .tiq-maintenance-section,
.tiq-page-flex .tiq-data-reports,
.tiq-page-flex .tiq-wordpress-section {
  width: 100vw !important;
  max-width: 100vw !important;
  margin-left: calc(50% - 50vw) !important;
  margin-right: calc(50% - 50vw) !important;
  padding-left: 0;
  padding-right: 0;
}
/* The breakout rule above zeros horizontal padding for all full-bleed
   sections, but the CTA block manages its own side padding so restore it. */
.tiq-page-flex .tiq-cta-section {
  padding-left: clamp(20px, 4vw, 48px);
  padding-right: clamp(20px, 4vw, 48px);
}
.tiq-contact-section .section-label {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #dd9b36;
  margin-bottom: 12px;
}
.tiq-contact-section .section-title {
  font-family: 'Bebas Neue', sans-serif;
  font-size: clamp(32px, 5vw, 48px);
  line-height: 1.05;
  margin: 0 0 12px;
  color: #1b2e1a;
}
.tiq-contact-section .section-body {
  font-size: 17px;
  line-height: 1.55;
  color: #4b5563;
  max-width: 620px;
  margin: 0 auto;
}
/* ─────────────────────────────────────────────────────────────────────────────
   Smartphone block — buttons under the phone
   .tiq-smartphone-with-actions wraps the phone frame (or .tiq-smartphone-group)
   plus a .tiq-smartphone-actions row. Inherits the block's data-align so the
   row lines up with the phone above it.
   ───────────────────────────────────────────────────────────────────────────── */

.tiq-smartphone-with-actions {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 28px;
}
.tiq-smartphone-with-actions[data-align="left"]  { align-items: flex-start; }
.tiq-smartphone-with-actions[data-align="right"] { align-items: flex-end; }
.tiq-smartphone-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}
.tiq-smartphone-with-actions[data-align="left"]  .tiq-smartphone-actions { justify-content: flex-start; }
.tiq-smartphone-with-actions[data-align="right"] .tiq-smartphone-actions { justify-content: flex-end; }
.tiq-smartphone-actions .btn-primary,
.tiq-smartphone-actions .btn-ghost {
  margin: 0;
}

/* components.js */

.hero {
    background: var(--forest);
    color: var(--white);
    padding-block: clamp(var(--space-m), 9vw, var(--space-2xl));
    /* For .hero-contours, which is absolute against this. Isolation
       keeps the whole arrangement — backdrop, contours, copy — in its
       own stacking context, so a z-index inside a hero cannot reach
       out and land on the fixed nav above it. .hero--photo already
       declared both for the same reason; they are up here now because
       a hero with no photograph has the layers too. */
    position: relative;
    isolation: isolate;
}
/* The copy, over both decorative layers. Also formerly .hero--photo
   only — a hero with no picture had nothing absolute in it to sit
   over, and now does. */
.hero > .tiq-container { position: relative; z-index: 1; }
.hero .eyebrow { color: var(--fern); }

/* ── Contour lines ────────────────────────────────────────────────
   The site's own subject, used as its texture: /assets/hero-contours
   .svg is a real height field traced by marching squares, generated
   by marketing/tools/hero-contours.mjs.

   background-size: 100% 100% rather than cover, because the SVG
   carries preserveAspectRatio="xMidYMid slice" and so covers ITSELF.
   Handing it the element's exact box lets it do the cropping once;
   cover would scale the whole image first and then the slice would
   crop again, throwing away most of the field on a tall phone hero.

   Masked to a ramp instead of a flat wash. A hero's copy sits left,
   so the lines are quietest under the headline and fullest out in the
   empty right-hand side — the same argument the photo scrim already
   makes from the same edge. It never reaches zero: a texture that
   stops has an edge, and an edge is a thing to notice. */
.hero-contours {
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image: url(/assets/hero-contours.svg);
    background-size: 100% 100%;
    background-position: center;
    background-repeat: no-repeat;
    opacity: 0.22;
    -webkit-mask-image: linear-gradient(90deg, rgba(0,0,0,0.32) 0%, rgba(0,0,0,0.6) 40%, #000 78%);
    mask-image: linear-gradient(90deg, rgba(0,0,0,0.32) 0%, rgba(0,0,0,0.6) 40%, #000 78%);
}
/* Over a photograph the ground already has texture of its own, and a
   second one at full strength reads as a dirty screen — the same
   thing that keeps hillshading off the satellite basemap. Halved
   rather than dropped, so a photo hero still belongs to the set.

   :has(.tiq-backdrop) and NOT .hero--photo, which was the first
   version of this rule and was wrong on nearly every page. That class
   means "this hero declares a picture SLOT", which most of them do
   and which stays true forever whether or not anybody ever uploads
   one — so it dimmed the contours on heroes with no photograph at
   all, and 0.22 was reached by almost nothing. The backdrop element
   REMOVES ITSELF when the image 404s or arrives as the 1x1 blank
   (see backdrop()), so its presence in the live DOM is the only
   honest answer to "is there a picture behind this", and :has reads
   the DOM after that removal. */
.hero:has(.tiq-backdrop) .hero-contours { opacity: 0.11; }
/* The ramp is paid for by the copy sitting in the left half, and the
   hero stacks to one column before 900px — the headline then runs
   the full width and there is no quiet side to ramp away from. So the
   mask comes off and the whole layer steps back instead. */
@media (max-width: 900px) {
    .hero-contours {
        /* Softer than the ramp's average, not just un-ramped. A phone
           holds the copy against the full width of the texture with
           no quiet column to put it in, and the screen is a hand's
           length from the eye rather than an arm's — the same value
           that reads as a whisper on a monitor reads as pattern
           there. */
        opacity: 0.11;
        -webkit-mask-image: none;
        mask-image: none;
    }
    .hero:has(.tiq-backdrop) .hero-contours { opacity: 0.06; }
}
/* Decoration, and printers render it as grey hatching across the
   headline. */
@media print { .hero-contours { display: none; } }
/* The theme's own hero h1, to the value. No max-width: the WordPress
   rule carries one and it is overridden, so the headline runs to the
   column and breaks where the markup says. */
.hero-title {
    font-size: clamp(52px, 9vw, 124px);
    font-weight: 600;
    line-height: 0.92;
    letter-spacing: 0.03em;
    margin-bottom: 28px;
}
.hero-title em { font-style: normal; color: var(--sage); }
.hero-sub {
    margin-top: 20px;
    max-width: 62ch;
    font-size: var(--text-large);
    color: rgba(255, 255, 255, 0.74);
    line-height: 1.55;
}
/* An opening claim carried inside the paragraph rather than as a
   second headline. Bold ALONE does almost nothing here, because the
   paragraph is already 74% white and weight on a dimmed colour reads
   as a rendering artefact — so it takes the full white as well, and
   the contrast step does as much of the work as the weight. 600
   rather than 700: DM Sans is loaded as a 300-700 variable face so
   both are real, and 700 under a display headline reads as a heading
   of its own. */
.hero-sub strong { font-weight: 600; color: var(--white); }
.hero-actions { margin-top: 32px; display: flex; flex-wrap: wrap; gap: 12px; }
.store-badges-label {
    margin-top: 26px;
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    opacity: 0.6;
}
.store-badges { margin-top: 10px; display: flex; flex-wrap: wrap; gap: 12px; align-items: center; }
/* ONE child inside .hero-visual, which the theme lays out as a flex
   ROW. The badges first went in beside the phone group as extra flex
   children carrying width:100% — so each demanded the full container,
   the hero's auto-sized column ballooned, and the 1fr text column
   collapsed to about one word per line. Stacking them under a single
   wrapper leaves the ported rule alone and cannot fight it.
   (No backticks in here: this block is a template literal.) */
.hero-visual-stack {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-width: 0;
}
.hero-visual-stack .store-badges { justify-content: center; }
.hero-visual-stack .store-badges-label { margin-top: 18px; text-align: center; }
.store-badges img { height: 44px; width: auto; }
.hero-actions .btn-secondary { color: var(--white); border-color: rgba(255, 255, 255, 0.32); }
.hero-actions .btn-secondary:hover { border-color: var(--white); }
.hero-note {
    margin-top: 18px;
    font-size: var(--text-small);
    color: rgba(255, 255, 255, 0.55);
    max-width: 54ch;
}



.showcase {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: clamp(24px, 5vw, 64px);
    align-items: start;
    padding-block: var(--space-m);
    border-top: 1px solid rgba(26, 26, 24, 0.09);
}
.showcase:first-child { border-top: 0; padding-top: 0; }
.showcase--solo { grid-template-columns: minmax(0, 1fr); }
/* The theme sizes a phone only inside a hero — its base rule is
   width:100% with 20px of vertical padding, meant for a phone sitting
   alone in a column. Dropped into a showcase, each one demanded the
   whole column and the pair drifted right with a gap in front of it.
   These are the theme's own hero overrides, applied to the one other
   place a phone group now appears. */
/* The pills carry a top margin and the theme's feature list carries
   margin: 0, so as adjacent siblings they sat flush and the first
   icon tile ran under the last pill. The gap belongs where the two
   meet rather than on either one alone — the pills also appear in a
   section head, where the spacing is already right. */
.showcase-text .pill-tags + .tiq-tc-features,
.showcase-text .pill-tags + .showcase-action { margin-top: 32px; }
.showcase-text .tiq-tc-features + .showcase-action { margin-top: 28px; }
/* With a picture in the right column the points move UNDER the
   paragraph, where the theme's zero top margin left them touching it.
   In the right column they are a column and read as one; under a
   paragraph they are a new thing and need the break to say so. */
.showcase-text p + .tiq-tc-features { margin-top: 34px; }

.showcase-visual .tiq-smartphone-wrap {
    padding: 0;
    width: var(--tiq-phone-w);
    flex-shrink: 0;
}
.showcase-visual .tiq-smartphone-group { padding: 0; width: auto; }

.showcase-visual {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 28px;
}
/* The hidden ATTRIBUTE is a UA rule — display:none at the lowest
   priority there is — so the display:flex above BEATS it. An optional
   visual therefore took its grid column while still hidden, which
   pushed the points list into the previous row and put it above the
   text. The box looked wrong in exactly the state it is supposed to
   look untouched in, and the built HTML was byte-identical either
   way, so nothing but a render could have caught it.
   Any author display on an element that may carry hidden needs this
   line; it is not specific to this component. */
.showcase-visual[hidden] { display: none; }
.showcase-visual .store-badges { justify-content: center; margin-top: 0; }
.showcase-visual .store-badges-label { display: none; }
/* A showcase carrying a picture centres its columns against each
   other; a points list still aligns to the top, where a list belongs. */
.showcase:has(.showcase-visual) { align-items: center; }
/* A solo showcase keeps a readable measure instead of running the
   full width of the container. */
.showcase--solo .showcase-text p { max-width: 62ch; }

/* The head's pill row needs air before whatever the section opens
   with — the steps ring sat directly under it and the two collided. */
.section-head .pill-tags { margin-bottom: 4px; }
/* A centred head centres everything in it, pills included — the
   theme's rule handles the text, and a flex row needs telling. */
.section-head.steps-header { margin-inline: auto; }
/* The split head spans the container, so the 720px measure that keeps
   a single-column head readable must come off — each of its two
   columns is already narrow enough on its own. */
.section-head.features-header { max-width: none; }
@media (max-width: 768px) {
    .section-head.features-header { display: block; }
    .section-head.features-header .section-body { margin-top: 16px; }
}
.section-head.steps-header .pill-tags { justify-content: center; }
.section-head + .steps-row { margin-top: var(--space-s); }
.showcase--flip .showcase-text { order: 2; }
.showcase h3 { font-size: var(--text-2xl); margin-bottom: 12px; }
.showcase-text p { color: var(--mid); max-width: 52ch; }
.showcase-action { margin-top: 20px; }
@media (max-width: 720px) {
    .showcase { grid-template-columns: 1fr; }
    .showcase--flip .showcase-text { order: 0; }
}
/* A frameWidth is how big the phone should be BESIDE the text, and
   that column stops being half the page long before the layout
   stacks: at 760px wide it is about 348px, so a 360px frame overhangs
   it and the bezel is cut down both sides. The inline width is a
   custom property precisely so a media query can cap it — same shape
   as the theme's rule for a phone GROUP, which learned this first.
   Capping here rather than asking every call site to pick a small
   enough number: the number a page passes is about the wide layout,
   and every phone on the site should narrow the same way. */
@media (max-width: 900px) {
    .showcase-visual .tiq-smartphone-wrap {
        --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 40vw);
    }
}
@media (max-width: 720px) {
    .showcase-visual .tiq-smartphone-wrap {
        --tiq-phone-w: min(var(--tiq-phone-w-user, 300px), 78vw);
    }
}

.cta-band {
    background: var(--forest);
    color: var(--white);
    padding-block: clamp(var(--space-m), 7vw, var(--space-xl));
    text-align: center;
}
.cta-band p {
    margin: 16px auto 0;
    max-width: 56ch;
    color: rgba(255, 255, 255, 0.74);
}
.cta-actions { margin-top: 28px; display: flex; justify-content: center; flex-wrap: wrap; gap: 12px; }
.cta-actions .btn-secondary { color: var(--white); border-color: rgba(255, 255, 255, 0.32); }

.fact-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    gap: 20px;
}
.fact dd {
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    line-height: 1;
    letter-spacing: 0.02em;
}
.fact dt {
    font-size: var(--text-small);
    color: var(--mid);
    order: 2;
    margin-top: 6px;
}
.fact { display: flex; flex-direction: column; }

/* The toolkit band is dark, like the theme's features-grid section.
   The heading trio inside takes its light variants. */
.page-section--dark { background: var(--forest); color: var(--white); }

/* The same grid, on a light band.
   ────────────────────────────────────────────────────────────────
   The theme only ever put .features-grid on the forest section, so
   its card paints the title white and the body at 50% white. Drop
   that grid onto a light section — which every use-case page wanted
   to do, to keep the page alternating instead of running two dark
   bands together — and each card renders as an icon above two lines
   of almost-invisible text.

   Worth knowing that this passed a build, a lint and the harness.
   Nothing was missing and nothing threw; the words were present in
   the HTML, at a contrast ratio of about 1.2:1. It reads as a font
   that failed to load rather than as a colour bug, which is the kind
   of thing only a screenshot catches.

   An override rather than a second component: same markup, same
   class names, one more place it is allowed to live. */
/* Count modifiers, and the media query around them is load-bearing.
   theme.css already collapses .features-grid to one column under
   640px. These rules are a single class too, so specificity ties and
   SOURCE ORDER decides — this file is emitted after the theme, so an
   unconditional --2 wins on a phone and pins two cards side by side
   at about fourteen characters a line. Only widen a row where the
   theme was going to give it more than one column anyway. */
@media (min-width: 641px) {
    .features-grid--1 { grid-template-columns: 1fr; }
    .features-grid--2 { grid-template-columns: repeat(2, 1fr); }
}
.page-section--tint .feature-card {
    background: var(--white);
    border-color: rgba(26, 26, 24, 0.10);
}
.page-section--tint .feature-card:hover { background: var(--white); }
.page-section--tint .feature-title { color: var(--ink); }
.page-section--tint .feature-desc { color: var(--mid); }
/* The icon tile already reads on both grounds — it is the brand
   orange at low alpha, which sits on cream as happily as on forest —
   so it is deliberately left alone. */

/* A showcase point with no icon stays a plain line — pages are not
   made to invent an icon for every sentence. */
.showcase-point {
    position: relative;
    padding-left: 22px;
    font-size: var(--text-small);
    color: var(--mid);
    list-style: none;
}
.showcase-point::before {
    content: "";
    position: absolute; left: 0; top: 0.62em;
    width: 9px; height: 2px;
    background: var(--accent);
}

/* Dividers. Absolute against a positioned parent, sitting at its
   bottom edge and painted in the colour of the band BELOW — which is
   what makes a shape read as one section intruding into another
   rather than as an ornament on the seam. */
.section-divider {
    position: absolute;
    bottom: 0; left: 0;
    width: 100%;
    overflow: hidden;
    line-height: 0;
    pointer-events: none;
    z-index: 2;
}
.section-divider svg { display: block; width: 100%; }
@media (max-width: 768px) {
    .section-divider svg { height: 50%; }
}

/* The footer's own mountains sit ABOVE it rather than inside, so the
   footer owns its signature and no page has to remember to add it. */
.section-divider--above { bottom: 100%; }
/* ── Optional photographic backdrop ───────────────────────────────
   Behind a hero or a section, under a scrim that is always on — see
   backdrop() for why it cannot be optional. The section keeps its own
   padding and container; this only paints behind it. */
/* ── Audience strip ───────────────────────────────────────────────
   Sand on a green site. The colour is the whole mechanism — see
   audienceStrip() — so it is the one thing here not to tune down.

   Vertical padding is the other half of the brief: this sits between
   a full-height hero and the first real section, and anything tall
   enough to need scrolling past becomes an obstacle for the readers
   it is NOT addressed to. One row of 15px text at 12px padding lands
   near 60px, which is under half a nav bar. */
.aud-strip {
    background: var(--sand);
    color: var(--bark);
    padding-block: 12px;
    /* The hero above is dark and the section below is white, so the
       band needs no rule on top and one hairline underneath — without
       it, sand meeting white reads as a printing misregistration
       rather than as two bands. */
    border-bottom: 1px solid rgba(92, 63, 34, 0.14);
}
.aud-strip__inner {
    display: flex;
    align-items: center;
    gap: 10px 18px;
    flex-wrap: wrap;
}
/* The two words that do the work. Mono uppercase is the site's label
   voice already (eyebrows, footer headings), which is what makes this
   read as a signpost rather than as a sentence somebody has to
   finish. */
.aud-strip__who {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.14em;
    text-transform: uppercase;
    font-weight: 500;
    /* Bark, not earth. Earth on sand measures 4.47:1, which misses AA
       for normal text by a hundredth — and 11px uppercase mono is
       normal text by every definition, the large-text exemption
       starting at 24px. Bark is 8.01:1, and it also puts the weight
       on the two words the whole strip turns on. */
    color: var(--bark);
    white-space: nowrap;
}
.aud-strip__icon { display: inline-flex; }
/* 20px, matching every other icon on the site. At the 17px this
   started as, the route glyph's two 2px-radius end dots render about
   a pixel and a half across and disappear into the antialiasing —
   what survives is the S-curve between them, and a lone S beside a
   label reads as a letterform rather than as a mark. */
.aud-strip__icon svg { width: 20px; height: 20px; display: block; }
/* min-width: 0 so a long line wraps inside the row instead of forcing
   the flex item wider than the container and pushing the button off
   the end. */
.aud-strip__line {
    flex: 1 1 320px;
    min-width: 0;
    font-size: 15px;
    line-height: 1.45;
    color: var(--bark);
}
/* Bark rather than the site's accent amber. The primary CTA in the
   hero directly above is amber, and a second amber button 60px under
   it competes with the action the page actually wants — this one is
   a door, not a call. */
.aud-strip__cta {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    gap: 8px;
    background: var(--bark);
    color: var(--white);
    text-decoration: none;
    font-size: 14px;
    font-weight: 500;
    padding: 9px 18px;
    border-radius: var(--radius);
    transition: background 0.2s, gap 0.2s;
    white-space: nowrap;
}
.aud-strip__cta:hover { background: var(--earth); gap: 12px; }
.aud-strip__cta:focus-visible { outline: 2px solid var(--bark); outline-offset: 2px; }
@media (max-width: 600px) {
    /* Everything stacks here, so the strip stops being a 66px rule
       and becomes a block: three rows, two gaps and its own padding.
       Left alone that measured 148px, about a fifth of a phone
       screen for something most readers are scrolling past, so the
       spacing comes in and the line drops a point.

       The BUTTON does not shrink. 41px is already the floor for a
       comfortable tap target, and the one row here that has to stay
       comfortable is the row a builder is aiming at. */
    .aud-strip { padding-block: 10px; }
    .aud-strip__inner { gap: 7px 14px; }
    .aud-strip__line { flex-basis: 100%; font-size: 14px; line-height: 1.4; }
    /* Full width rather than sitting beside a wrapped line, where it
       lands under the text at an arbitrary indent and reads as
       belonging to nothing. */
    .aud-strip__cta { flex: 1 1 100%; justify-content: center; }
}
@media (prefers-reduced-motion: reduce) {
    .aud-strip__cta { transition: none; }
    .aud-strip__cta:hover { gap: 8px; }
}

/* .hero--photo's half of this moved up to .hero when the contour
   layer arrived — every hero has an absolutely-positioned child now,
   not only the ones with a picture. The section rules stay here. */
.page-section--photo { position: relative; isolation: isolate; }
.page-section--photo > .tiq-container { position: relative; z-index: 1; }

.tiq-backdrop { position: absolute; inset: 0; z-index: 0; overflow: hidden; }
.tiq-backdrop img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* The uploaded picture, layered over the repo one. A slot with no
   upload 404s and this layer paints nothing, which is exactly the
   fallback — see backdrop() for why it is a background rather than
   an img. */
.tiq-backdrop__live {
    position: absolute; inset: 0;
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
}
.tiq-backdrop__scrim {
    position: absolute; inset: 0;
    background: linear-gradient(180deg,
        rgba(12, 20, 12, 0.42) 0%,
        rgba(12, 20, 12, 0.28) 38%,
        rgba(12, 20, 12, 0.74) 84%,
        rgba(12, 20, 12, 0.90) 100%);
}
/* A hero's copy sits left, so it gets a second wash from that edge —
   a vertical gradient alone leaves the first line of a headline on
   whatever the photograph happens to be doing up there. */
.hero--photo .tiq-backdrop__scrim {
    background:
        linear-gradient(90deg, rgba(12, 20, 12, 0.72) 0%, rgba(12, 20, 12, 0.10) 62%),
        linear-gradient(180deg, rgba(12, 20, 12, 0.30) 0%, rgba(12, 20, 12, 0.62) 100%);
}


/* gallery.js */

/* ── bleed: full-width photograph with copy over it ─────────────── */
.tiq-bleed {
    position: relative;
    min-height: var(--bleed-h, 70svh);
    display: flex;
    align-items: flex-end;
    overflow: hidden;
    isolation: isolate;
}
.tiq-bleed--center { align-items: center; }
.tiq-bleed-media, .tiq-bleed-media img, .tiq-bleed-media .tiq-ridges {
    position: absolute; inset: 0; width: 100%; height: 100%;
}
.tiq-ridges svg { width: 100%; height: 100%; display: block; }
/* Named, quietly, for whoever is going to drop the photograph in. */
.tiq-ridges-slot {
    position: absolute; left: 18px; bottom: 14px;
    font-family: var(--font-mono); font-size: var(--text-xs);
    color: rgba(255, 255, 255, 0.28); letter-spacing: 0.06em;
}
.tiq-bleed-media img { object-fit: cover; }
.tiq-bleed-scrim {
    position: absolute; inset: 0;
    background: linear-gradient(180deg,
        rgba(12, 20, 12, 0.34) 0%,
        rgba(12, 20, 12, 0.10) 34%,
        rgba(12, 20, 12, 0.72) 82%,
        rgba(12, 20, 12, 0.90) 100%);
}
.tiq-bleed-copy { position: relative; width: 100%; padding: var(--space-l) 0 var(--space-m); }
.tiq-bleed-eyebrow {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.18em;
    text-transform: uppercase;
    color: var(--fern);
    margin-bottom: 14px;
}
.tiq-bleed-title {
    font-family: var(--font-display);
    font-size: clamp(44px, 6.4vw, 104px);
    line-height: 0.94;
    color: var(--white);
    max-width: 18ch;
    text-wrap: balance;
}
.tiq-bleed-title em { font-style: normal; color: var(--accent); }
.tiq-bleed-body {
    margin-top: 20px;
    font-size: var(--text-large);
    line-height: 1.55;
    color: rgba(255, 255, 255, 0.86);
    max-width: 46ch;
    font-weight: 300;
}
.tiq-bleed-copy .cta-actions,
.tiq-bleed-copy .tiq-bleed-actions { margin-top: 28px; display: flex; flex-wrap: wrap; gap: 12px; }
/* The secondary button is dark-on-light by default, which on a photograph
   is a button nobody can see — the first render of the hero had an
   invisible "See plans" beside the primary. */
.tiq-bleed .btn-secondary {
    color: var(--white);
    border-color: rgba(255, 255, 255, 0.55);
    background: rgba(255, 255, 255, 0.06);
}
.tiq-bleed .btn-secondary:hover {
    background: rgba(255, 255, 255, 0.16);
    border-color: var(--white);
}

/* ── figureRow: one crop of the app, beside forty words ─────────── */
.tiq-figure {
    display: grid;
    grid-template-columns: 1.15fr 1fr;
    gap: clamp(32px, 5vw, 76px);
    align-items: center;
    padding: clamp(40px, 6vw, 84px) 0;
}
.tiq-figure + .tiq-figure { border-top: 1px solid rgba(26, 26, 24, 0.08); }
.tiq-figure--flip .tiq-figure-media { order: 2; }

.tiq-figure-media { margin: 0; }
/* The crop sits on the page's own surface with a soft edge rather
   than a hard frame: it is a piece of the app, not a screenshot in a
   document, and a heavy border makes it read as the latter. */
.tiq-figure-img {
    width: 100%;
    height: auto;
    /* A crop taller than this stops being a figure beside a
       paragraph and becomes a poster with a caption. Capped and
       cropped from the top, which is where a card's meaning is. */
    max-height: 560px;
    object-fit: cover;
    object-position: left top;
    border-radius: 14px;
    border: 1px solid rgba(26, 26, 24, 0.09);
    box-shadow: 0 18px 48px -24px rgba(26, 46, 26, 0.45);
    background: var(--white);
}
.tiq-figure-media figcaption {
    margin-top: 12px;
    font-size: var(--text-small);
    color: var(--light);
}
.tiq-figure-eyebrow {
    font-family: var(--font-mono);
    font-size: var(--text-xs);
    letter-spacing: 0.16em;
    text-transform: uppercase;
    color: var(--moss);
    margin-bottom: 12px;
}
.tiq-figure-title {
    font-family: var(--font-display);
    font-size: clamp(30px, 3.4vw, 50px);
    line-height: 1.02;
    color: var(--ink);
    margin-bottom: 14px;
}
.tiq-figure-title em { font-style: normal; color: var(--moss); }
.tiq-figure-body { font-size: var(--text-med); line-height: 1.65; color: var(--mid); max-width: 44ch; }
.tiq-figure-link {
    display: inline-flex; align-items: center; gap: 8px;
    margin-top: 18px;
    font-size: var(--text-small); font-weight: 600;
    color: var(--moss); text-decoration: none;
}
.tiq-figure-link:hover { text-decoration: underline; }

/* ── cardStrip: the post-generator cards ────────────────────────── */
.tiq-card-strip {
    list-style: none; margin: 0; padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 20px;
}
.tiq-card-strip-item { margin: 0; }
.tiq-card-strip img, .tiq-card-strip .tiq-slot {
    width: 100%; height: auto; display: block;
    border-radius: 14px;
    box-shadow: 0 20px 50px -28px rgba(0, 0, 0, 0.55);
}

/* ── proofGrid: the long tail, shown rather than listed ─────────── */
.tiq-proof-grid {
    list-style: none; margin: 0; padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
    gap: 22px;
}
.tiq-proof-cell { margin: 0; }
/* Fixed-height window with the crop anchored top-left. These come in
   at whatever height their card was, and a grid of different heights
   reads as a mistake; cropping to a common window keeps the rhythm
   and keeps the part of each picture that carries the meaning. */
.tiq-proof-media {
    aspect-ratio: 4 / 3;
    overflow: hidden;
    border-radius: 12px;
    border: 1px solid rgba(26, 26, 24, 0.09);
    background: var(--white);
}
.tiq-proof-media img { width: 100%; height: 100%; object-fit: cover; object-position: left top; }
.tiq-proof-label {
    margin-top: 10px;
    font-size: var(--text-small);
    font-weight: 600;
    color: var(--ink);
}

/* ── the placeholder ────────────────────────────────────────────── */
.tiq-slot {
    aspect-ratio: var(--slot-ratio, 16 / 9);
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 6px; text-align: center; padding: 16px;
    color: rgba(255, 255, 255, 0.9);
    background:
        repeating-linear-gradient(45deg,
            rgba(255, 255, 255, 0.05) 0 12px,
            rgba(255, 255, 255, 0.10) 12px 24px),
        var(--pine);
    border: 2px dashed rgba(255, 255, 255, 0.35);
    border-radius: 12px;
}
.tiq-slot-name { font-family: var(--font-mono); font-size: var(--text-small); letter-spacing: 0.08em; }
.tiq-slot-meta { font-size: var(--text-xs); color: rgba(255, 255, 255, 0.6); }


@media (max-width: 860px) {
    .tiq-figure { grid-template-columns: 1fr; gap: 28px; }
    .tiq-figure--flip .tiq-figure-media { order: 0; }
    .tiq-bleed-title { max-width: none; }
}

/* device.js */

/* ── Device frames ───────────────────────────────────────────────── */
.tiq-device { position: relative; }

/* PARTIAL: the device is drawn whole and the frame is cut on ONE
   edge, so radii and bezels stay in a real device's proportions.

   It is clip-path rather than overflow: hidden, and that is the
   difference between a device running off an edge and a device sitting
   in a box. A tight overflow box cuts all four sides, so the drop
   shadow — which reaches ~120px — ends on a straight line the reader
   sees as a container edge. The phone also lifts and rotates on hover,
   and an overflow box eats the corners it moves into.

   So the three free sides get NEGATIVE insets: the shadow and the
   hover travel paint outside the layout box, and only the intended
   edge is cut. Negative values are valid in inset() for exactly
   this.

   --tiq-cut is the one number. It is the third inset AND the
   negative margin, so the amount painted away and the amount removed
   from layout cannot drift apart — the failure that leaves either a
   gap under the phone or the next block sitting on top of it. */
.tiq-device--clip-b,
.tiq-device--clip-r,
.tiq-device--clip-l { --tiq-cut: 84px; --tiq-air: 140px; }

.tiq-device--clip-b {
    clip-path: inset(calc(-1 * var(--tiq-air)) calc(-1 * var(--tiq-air))
                     var(--tiq-cut) calc(-1 * var(--tiq-air)));
    margin-bottom: calc(-1 * var(--tiq-cut));
}
/* A SIDE clip is different from a bottom one, and getting this wrong
   is what made the first version read as a box.

   A bottom cut lands on a band boundary — a real edge, where the
   background changes anyway. A side cut inside a two-column showcase
   lands on an arbitrary interior line, and a straight edge in the
   middle of a white section is exactly what a container edge looks
   like. So a side-clipped device is not clipped by anything of its
   own: it is pushed out to the PAGE edge and cut by the viewport,
   which body's overflow-x: hidden already does.

   The push is computed rather than guessed. The device's column ends
   at the container's content edge, so the distance to the viewport
   edge is the container's outer gutter plus its inline padding —
   both of which the stylesheet already states, so this stays correct
   at every viewport width instead of being a number tuned against one
   screenshot.

   It is a transform rather than a margin for two reasons: layout does
   not move, so the text column keeps its measure; and the frame is
   right-aligned in its column FIRST, which makes the travel exact.
   Centred-then-translated would land somewhere that depends on the
   column's leftover width. */
.tiq-device--clip-r,
.tiq-device--clip-l {
    --tiq-bleed: calc(
        max(0px, (100vw - var(--content-width, 1200px)) / 2)
        + clamp(20px, 4vw, 48px)
    );
}
.showcase-visual:has(.tiq-device--clip-r) { align-items: flex-end; }
.showcase-visual:has(.tiq-device--clip-l) { align-items: flex-start; }
.tiq-device--clip-r { transform: translateX(calc(var(--tiq-bleed) + var(--tiq-cut))); }
.tiq-device--clip-l { transform: translateX(calc(-1 * (var(--tiq-bleed) + var(--tiq-cut)))); }

/* Above this width the bleed stops, and that is not a fudge factor.
   ────────────────────────────────────────────────────────────────
   The container is capped at --content-width. The wider the viewport,
   the further the page edge is from the content — so running a device
   out to it means dragging the device further and further from the
   column it belongs to. On a 2000px screen the text ends around the
   middle and the phone is pinned to the far right with a lake of
   empty white between them, and barely clipped at that: the travel
   grows faster than the overlap.

   Capping the travel does not help — it lands the device in the
   middle of that gap, floating, with no cut at all. The two states
   are the only good ones: bleed off the edge, or sit in the column.

   So the effect applies while the page edge is CLOSE to the content,
   which is where it reads as the composition it was borrowed from.
   Past that the device stays in its column, whole, with its shadow —
   an ordinary mockup, which is the right answer when there is no edge
   nearby to run off. */
@media (min-width: 1400px) {
    .tiq-device--clip-r,
    .tiq-device--clip-l { transform: none; }
    .showcase-visual:has(.tiq-device--clip-r),
    .showcase-visual:has(.tiq-device--clip-l) { align-items: center; }
}

/* The screenshot inside any screen. --tiq-zoom scales it past the
   frame and --tiq-focus decides which part survives, which is what
   turns a whole-page capture into a readable zoom without a second
   image file. */
/* Unzoomed, the picture sets the height and the frame wraps it — the
   same rule the phone bezel now follows, so ANY capture fits without
   a letterbox or a strip across the top. A laptop and a browser both
   pin their screen to a real 16:10 aspect, so the media fills that
   box instead; only the phone is free.

   Zoomed, there has to be a box to crop against, so the screen takes
   the shot's own aspect and the media overflows it. That keeps zoom
   working without reintroducing a fixed device shape. */
.tiq-device__media {
    width: 100%;
    height: auto;
    display: block;
}
.tiq-laptop__screen .tiq-device__media,
.tiq-browser__screen .tiq-device__media,
.tiq-device--zoomed .tiq-device__media {
    width: calc(100% * var(--tiq-zoom, 1));
    height: calc(100% * var(--tiq-zoom, 1));
    object-fit: cover;
    object-position: var(--tiq-focus, center top);
}
.tiq-device--zoomed .tiq-smartphone__screen { aspect-ratio: var(--tiq-shot, 1 / 2); }
.tiq-device--phone .tiq-smartphone-wrap { padding: 0; }

/* ── A fan of phones ─────────────────────────────────────────────── */
/* Every rule here is per-POSITION, because the count is decided in the
   browser: an unfilled layer is a 1x1 blank that removes itself, and
   nth-child, :has() and the adjacent sibling combinator all
   re-evaluate when it goes. A stack of four with two empties is a
   stack of two, laid out as though it had been authored that way — no
   hole where the third phone was, no height reserved for a phone that
   is not there, and the phones sized for the fan that survived.

   The lift is a MARGIN and not a transform, and that is the whole
   reason the height behaves. align-items: flex-end puts every phone's
   margin edge on the container's bottom, so a bottom margin lifts the
   phone AND is counted in the container's height. A transform would
   paint the fan in the right place and leave the box the height of one
   phone, with the back layers hanging over whatever came next. */
/* The page's own frame width arrives inline as --tiq-stack-w-user and
   is a CEILING, never the value. The caps below are the other half of
   the min(), and they are the ONLY ones a media query can move — an
   inline custom property outranks a media query, so anything written
   into the style attribute is fixed at every viewport. That is not a
   style note: the first version wrote the width and the overlap
   inline, and a four-phone fan ran 437px wide on a 390px screen with
   the narrow-screen rule sitting there being ignored. */
.tiq-device--stack {
    --tiq-cap-1: 320px;
    --tiq-cap-2: 260px;
    --tiq-cap-3: 230px;
    --tiq-cap-4: 200px;
    --tiq-stack-overlap: 0.55;
}
.tiq-phonestack {
    --tiq-stack-w: min(var(--tiq-stack-w-user, 230px), var(--tiq-cap-1));
    --tiq-stack-rise: calc(var(--tiq-stack-w) * 0.19);
    display: flex;
    align-items: flex-end;
    justify-content: center;
    isolation: isolate;
}
/* The more phones survive, the narrower each one is, so a four-phone
   fan and a one-phone fallback both fit the same column. The page's
   own frame width is a CEILING rather than the value — a section that
   asked for a 300px phone gets one when its extra layers are empty,
   and a narrower fan when they are not. */
.tiq-phonestack:has(> .tiq-device--phone:nth-child(2)) { --tiq-stack-w: min(var(--tiq-stack-w-user, 230px), var(--tiq-cap-2)); }
.tiq-phonestack:has(> .tiq-device--phone:nth-child(3)) { --tiq-stack-w: min(var(--tiq-stack-w-user, 230px), var(--tiq-cap-3)); }
.tiq-phonestack:has(> .tiq-device--phone:nth-child(4)) { --tiq-stack-w: min(var(--tiq-stack-w-user, 230px), var(--tiq-cap-4)); }

.tiq-phonestack > .tiq-device--phone {
    flex: 0 0 auto;
    width: var(--tiq-stack-w);
}
.tiq-phonestack .tiq-smartphone-wrap { --tiq-phone-w: var(--tiq-stack-w); padding: 0; }

/* Stops matching the moment the layer before it is removed, so the
   first SURVIVING phone is always flush rather than pulled left into
   the gap its predecessor left. */
.tiq-phonestack > .tiq-device--phone + .tiq-device--phone {
    margin-left: calc(-1 * var(--tiq-stack-w) * var(--tiq-stack-overlap));
}
/* Layer one lowest and in front; each one behind it steps up. Written
   out rather than computed because there is no counter to compute
   from — four positions is the whole range, and MAX_STACK in the
   module above says so too. */
.tiq-phonestack > .tiq-device--phone:nth-child(1) { z-index: 4; margin-bottom: 0; }
.tiq-phonestack > .tiq-device--phone:nth-child(2) { z-index: 3; margin-bottom: var(--tiq-stack-rise); }
.tiq-phonestack > .tiq-device--phone:nth-child(3) { z-index: 2; margin-bottom: calc(2 * var(--tiq-stack-rise)); }
.tiq-phonestack > .tiq-device--phone:nth-child(4) { z-index: 1; margin-bottom: calc(3 * var(--tiq-stack-rise)); }

@media (max-width: 1080px) {
    /* Overlapping harder costs the back screens some width and keeps
       the whole fan in the column, which is the right trade — the back
       layers are there to say "there is more of this", and the front
       one is the one being read. */
    .tiq-device--stack {
        --tiq-cap-2: 230px;
        --tiq-cap-3: 200px;
        --tiq-cap-4: 175px;
        --tiq-stack-overlap: 0.62;
    }
}
@media (max-width: 860px) {
    /* Viewport units below this, because the column IS the viewport
       here rather than half of a capped container — a fixed cap that
       fits a 780px screen is a horizontal scrollbar on a 360px one. */
    .tiq-device--stack {
        --tiq-cap-1: min(230px, 62vw);
        --tiq-cap-2: min(200px, 46vw);
        --tiq-cap-3: min(180px, 40vw);
        --tiq-cap-4: min(160px, 36vw);
        --tiq-stack-overlap: 0.68;
    }
}

/* ── Laptop ──────────────────────────────────────────────────────── */
.tiq-device--laptop { --tiq-laptop-w: 100%; }
.tiq-laptop { width: var(--tiq-laptop-w); max-width: 100%; margin: 0 auto; }
.tiq-laptop__lid {
    position: relative;
    background: linear-gradient(160deg, #3a3a44 0%, #26262e 40%, #1c1c22 100%);
    border-radius: 14px;
    padding: 11px 11px 12px;
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.06),
        0 30px 60px -28px rgba(0, 0, 0, 0.65);
}
.tiq-laptop__screen {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    border-radius: 4px;
    background: #000;
}
/* The base: wider than the lid, with the thumb notch a MacBook has.
   Trapezoid rather than a rectangle, which is the shape that reads as
   "seen slightly from above" and stops it looking like a shelf. */
.tiq-laptop__base {
    position: relative;
    width: 112%;
    left: -6%;
    height: 13px;
    background: linear-gradient(180deg, #33333d 0%, #22222a 55%, #17171d 100%);
    border-radius: 0 0 10px 10px;
    clip-path: polygon(1.6% 0, 98.4% 0, 96.5% 100%, 3.5% 100%);
    box-shadow: 0 14px 26px -14px rgba(0, 0, 0, 0.7);
}
.tiq-laptop__base span {
    position: absolute; left: 50%; top: 0;
    width: 13%; height: 5px;
    transform: translateX(-50%);
    background: #14141a;
    border-radius: 0 0 7px 7px;
}

/* ── Browser ─────────────────────────────────────────────────────── */
.tiq-browser {
    border-radius: 12px;
    overflow: hidden;
    background: #e9e9ec;
    box-shadow:
        0 0 0 1px rgba(26, 26, 24, 0.10),
        0 26px 56px -30px rgba(26, 46, 26, 0.55);
}
.tiq-browser__bar {
    display: flex; align-items: center; gap: 6px;
    padding: 9px 12px;
    background: linear-gradient(180deg, #f4f4f6, #e5e5e9);
    border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}
.tiq-browser__dot {
    width: 9px; height: 9px; border-radius: 50%;
    background: rgba(0, 0, 0, 0.16);
}
.tiq-browser__addr {
    flex: 1; height: 15px; margin-left: 10px;
    border-radius: 8px;
    background: rgba(255, 255, 255, 0.85);
    box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.07);
}
.tiq-browser__screen {
    position: relative;
    aspect-ratio: 16 / 10;
    overflow: hidden;
    background: #fff;
}

@media (max-width: 860px) {
    /* A clipped frame on a narrow screen has already lost most of its
       width to the column; clipping further leaves a sliver. The cut
       is a fixed length, so it is a bigger share of a smaller phone —
       side clipping comes off entirely and the bottom one halves. */
    .tiq-device--clip-r, .tiq-device--clip-l {
        transform: none;
    }
    .showcase-visual:has(.tiq-device--clip-r),
    .showcase-visual:has(.tiq-device--clip-l) { align-items: center; }
    .tiq-device--clip-b { --tiq-cut: 42px; }
    .tiq-laptop__base { height: 10px; }
}

/* pricing.js */

.plan-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
    gap: 24px;
}
.plan-card {
    background: var(--white);
    border: 1px solid rgba(26, 26, 24, 0.08);
    border-radius: var(--radius-lg);
    padding: 28px 24px;
    display: flex;
    flex-direction: column;
}
.plan-card--featured { border-color: var(--accent); }
.plan-name { font-size: var(--text-xl); margin-bottom: 4px; }
.plan-price {
    font-family: var(--font-display);
    font-size: var(--text-2xl);
    line-height: 1;
    letter-spacing: 0.02em;
}
.plan-per { font-family: var(--font-body); font-size: var(--text-small); color: var(--mid); }
.plan-blurb { font-size: var(--text-small); color: var(--mid); margin: 12px 0 20px; }
.plan-cta { text-align: center; }
.plan-cta--text { font-size: var(--text-small); color: var(--mid); padding: 12px 0; }
.plan-limits {
    margin-top: 24px;
    border-top: 1px solid rgba(26, 26, 24, 0.08);
    padding-top: 16px;
    display: flex; flex-direction: column; gap: 8px;
}
.plan-limit { display: flex; justify-content: space-between; gap: 12px; font-size: var(--text-small); }
.plan-limit dt { color: var(--mid); }
.plan-limit dd { font-family: var(--font-mono); }
.plan-note { margin-top: var(--space-s); font-size: var(--text-small); color: var(--mid); max-width: 640px; }

/* ── Common questions ─────────────────────────────────────────────
   A <details> element rather than a scripted accordion: open-able with no
   JavaScript, keyboard-reachable for free, and — the part that
   matters here — the answer text is IN THE DOCUMENT whether or not it
   is expanded, so a crawler reads it and the FAQPage markup describes
   something really present. An accordion that injected answers on
   click would make that markup a claim about content the page does
   not contain. */
.faq-list { max-width: 52rem; }
.faq-item {
    background: var(--white);
    border: 1px solid rgba(26, 26, 24, 0.08);
    border-radius: 10px;
}
.faq-item + .faq-item { margin-top: 10px; }
.faq-item summary {
    cursor: pointer;
    list-style: none;
    padding: 18px 22px;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 16px;
}
.faq-item summary::-webkit-details-marker { display: none; }
.faq-item summary::after {
    content: "+";
    font-family: var(--font-display);
    font-size: 26px;
    line-height: 1;
    color: var(--moss);
    flex: 0 0 auto;
}
.faq-item[open] summary::after { content: "–"; }
.faq-item summary h3 {
    display: inline;
    font-family: var(--font-body);
    font-size: var(--text-med);
    font-weight: 500;
    color: var(--ink);
    margin: 0;
}
.faq-item p {
    margin: 0;
    padding: 0 22px 20px;
    font-size: var(--text-med);
    color: var(--mid);
    line-height: 1.65;
    max-width: 46rem;
}

/* contactUs.js */

.contact-form { display: flex; flex-direction: column; gap: 18px; }
.contact-row { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 640px) { .contact-row { grid-template-columns: 1fr; } }
.contact-field { display: flex; flex-direction: column; gap: 7px; }
.contact-field > span {
    font-family: var(--font-mono);
    font-size: 11px;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--mid);
}
.contact-field input,
.contact-field select,
.contact-field textarea {
    font-family: inherit;
    font-size: var(--text-med);
    color: var(--ink);
    background: var(--white);
    border: 1px solid rgba(26, 26, 24, 0.16);
    border-radius: var(--radius, 8px);
    padding: 12px 14px;
    width: 100%;
    transition: border-color 0.18s;
}
.contact-field textarea { resize: vertical; min-height: 150px; line-height: 1.6; }
.contact-field input:focus,
.contact-field select:focus,
.contact-field textarea:focus {
    outline: none;
    border-color: var(--moss);
    box-shadow: 0 0 0 3px rgba(74, 103, 65, 0.14);
}
/* Off-screen rather than display:none. A bot reading the DOM fills
   what it can see in the markup either way, but some skip anything
   the browser would not render at all — and this trap works best when
   it looks like an ordinary field to everything except a person. */
.contact-hp {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}
.contact-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 18px; }
.contact-note { font-size: var(--text-small); color: var(--mid); max-width: 40ch; margin: 0; }
.contact-status {
    margin: 0;
    padding: 14px 16px;
    border-radius: var(--radius, 8px);
    font-size: var(--text-small);
    line-height: 1.55;
}
.contact-status[data-kind="ok"] {
    background: rgba(74, 103, 65, 0.10);
    border: 1px solid rgba(74, 103, 65, 0.30);
    color: var(--forest);
}
.contact-status[data-kind="error"] {
    background: rgba(190, 60, 40, 0.08);
    border: 1px solid rgba(190, 60, 40, 0.28);
    color: #8c2d1c;
}

/* notFound.js */

.notfound { padding-top: var(--space-l); }
.notfound-title {
    font-family: var(--font-display);
    font-size: clamp(46px, 8vw, 92px);
    line-height: 0.95;
    margin: 8px 0 var(--space-xs);
    color: var(--ink);
}
.notfound-lead {
    font-size: var(--text-large);
    color: var(--mid);
    max-width: 44rem;
    margin-bottom: var(--space-s);
}
.notfound-grid {
    list-style: none;
    padding: 0;
    margin: 0 0 var(--space-s);
    display: grid;
    gap: 14px;
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.notfound-card a {
    display: block;
    height: 100%;
    padding: 20px 22px;
    border: 1px solid rgba(26, 46, 26, 0.14);
    border-radius: 10px;
    background: var(--white);
    text-decoration: none;
    transition: border-color 0.18s ease, transform 0.18s ease;
}
.notfound-card a:hover {
    border-color: var(--moss);
    transform: translateY(-2px);
}
.notfound-card-label {
    display: block;
    font-family: var(--font-display);
    font-size: var(--text-xl);
    color: var(--forest);
    letter-spacing: 0.5px;
}
.notfound-card-body {
    display: block;
    margin-top: 4px;
    font-size: var(--text-small);
    color: var(--mid);
    line-height: 1.45;
}
.notfound-foot { font-size: var(--text-med); color: var(--mid); max-width: 44rem; }
.notfound-foot a { color: var(--moss); }
