/* =========================================================================
   COMPONENTS - the rebuilt UI

   Only things that do not belong in inline utilities live here: the scroll
   reveal, long-form prose, and the toast (whose markup is shared with code
   that toggles a class on it). Everything else is Tailwind utilities on the
   tokens, the same way the FlowpyDisk site is built.

   Loaded by base.html only. legacy_base.html keeps style.css.
   ========================================================================= */

/* ---- Scroll reveal ------------------------------------------------------
   Matches the FlowpyDisk behaviour exactly: an element rises into place ONCE
   as it enters, and stays there.

   This replaced an `animation-timeline: view()` version. That one was cheaper
   - no JS at all, and it ran on the compositor - but a scroll-driven animation
   SCRUBS: it is tied to scroll position, not to time, so the section fades
   back out when you scroll up past it and re-fades on the way down. On a
   marketing page people scroll back up constantly, to re-read a price or a
   feature, and having the copy dissolve under them reads as a glitch. A
   reveal should be an entrance, and an entrance happens once.

   Guarded so a page whose JS never arrives is never blank: the hidden state
   is scoped to .reveal-ready, which site.js puts on <html> immediately before
   it starts observing. No script, no class, everything visible - the failure
   mode is "no animation", not "no page". prefers-reduced-motion opts out at
   the same level, so those users also just get the content. */
.reveal { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  /* The hidden state is scoped to :not(.is-visible) rather than being undone
     by a second rule, and this is load-bearing rather than tidiness.

     `.reveal-ready .reveal.is-visible { transform: none }` is three classes,
     (0,3,0). A hover rule like `.roadmap-card:hover` is (0,2,0). So once a
     section had revealed, that settled state OUTRANKED every hover transform
     inside it - the card lift was silently dead on every staggered grid on
     the page, while the border and shadow halves of the same hover still
     applied, which is why it looked like it was working.

     Declaring nothing once revealed leaves hover to win on its own. The
     transition stays on the element unconditionally so both directions still
     animate. */
  .reveal-ready .reveal {
    transition:
      opacity 0.7s var(--ease-out-quint),
      transform 0.7s var(--ease-out-quint);
  }
  .reveal-ready .reveal:not(.is-visible) {
    opacity: 0;
    transform: translateY(16px);
  }

  /* A grid or row whose children arrive in sequence rather than as one slab.
     The container is the single observed element and the children inherit its
     .is-visible, so this costs one IntersectionObserver entry no matter how
     many cards are in it.

     70ms is deliberately short. The gap has to be long enough to read as a
     sequence and short enough that the last card is not still arriving after
     the eye has moved on: six cards land inside 350ms. */
  .reveal-ready .reveal-stagger > * {
    transition:
      opacity 0.6s var(--ease-out-quint),
      transform 0.6s var(--ease-out-quint);
  }
  .reveal-ready .reveal-stagger:not(.is-visible) > * {
    opacity: 0;
    transform: translateY(14px);
  }
  .reveal-stagger > *:nth-child(2) { transition-delay: 0.07s; }
  .reveal-stagger > *:nth-child(3) { transition-delay: 0.14s; }
  .reveal-stagger > *:nth-child(4) { transition-delay: 0.21s; }
  .reveal-stagger > *:nth-child(5) { transition-delay: 0.28s; }
  .reveal-stagger > *:nth-child(6) { transition-delay: 0.35s; }
  .reveal-stagger > *:nth-child(n + 7) { transition-delay: 0.42s; }

  /* The delay is an entrance cost, not a permanent property of the child. If
     it survived, a card that also animates on hover would sit there for a
     fifth of a second before responding. */
  .reveal-stagger.is-visible > * { transition-delay: 0s; }
  .reveal-stagger.is-revealing > *:nth-child(2) { transition-delay: 0.07s; }
  .reveal-stagger.is-revealing > *:nth-child(3) { transition-delay: 0.14s; }
  .reveal-stagger.is-revealing > *:nth-child(4) { transition-delay: 0.21s; }
  .reveal-stagger.is-revealing > *:nth-child(5) { transition-delay: 0.28s; }
  .reveal-stagger.is-revealing > *:nth-child(6) { transition-delay: 0.35s; }
  .reveal-stagger.is-revealing > *:nth-child(n + 7) { transition-delay: 0.42s; }
}

/* ---- Nav island, scrolled state -----------------------------------------
   FlowpyDisk's island goes denser and sits deeper once the page has moved, so
   it reads as lifted off the content rather than painted onto it. Only the
   opacity stops, the border and the shadow change - the gradient and the two
   inset hairlines that make it metal stay exactly as they are.

   Written as plain CSS on one toggled class rather than as the six arbitrary
   Tailwind classes the original swaps. Tailwind's browser build compiles by
   scanning the DOM, so classes that appear only after a scroll would have it
   recompiling mid-scroll to produce rules this file can just state.

   The literals are the ones from tokens.css - ink-750/900/950 at the top,
   middle and bottom of the ramp. They are written out because a gradient stop
   needs the colour AND an alpha, and the alpha is the thing that changes. */
#nav-island.is-scrolled {
  background: linear-gradient(180deg,
    rgb(255 255 255 / 0.94) 0%,
    rgb(252 252 254 / 0.94) 55%,
    rgb(248 248 251 / 0.95) 100%);
  border-color: var(--card-line);
  box-shadow:
    0 16px 44px -14px rgb(16 18 27 / 0.18),
    inset 0 1px 0 rgb(255 255 255 / 0.1),
    inset 0 -1px 0 rgb(16 18 27 / 0.04);
}

/* Reading progress. A 2px rule along the bottom edge of the island, inside
   its radius, so it belongs to the island rather than floating over the page.
   scaleX rather than width: it is transform-only, so it never triggers layout
   on a handler that runs on every scroll frame. */
#nav-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  overflow: hidden;
  border-bottom-left-radius: 16px;
  border-bottom-right-radius: 16px;
  pointer-events: none;
}
#nav-progress span {
  display: block;
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left center;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--brand-600) 55%, transparent),
    var(--brand-500));
}

/* ---- Long-form prose ----------------------------------------------------
   For the legal pages, help centre and blog posts, which are runs of headings
   and paragraphs rather than composed layouts. Hand-written rather than a
   typography plugin: the palette is bespoke and overriding a plugin's defaults
   costs more than this does. */
.prose-inv {
  font-size: 16px;
  line-height: 1.75;
  color: var(--fg-muted);
}
.prose-inv > * + * { margin-top: 1.3em; }

.prose-inv h2,
.prose-inv h3,
.prose-inv h4 {
  font-family: var(--font-display);
  font-weight: 700;
  color: var(--fg);
  letter-spacing: -0.018em;
  line-height: 1.2;
}
.prose-inv h2 { font-size: 24px; margin-top: 2em; }
.prose-inv h3 { font-size: 19px; margin-top: 1.7em; }
.prose-inv h4 { font-size: 16.5px; margin-top: 1.5em; }

/* The top margin is there to separate a heading from the paragraph ABOVE it.
   The first heading on a page has nothing above it, so that margin only pushes
   the page's first line away from its own header. */
.prose-inv > :first-child > :first-child,
.prose-inv > :first-child { margin-top: 0; }

.prose-inv a {
  color: var(--brand-300);
  text-underline-offset: 3px;
  text-decoration-line: underline;
  text-decoration-color: color-mix(in srgb, var(--brand-300) 40%, transparent);
  transition: color 0.2s, text-decoration-color 0.2s;
}
.prose-inv a:hover { color: var(--brand-200); text-decoration-color: currentColor; }

.prose-inv strong { color: var(--fg); font-weight: 600; }
.prose-inv ul, .prose-inv ol { padding-left: 1.4em; }
.prose-inv ul { list-style: disc; }
.prose-inv ol { list-style: decimal; }
.prose-inv li + li { margin-top: 0.5em; }
.prose-inv li::marker { color: var(--fg-faint); }

.prose-inv code {
  font-family: var(--font-mono);
  font-size: 0.875em;
  background: var(--ink-850);
  border: 1px solid var(--ink-700);
  border-radius: 4px;
  padding: 0.1em 0.35em;
}

.prose-inv hr { border: 0; border-top: 1px solid var(--ink-700); margin: 2.5em 0; }

.prose-inv blockquote {
  border-left: 2px solid var(--brand-500);
  padding-left: 1.1em;
  color: var(--fg-subtle);
}

/* Tables. The guides lean on them for "what each option means" references.
   Full column width, so a page of them lines up as one grid rather than
   stepping in and out with each table's content. Guide bodies wrap each table
   in .table-wrap, which scrolls a wide one inside the column on a phone
   instead of pushing the whole page sideways. */
.prose-inv .table-wrap { overflow-x: auto; }
.prose-inv table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14.5px;
  line-height: 1.55;
}
.prose-inv th,
.prose-inv td {
  padding: 10px 16px 10px 0;
  border-bottom: 1px solid var(--ink-700);
  text-align: left;
  vertical-align: top;
}
.prose-inv td:first-child { color: var(--fg-subtle); }
.prose-inv th {
  color: var(--fg);
  font-weight: 600;
  font-size: 13px;
  border-bottom-color: var(--ink-600);
}
.prose-inv td:last-child,
.prose-inv th:last-child { padding-right: 0; }

/* Callouts, written in a body as <div class="callout" markdown="1">. The
   --warn variant is for the few steps that cannot be undone. */
.prose-inv .callout {
  border: 1px solid var(--card-line);
  border-left: 3px solid var(--brand-500);
  border-radius: 10px;
  background: var(--card);
  padding: 14px 18px;
  font-size: 15px;
  line-height: 1.65;
}
.prose-inv .callout > * + * { margin-top: 0.6em; }
.prose-inv .callout--warn { border-left-color: var(--accent-orange); }

/* Screenshot slots, filled from the Guide admin's Screenshots section. */
.prose-inv .guide-shot { margin: 2em 0; }
.prose-inv .guide-shot img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--ink-700);
  border-radius: 12px;
}
.prose-inv .guide-shot figcaption {
  margin-top: 10px;
  font-size: 13px;
  color: var(--fg-faint);
  text-align: center;
}
/* Only ever rendered in a staff preview: what still needs capturing. */
.prose-inv .guide-shot--placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 220px;
  padding: 28px;
  border: 1.5px dashed color-mix(in srgb, var(--brand-500) 55%, transparent);
  border-radius: 12px;
  background: color-mix(in srgb, var(--brand-500) 6%, transparent);
  text-align: center;
}
.guide-shot__tag {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--brand-300);
}
.guide-shot__brief {
  max-width: 46ch;
  font-size: 14px;
  line-height: 1.55;
  color: var(--fg-subtle);
}

/* ---- Toast --------------------------------------------------------------
   Carried over from style.css with the palette re-pointed at the tokens. The
   class contract is unchanged - `.show` is what toggles it - because
   showToast() in includes/contact_form.html sets exactly that. */
.toast-custom {
  position: fixed;
  bottom: 28px;
  left: 50%;
  transform: translateX(-50%) translateY(80px);
  background: var(--ink-850);
  border: 1px solid var(--ink-700);
  border-radius: 12px;
  padding: 16px 20px;
  min-width: 320px;
  max-width: 440px;
  display: flex;
  align-items: flex-start;
  gap: 12px;
  box-shadow: 0 12px 40px rgb(16 18 27 / 0.1);
  z-index: 9999;
  opacity: 0;
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.35s;
  pointer-events: none;
}
.toast-custom.show {
  transform: translateX(-50%) translateY(0);
  opacity: 1;
  pointer-events: all;
}

.toast-icon {
  width: 32px;
  height: 32px;
  border-radius: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
}
.toast-icon.success { background: color-mix(in srgb, var(--accent-green) 14%, transparent); color: var(--accent-green); }
.toast-icon.error   { background: color-mix(in srgb, var(--accent-red) 14%, transparent);   color: var(--accent-red); }

.toast-content { flex: 1; min-width: 0; }
.toast-title   { font-size: 14px; font-weight: 600; color: var(--fg-strong); margin-bottom: 4px; }
.toast-msg     { font-size: 13px; color: var(--fg-faint); line-height: 1.5; }

.toast-close-btn {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--fg-faint);
  padding: 2px;
  line-height: 1;
  font-size: 18px;
  flex-shrink: 0;
  transition: color 0.2s;
  align-self: flex-start;
}
.toast-close-btn:hover { color: var(--fg-strong); }

@media (max-width: 480px) {
  .toast-custom { min-width: calc(100vw - 32px); max-width: calc(100vw - 32px); }
}

/* ---- Forms and buttons --------------------------------------------------
   includes/contact_form.html carries no styles of its own and no Tailwind
   classes; it names .form-box, .form-input and friends, and until now those
   came from style.css. Redefining them here on the tokens means the form is
   themed on the rebuilt pages while the file itself - and the JS that reads
   its ids, toggles .input-error and calls showToast() - stays untouched.

   The class contract is copied exactly, including `.form-error-msg` being
   display:none until `.visible` is added. */
.form-section { padding: 88px 0; }

.form-box {
  background: var(--ink-850);
  border: 1px solid var(--ink-700);
  border-radius: 16px;
  padding: 40px;
  max-width: 540px;
  margin: 0 auto;
  box-shadow: 0 20px 40px rgb(16 18 27 / 0.08);
}
@media (max-width: 540px) { .form-box { padding: 26px 20px; border-radius: 14px; } }

.form-group { margin-bottom: 20px; text-align: left; }

.form-label {
  display: block;
  color: var(--fg);
  font-size: 13px;
  font-weight: 600;
  margin-bottom: 7px;
}

.form-input {
  width: 100%;
  padding: 11px 13px;
  background: var(--ink-800);
  border: 1px solid var(--ink-700);
  border-radius: 9px;
  color: var(--fg);
  font-family: inherit;
  font-size: 14px;
  outline: none;
  transition: border-color 0.2s, background 0.2s;
}
.form-input::placeholder { color: var(--fg-faint); }
.form-input:focus { border-color: var(--brand-500); background: var(--ink-750); }
select.form-input { appearance: none; cursor: pointer; }
textarea.form-input { min-height: 100px; resize: vertical; }

.form-input.input-error { border-color: var(--accent-red); }

.form-error-msg {
  font-size: 12px;
  color: var(--accent-red);
  margin-top: 6px;
  display: none;
  align-items: center;
  gap: 4px;
}
.form-error-msg.visible { display: flex; }

/* The button vocabulary, matching the one used inline across the rebuilt
   pages so a .btn in carried-over markup is not a different button. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 13px 24px;
  border: 1px solid transparent;
  border-radius: 8px;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  transition: background-color 0.2s, border-color 0.2s, color 0.2s;
}
.btn-primary { background: var(--brand-600); color: #fff; }
.btn-primary:hover { background: var(--brand-700); }
.btn-primary:disabled { opacity: 0.6; cursor: not-allowed; }
.btn-outline { border-color: var(--ink-600); color: var(--fg-muted); background: transparent; }
.btn-outline:hover { border-color: var(--ink-500); color: var(--fg); }

/* ---- Blog post body -----------------------------------------------------
   The article is .prose-inv; these are the extras a Markdown-rendered post
   needs on top of it. */
.post-body pre {
  position: relative;
  overflow-x: auto;
  background: var(--ink-950);
  border: 1px solid var(--ink-700);
  border-radius: 10px;
  padding: 16px 18px;
  font-size: 13px;
  line-height: 1.6;
}
.post-body pre code {
  background: none;
  border: 0;
  padding: 0;
  font-size: inherit;
}

/* The copy button is added by script, so it has no Tailwind classes to carry. */
.code-copy {
  position: absolute;
  top: 8px;
  right: 8px;
  padding: 4px 9px;
  border: 1px solid var(--ink-700);
  border-radius: 6px;
  background: var(--ink-850);
  color: var(--fg-faint);
  font-family: var(--font-mono);
  font-size: 11px;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s, color 0.2s, border-color 0.2s;
}
.post-body pre:hover .code-copy,
.code-copy:focus-visible { opacity: 1; }
.code-copy:hover { color: var(--fg); border-color: var(--ink-500); }

.post-body img { border-radius: 10px; border: 1px solid var(--ink-700); }

/* The post's cover, which leads the article body.

   It needs its own rule rather than Tailwind utilities because `.post-body
   img` above is a class plus an element - (0,1,1) - and beats a utility class
   at (0,1,0), so a cover asking for a 16px radius would still render at the
   10px one inline images use. Scoping this under .post-body puts it at
   (0,2,0), above both.

   The aspect ratio is fixed and the fit is cover, so a slightly-off upload is
   cropped rather than allowed to change the article's opening height - and the
   space is reserved before the image decodes. */
.post-body .post-cover {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  border: 1px solid var(--ink-700);
  border-radius: 16px;
  margin: 0 0 40px;
}

/* The fallback, for a post with no uploaded cover. Same box, same spacing, so
   which one a post has does not move the article underneath. */
.post-body .post-cover-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--ink-700);
  border-radius: 16px;
  background: var(--ink-850);
  margin: 0 0 40px;
  text-align: center;
}
.post-body h2, .post-body h3 { scroll-margin-top: 6rem; }

/* ---- Layout primitives carried over from style.css ---------------------
   The merged home page (previously the Vivre page) is built on these class
   names. They used to come from style.css, which the rebuilt base does not
   load, so without them the pricing cards and roadmap stacked into one column
   and every section ran full-bleed. Same structure as the originals, re-read
   against the tokens. */
.container {
  max-width: var(--width-wide);
  margin: 0 auto;
  padding: 0 20px;
}
@media (min-width: 640px) { .container { padding: 0 32px; } }

.section-box {
  padding: 56px 0;
  border-bottom: 1px solid var(--ink-700);
}
.section-box:last-of-type { border-bottom: none; }
@media (min-width: 640px) { .section-box { padding: 72px 0; } }

.product-section {
  padding: 56px 0;
  border-bottom: 1px solid var(--ink-700);
}
.product-section:last-of-type { border-bottom: none; }
@media (min-width: 640px) { .product-section { padding: 72px 0; } }

/* Fluid rather than a fixed 32px: these are section headings on a page whose
   other headings all scale, and a 32px h2 beside a clamped h1 reads as a
   different page. */
.section-title {
  font-family: var(--font-display);
  font-size: clamp(24px, 3.6vw, 34px);
  font-weight: 700;
  letter-spacing: -0.018em;
  line-height: 1.12;
  color: var(--fg);
  margin: 0 0 14px;
  text-wrap: balance;
}
.section-desc {
  font-size: 15.5px;
  line-height: 1.65;
  color: var(--fg-subtle);
  max-width: 620px;
  margin: 0 auto 40px;
  text-wrap: pretty;
}

.hero-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  background: var(--ink-850);
  border: 1px solid var(--ink-700);
  padding: 6px 13px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 500;
  color: var(--fg-subtle);
}

/* One column on a phone, splitting only where there is width for it. The
   originals were desktop-first with a single 768px breakpoint. */
.grid-2, .grid-3, .grid-4 { display: grid; grid-template-columns: 1fr; gap: 20px; }
@media (min-width: 768px) {
  .grid-2 { grid-template-columns: 1fr 1fr; gap: 48px; align-items: center; }
  .grid-3 { grid-template-columns: repeat(3, 1fr); gap: 20px; }
  .grid-4 { grid-template-columns: repeat(4, 1fr); gap: 18px; }
}

.feature-list { list-style: none; margin: 0 0 24px; padding: 0; }
.feature-item {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-bottom: 11px;
  color: var(--fg-muted);
  font-size: 14px;
  line-height: 1.5;
}

/* The pricing feature lists.
   -------------------------------------------------------------------------
   THE BUG THIS FIXES: the clamp below used to apply at every width, while
   the toggle that opens it was only ever shown under 768px - a split that
   came from the original page, where the clamp was mobile-only too. Putting
   an unconditional max-height on the list left DESKTOP showing 6 of 11
   features per plan with no control to reveal the other 5: "10 Webhooks",
   "API Access" and "Priority + Community Support" were unreachable on the
   section the whole page is selling.

   So the clamp and the toggle now live at the same breakpoint. Full list on
   desktop, where there is room for it; clamped with a fade and a working
   toggle on a phone, where eleven rows per card times three cards is a lot
   of scrolling to get past. toggleCardFeatures() in the page flips
   .expanded and relabels the button. */
.pricing-features-toggle {
  display: none;
  width: 100%;
  margin-top: 8px;
  /* 4px gave a 28px-tall control. It is full width, so the only axis that was
     ever hard to hit is this one; 12px takes it to 44px without moving the
     card's other content more than 4px. */
  padding: 12px 0;
  background: none;
  border: 0;
  color: var(--brand-300);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  text-align: center;
  cursor: pointer;
}
.pricing-features-toggle:hover { color: var(--brand-200); }

/* ONLY A LIST THAT OVERFLOWS IS CLAMPED. The clamp, the fade and the toggle
   used to apply to every list below 768px - sized for the old eleven-item
   cards. The catalogue's lists are four and six items, 140px and 210px under a
   248px cap, so each card carried a "Show all features" button that revealed
   nothing, and a fade that dimmed the bottom of a list that did not continue.

   site.js adds .is-clamped after measuring the list's natural height, so with
   JavaScript off the whole list shows and there is no button that cannot work. */
/* About eight rows on a desktop and six on a phone. site.js uses the same
   two numbers to decide whether a list is long enough to clamp. */
.pricing-features { --features-clamp: 356px; }
@media (max-width: 768px) { .pricing-features { --features-clamp: 248px; } }
@media all {
  .pricing-features.is-clamped {
    position: relative;
    max-height: var(--features-clamp);
    overflow: hidden;
    transition: max-height 0.35s var(--ease-out-quint);
  }
  /* The fade says the list continues, so the toggle is not the only cue. */
  .pricing-features.is-clamped:not(.expanded) {
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent 100%);
    mask-image: linear-gradient(to bottom, #000 calc(100% - 28px), transparent 100%);
  }
  /* A large value rather than `none`, which cannot be transitioned. */
  .pricing-features.is-clamped.expanded {
    max-height: 1200px;
    -webkit-mask-image: none;
    mask-image: none;
  }
  .pricing-features.is-clamped + .pricing-features-toggle { display: block; }
}

/* ---- FAQ ----------------------------------------------------------------
   The accordion was the last block on the page still on a flat ink-850 fill
   with a plain border - the look every other surface moved off. Same card
   material, plus two things a list of questions specifically needs:

   - a left edge that lights up on the OPEN row, so when several are expanded
     you can still see which is which while scrolling past them
   - an answer that opens rather than appearing. <details> has no transition
     of its own, so the inner element animates instead of the height. */
.faq-list {
  border: 1px solid var(--card-line);
  border-radius: 16px;
  overflow: hidden;
  background:
    linear-gradient(177deg,
      color-mix(in srgb, var(--card) 100%, #fff 3%) 0%,
      var(--card) 55%,
      color-mix(in srgb, var(--card) 100%, #0b0d14 3%) 100%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.06),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 14px 34px -22px rgb(16 18 27 / 0.16);
}

.faq-item { position: relative; border-top: 1px solid var(--card-line); }
.faq-item:first-child { border-top: 0; }

/* The open marker. Zero-width when closed so it costs no layout. */
.faq-item::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 2px;
  background: linear-gradient(180deg, var(--brand-500), var(--brand-600));
  transform: scaleY(0);
  transform-origin: top;
  transition: transform 0.35s var(--ease-out-quint);
}
.faq-item[open]::before { transform: scaleY(1); }

.faq-q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 20px;
  cursor: pointer;
  list-style: none;
  font-size: 14.5px;
  font-weight: 500;
  color: var(--fg);
  transition: background-color 0.2s ease, color 0.2s ease;
}
.faq-q::-webkit-details-marker { display: none; }
.faq-q:hover { background: rgb(16 18 27 / 0.035); color: var(--fg-strong); }
.faq-item[open] .faq-q { color: var(--fg-strong); }

.faq-chev {
  flex: none;
  color: var(--fg-faint);
  transition: transform 0.3s var(--ease-out-quint), color 0.2s ease;
}
.faq-item[open] .faq-chev { transform: rotate(180deg); color: var(--brand-300); }

.faq-a {
  padding: 0 20px 18px;
  font-size: 14px;
  line-height: 1.65;
  color: var(--fg-subtle);
}
/* Answers are Markdown from the admin: paragraphs, lists, links, bold,
   italic and underline. Tailwind's preflight strips list markers and link
   styling, so they are put back here, scoped to the answer. */
.faq-a > * + * { margin-top: 10px; }
.faq-a ul, .faq-a ol { padding-left: 1.3em; }
.faq-a ul { list-style: disc; }
.faq-a ol { list-style: decimal; }
.faq-a li + li { margin-top: 4px; }
.faq-a li::marker { color: var(--brand-300); }
.faq-a strong { color: var(--fg); font-weight: 600; }
.faq-a a {
  color: var(--brand-300);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color 0.15s;
}
.faq-a a:hover { color: var(--brand-200); }

@media (prefers-reduced-motion: no-preference) {
  .faq-item[open] .faq-a { animation: faq-open 0.35s var(--ease-out-quint) both; }
}
@keyframes faq-open {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* ---- Hero ---------------------------------------------------------------
   Also from style.css. Without these the headline fell back to a browser
   default h1 and the section lost its centring, so the page opened on small
   left-aligned text under a centred badge.

   The original reserved calc(180px + announcement) at the top to clear the
   fixed navbar. The nav is sticky and in normal flow now, so that space would
   be empty page. */
.hero {
  position: relative;
  padding: 48px 0 64px;
  text-align: center;
}
@media (min-width: 640px) { .hero { padding: 64px 0 80px; } }

.hero h1 {
  font-family: var(--font-display);
  font-size: clamp(34px, 6.2vw, 58px);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: -0.03em;
  color: var(--fg);
  margin: 18px 0 0;
  text-wrap: balance;
}

.hero p {
  font-size: clamp(16px, 2.2vw, 19px);
  line-height: 1.6;
  color: var(--fg-subtle);
  max-width: 62ch;
  margin: 18px auto 0;
  text-wrap: pretty;
}

.hero-badges {
  display: flex;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
}

.hero-visual {
  margin: 44px auto 0;
  max-width: 1080px;
  position: relative;
}

/* ---- Cards --------------------------------------------------------------
   Matte charcoal, but not flat. The material is the one the nav island uses:

     - a near-vertical gradient, because a real panel catches more light along
       its top edge than its face
     - an inset hairline at the top: the specular edge
     - an inset hairline at the bottom: the shadowed underside
     - an outer border carrying the silhouette (see --card in tokens.css - the
       fill alone is only 1.08:1 against the page)

   Those two insets are what stop a dark rectangle reading as a hole punched in
   the page. Without them the card is a fill and a border, which is what "bland"
   looks like.

   On hover the card lifts, the border brightens, and the top highlight
   strengthens - light behaving as if the card moved toward it. */
.card-matte,
.roadmap-card,
.pricing-card {
  --card-sheen: 0.055;
  position: relative;
  display: flex;
  flex-direction: column;
  background:
    linear-gradient(177deg,
      color-mix(in srgb, var(--card) 100%, #fff 3%) 0%,
      var(--card) 46%,
      color-mix(in srgb, var(--card) 100%, #0b0d14 3%) 100%);
  border: 1px solid var(--card-line);
  border-radius: 16px;
  padding: 24px;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / var(--card-sheen)),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 1px 2px rgb(16 18 27 / 0.06);
  transition:
    transform 0.32s var(--ease-out-quint),
    border-color 0.32s ease,
    box-shadow 0.32s ease,
    --card-sheen 0.32s ease;
}

.card-matte:hover,
.roadmap-card:hover,
.pricing-card:hover {
  --card-sheen: 0.10;
  transform: translateY(-4px);
  border-color: var(--card-line-hover);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / var(--card-sheen)),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 20px 38px -18px rgb(16 18 27 / 0.15);
}

/* A highlight that follows the pointer across the card, as if the surface
   were catching a light source the cursor is holding.

   It is the difference between a card that responds to being hovered and one
   that responds to WHERE it is hovered, and it is the single cheapest thing
   on this page that reads as expensive. --mx/--my come from site.js; both
   default to the card's centre, so if that listener never runs the hover is
   just a centred glow rather than nothing.

   z-index is load-bearing. ::after is the card's last child in paint order,
   so without these two rules the glow would wash over the heading and body
   text instead of sitting under them. */
.card-matte::after,
.roadmap-card::after,
.pricing-card::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease;
  background: radial-gradient(
    260px circle at var(--mx, 50%) var(--my, 50%),
    color-mix(in srgb, var(--brand-500) 15%, transparent) 0%,
    transparent 62%);
}
.card-matte:hover::after,
.roadmap-card:hover::after,
.pricing-card:hover::after { opacity: 1; }

.card-matte > *,
.roadmap-card > *,
.pricing-card > * { position: relative; z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  .card-matte::after, .roadmap-card::after, .pricing-card::after { display: none; }
}

/* The sheen is animated, which needs the property registered - otherwise a
   custom property jumps rather than transitions. */
@property --card-sheen {
  syntax: '<number>';
  inherits: false;
  initial-value: 0.055;
}

@media (prefers-reduced-motion: reduce) {
  .card-matte, .roadmap-card, .pricing-card { transition: border-color 0.3s ease, box-shadow 0.3s ease; }
  .card-matte:hover, .roadmap-card:hover, .pricing-card:hover { transform: none; }
}

/* The capability cards read as bland for two measurable reasons, not one:
   the type was small against a lot of padding, so each card was mostly empty
   surface; and the icon - the only thing carrying colour - was a 44px chip
   in a neutral grey that barely separated from the card behind it.

   So: the icon tile gets larger and gets the brand, the title steps up, and
   a hairline along the top edge lights up in the brand on hover. Six
   identical tiles will always be a grid; the job is making each one feel
   made rather than filled in. */
.roadmap-card {
  padding: 22px;
  overflow: hidden;
}

/* The accent rule. Sits inside the radius, spans the card, and is invisible
   until the card is hovered - so a still page is not six glowing lines, and
   the one you are pointing at is unmistakable. */
.roadmap-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1;
  height: 2px;
  border-radius: inherit;
  background: linear-gradient(90deg,
    transparent,
    var(--brand-500) 35%,
    var(--brand-300) 50%,
    var(--brand-500) 65%,
    transparent);
  opacity: 0;
  transform: scaleX(0.6);
  transition: opacity 0.35s ease, transform 0.45s var(--ease-out-quint);
}
.roadmap-card:hover::before { opacity: 0.9; transform: scaleX(1); }

/* h3 on the home page's capability grid, which sits directly under the
   section's h2; h4 is kept for any older markup still using it. */
.roadmap-card h3,
.roadmap-card h4 {
  font-family: var(--font-display);
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.018em;
  color: var(--fg-strong);
  margin: 18px 0 0;
}
.roadmap-card p {
  font-size: 14px;
  line-height: 1.6;
  color: var(--fg-subtle);
  margin: 8px 0 0;
}

.roadmap-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  flex: none;
  border-radius: 13px;
  /* Brand-tinted rather than neutral grey. At 14% over the card it is still a
     dark chip - it reads as the brand catching light, not as a purple box. */
  background: linear-gradient(165deg,
    color-mix(in srgb, var(--brand-600) 26%, var(--ink-800)),
    color-mix(in srgb, var(--brand-600) 10%, var(--ink-850)));
  border: 1px solid color-mix(in srgb, var(--brand-500) 32%, var(--card-line));
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.09),
    inset 0 -1px 0 rgb(16 18 27 / 0.04);
  color: var(--brand-200);
  transition:
    transform 0.32s var(--ease-out-quint),
    border-color 0.32s ease,
    box-shadow 0.32s ease;
}
.roadmap-card:hover .roadmap-icon {
  transform: translateY(-2px) scale(1.05);
  border-color: color-mix(in srgb, var(--brand-500) 65%, transparent);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.14),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 8px 20px -10px color-mix(in srgb, var(--brand-600) 80%, transparent);
}
@media (prefers-reduced-motion: reduce) {
  .roadmap-card:hover .roadmap-icon { transform: none; }
  .roadmap-card::before { transition: opacity 0.3s ease; transform: none; }
}

/* ---- Pricing cards ------------------------------------------------------
   The same material as every other card, which is the point: these were the
   last three on the page still carrying a flat `background: var(--card)` and
   a hard drop shadow inline, so the three cards a visitor looks at hardest
   were the three that looked unfinished.

   They differ from .card-matte in one way only - no padding. A pricing card
   is three stacked bands (price / features / actions) with rules between
   them, and those bands have to reach the card's edges, so the padding lives
   on each band instead. overflow:hidden keeps them inside the radius. */
.pricing-card {
  padding: 0;
  overflow: hidden;
  border-radius: 16px;
}

.pricing-card__head { padding: 28px 28px 24px; }
.pricing-card__body {
  flex: 1;
  padding: 20px 28px;
  border-top: 1px solid var(--card-line);
  border-bottom: 1px solid var(--card-line);
  /* A touch darker than the card face, so the feature list reads as an inset
     tray rather than as more card. */
  background: var(--ink-850);
}
.pricing-card__foot { padding: 20px 28px; display: flex; flex-wrap: wrap; gap: 10px; }
.pricing-card__foot > * { flex: 1; }
/* The Free card's "No credit card required" takes a line of its own under
   the button. Side by side, the other cards reserve the same line, empty, so
   every button sits at the same height. */
.pricing-card__foot > .cta-note--card { flex: 0 0 100%; }
@media (min-width: 768px) {
  .pricing-grid:not(.pricing-grid--1):has(.cta-note--card) .pricing-card__foot:not(:has(.cta-note--card))::after {
    content: ""; flex: 0 0 100%; height: 18px;
  }
}

.pricing-card__name {
  font-family: var(--font-display);
  font-size: 21px;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--fg-strong);
  margin: 0 0 8px;
}
.pricing-card__cycle { color: var(--fg-faint); font-size: 14.5px; font-weight: 400; }

.pricing-card__amount {
  font-family: var(--font-display);
  font-size: 42px;
  font-weight: 800;
  letter-spacing: -0.03em;
  line-height: 1;
  color: var(--fg-strong);
}
.pricing-card__per { color: var(--fg-faint); font-size: 15px; }
.pricing-card__was {
  color: var(--fg-faint);
  font-size: 14px;
  font-weight: 500;
  text-decoration: line-through;
}
.pricing-card__note { color: var(--fg-faint); font-size: 13px; margin: 8px 0 0; }
.pricing-card__blurb { color: var(--fg-faint); font-size: 14px; margin: 10px 0 0; }

/* The discount chip and the recommended ribbon share a shape. */
.pricing-card__chip {
  position: absolute;
  top: 18px;
  right: 18px;
  border-radius: 999px;
  padding: 4px 11px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: color-mix(in srgb, var(--accent-green) 16%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-green) 48%, transparent);
  color: var(--accent-green);
}

/* The recommended plan. Its edge is the brand rather than the neutral line,
   and it carries a soft brand halo under it - the only place on the page
   where colour is used to rank two things rather than to decorate one. */
.pricing-card--featured {
  border-color: color-mix(in srgb, var(--brand-500) 70%, transparent);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / var(--card-sheen)),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 18px 40px -22px color-mix(in srgb, var(--brand-600) 55%, transparent),
    0 1px 2px rgb(16 18 27 / 0.06);
}
.pricing-card--featured:hover {
  border-color: color-mix(in srgb, var(--brand-500) 100%, transparent);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / var(--card-sheen)),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 26px 50px -20px color-mix(in srgb, var(--brand-600) 60%, transparent),
    0 20px 38px -18px rgb(16 18 27 / 0.12);
}
.pricing-card--featured .pricing-card__body {
  border-top-color: color-mix(in srgb, var(--brand-600) 30%, transparent);
  border-bottom-color: color-mix(in srgb, var(--brand-600) 30%, transparent);
}


/* Defined here rather than left to Tailwind: the comparison table uses it to
   carry the Yes/No a screen reader needs beside each glyph, and Tailwind's
   browser build compiles after first paint - so on a slow or blocked CDN
   those words would flash, or stay, inside the cells. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border-width: 0;
}

/* ---- Seats / collaboration section --------------------------------------
   The offer in two lines and the same pair of buttons as the hero.
   ------------------------------------------------------------------------ */
.seats-lead {
  max-width: 62ch;
  margin: 22px auto 0;
  text-align: center;
  font-size: 16px;
  line-height: 1.7;
  color: var(--fg-subtle);
  text-wrap: pretty;
}
.seats-accent { color: var(--brand-200); font-weight: 700; }

/* The pitch above the buttons: the offer in display type, then one line on
   what it means. Phones skip it; the heading already carries the point. */
.seats-pitch { margin-top: 22px; text-align: center; }
.seats-pitch__title {
  font-family: var(--font-display);
  font-size: clamp(20px, 2.4vw, 24px);
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--fg);
}
.seats-pitch .seats-lead { margin-top: 8px; }
@media (max-width: 639px) { .seats-pitch { display: none; } }

/* "No credit card required" under a sign-up button. */
.cta-note {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  margin-top: 14px;
  font-size: 13px;
  /* This face's word space is about 2px at this size, which ran the words
     together ("Nocreditcard..."). */
  word-spacing: 0.14em;
  color: var(--fg-faint);
}
.cta-note svg { width: 14px; height: 14px; flex: none; }
.cta-note--card { margin-top: 0; font-size: 12.5px; line-height: 18px; }

/* The chip that closes the capability row. Deliberately not styled like the
   four beside it: brand text on a brand-tinted edge and no leading dot, so
   it reads as "and more" rather than as a fifth capability named "more". */
.hero-pill--more {
  border-color: color-mix(in srgb, var(--brand-500) 40%, var(--card-line));
  background: color-mix(in srgb, var(--brand-600) 12%, var(--card));
  color: var(--brand-200);
  font-weight: 600;
}

.seats-cta { margin-top: 28px; }

/* Start free beside Book a demo. One line at every width: on a phone the two
   share the row equally instead of stacking into two different widths. */
.cta-pair {
  display: flex;
  justify-content: center;
  gap: 12px;
}
@media (max-width: 639px) {
  .cta-pair > a { flex: 1 1 0; min-width: 0; justify-content: center; padding-inline: 12px; }
}

/* The hero's two badges as one pill, kept on a single line. */
.hero-badges {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 6px 14px;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  background: var(--card);
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--fg-subtle);
  white-space: nowrap;
}
.hero-badges__item { display: inline-flex; align-items: center; gap: 7px; }
.hero-badges__sep { width: 1px; height: 12px; background: var(--card-line); }
@media (max-width: 400px) {
  .hero-badges { gap: 9px; padding: 6px 12px; font-size: 10.5px; }
  .hero-badges__item { gap: 5px; }
}

/* ---- Built for: three audience cards ---------------------------------- */
.audience-grid {
  display: grid;
  gap: 14px;
  max-width: 980px;
  margin: 36px auto 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 720px) { .audience-grid { grid-template-columns: repeat(3, 1fr); gap: 18px; } }
.audience-card,
.step {
  padding: 22px;
  border: 1px solid var(--card-line);
  border-radius: 14px;
  background: linear-gradient(177deg, var(--ink-850), var(--card));
  box-shadow: inset 0 1px 0 rgb(255 255 255 / 0.05);
  transition: border-color 0.3s ease, transform 0.3s var(--ease-out-quint);
}
.audience-card:hover { border-color: var(--card-line-hover); transform: translateY(-2px); }
@media (prefers-reduced-motion: reduce) { .audience-card:hover { transform: none; } }
.audience-card__icon,
.step__num {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 10px;
  background: color-mix(in srgb, var(--brand-600) 18%, var(--ink-800));
  border: 1px solid color-mix(in srgb, var(--brand-500) 26%, var(--card-line));
  color: var(--brand-200);
}
.audience-card__icon svg { width: 19px; height: 19px; }
.audience-card__title,
.step__title {
  margin: 14px 0 0;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--fg-strong);
}
.audience-card__text,
.step__text {
  margin: 6px 0 0;
  font-size: 14px;
  line-height: 1.55;
  color: var(--fg-muted);
}

/* ---- How to start: three numbered steps, then the trust facts ---------- */
/* Inside the seats section, under its buttons: a rule and a smaller heading
   so the steps read as the next part of the section, not more buttons. */
.seats-start {
  max-width: 980px;
  margin: 56px auto 0;
  padding-top: 48px;
  border-top: 1px solid var(--card-line);
}
.seats-start__title {
  margin: 0;
  text-align: center;
  font-family: var(--font-display);
  font-size: clamp(20px, 2.4vw, 26px);
  font-weight: 700;
  letter-spacing: -0.02em;
  color: var(--fg-strong);
}
.seats-start .steps { margin-top: 28px; }
@media (max-width: 639px) {
  .seats-start { margin-top: 40px; padding-top: 36px; }
}
.steps {
  display: grid;
  gap: 14px;
  max-width: 980px;
  margin: 36px auto 0;
  padding: 0;
  list-style: none;
}
@media (min-width: 720px) { .steps { grid-template-columns: repeat(3, 1fr); gap: 18px; } }
.step__num {
  font-family: var(--font-display);
  font-size: 15px;
  font-weight: 700;
}

.trust-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 10px 26px;
  max-width: 980px;
  margin: 28px auto 0;
  padding: 16px 20px;
  border: 1px solid var(--card-line);
  border-radius: 14px;
  list-style: none;
  font-size: 14px;
  font-weight: 600;
  color: var(--fg);
}
.trust-row li { display: inline-flex; align-items: center; gap: 8px; }
.trust-row svg { width: 17px; height: 17px; color: var(--brand-300); flex: none; }
@media (max-width: 559px) {
  .trust-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 14px; font-size: 13px; }
}

/* ---- Comparison table ---------------------------------------------------
   Was a single minified line of inline styles: a `style=` attribute repeated
   on all thirty cells, which is why changing the Vivre column's tint meant a
   find-and-replace across 6KB of one line and why a broken value in it went
   unnoticed. Same table, same data, styled once.

   The frame is the card material, so it sits on the page as a surface rather
   than as loose rules, and the Vivre column is tinted end to end - it is the
   column the section is about. */
/* The frame is the visual surface and the positioning context; .cmp-scroll
   inside it is what actually scrolls. They have to be separate elements: an
   absolutely-positioned edge fade inside a scroll container scrolls away with
   the content, which is exactly when you no longer want it. */
.cmp-frame {
  position: relative;
  /* Clips, but does not scroll - .cmp-scroll inside does that. Without it the
     760px table escaped the frame's layout and gave the whole document 318px
     of horizontal overflow on a phone, even though the scroller was correctly
     clipping it visually. It is also what rounds the table's corners. */
  overflow: hidden;
  margin-top: 28px;
  border: 1px solid var(--card-line);
  border-radius: 16px;
  background:
    linear-gradient(177deg,
      color-mix(in srgb, var(--card) 100%, #fff 3%) 0%,
      var(--card) 55%,
      color-mix(in srgb, var(--card) 100%, #0b0d14 3%) 100%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.06),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 14px 34px -22px rgb(16 18 27 / 0.16);
}

.cmp-scroll {
  overflow-x: auto;
  border-radius: inherit;
}

/* The scroll cue. On a phone the table is 760px wide in a ~350px frame, so
   four of the five comparison columns sit off-screen with nothing to say so -
   the same failure as a clamped list with no control to open it. site.js adds
   .is-scrollable while there is more to the right and drops it at the end. */
.cmp-frame::after {
  content: '';
  position: absolute;
  top: 1px;
  right: 1px;
  bottom: 1px;
  width: 64px;
  pointer-events: none;
  border-radius: 0 15px 15px 0;
  background: linear-gradient(to left,
    var(--card) 12%,
    transparent 100%);
  opacity: 0;
  transition: opacity 0.25s ease;
}
.cmp-frame.is-scrollable::after { opacity: 1; }

.cmp {
  width: 100%;
  min-width: 760px;
  border-collapse: collapse;
  font-size: 13.5px;
}

.cmp th,
.cmp td {
  padding: 13px 16px;
  text-align: left;
  vertical-align: middle;
  white-space: nowrap;
}

.cmp thead th {
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--fg-subtle);
  border-bottom: 1px solid var(--card-line);
}

.cmp tbody td { color: var(--fg-faint); border-bottom: 1px solid var(--card-line); }
.cmp tbody tr:last-child td { border-bottom: 0; }

/* The row label, which is the claim being compared. */
.cmp th[scope='row'] {
  color: var(--fg);
  font-weight: 600;
  white-space: normal;
  border-bottom: 1px solid var(--card-line);
}
.cmp tbody tr:last-child th[scope='row'] { border-bottom: 0; }
/* The shared basics, folded into one row with the list underneath. */
.cmp-sub {
  display: block;
  margin-top: 3px;
  font-size: 12.5px;
  font-weight: 500;
  line-height: 1.45;
  color: var(--fg-faint);
}

/* Vivre's column, tinted the full height rather than per cell. */
.cmp .cmp-us {
  background: color-mix(in srgb, var(--brand-600) 10%, transparent);
  color: var(--fg);
  font-weight: 600;
}
.cmp thead .cmp-us {
  background: color-mix(in srgb, var(--brand-600) 16%, transparent);
  color: var(--brand-200);
}

/* Two columns, and the cells are marks rather than sentences: a comparison
   is read by scanning down a column, and prose defeats that. Only the values
   a symbol cannot carry - "99 users", "Plan-gated", "Higher tiers" - stay as
   text, kept to two words so the scan still works.

   Both value columns are centred and equal, so the eye can run straight down
   either one. */
.cmp--duo { min-width: 520px; }
.cmp--duo th[scope='row'] { width: 44%; white-space: normal; }
/* The first header is visible now, so it needs the row-label alignment
   rather than the centring the two value columns get. */
.cmp--duo thead th:first-child { text-align: left; }
.cmp--duo td { width: 28%; text-align: center; }
.cmp--duo thead th.cmp-us,
.cmp--duo thead th:last-child { text-align: center; }

/* On a phone the two-column table MUST fit. At min-width 520 in a ~350px
   frame the entire "typical per-user CRM" column sat off-screen behind a
   scroll - so the section showed Vivre's values and nothing to compare them
   against, which is a feature list, not a comparison. The scroll cue made
   that discoverable; it did not make it right.

   Three columns of short values fit 350px if the label is allowed to wrap
   and the padding comes in, so below 640px the minimum is dropped and the
   table simply fits. */
@media (max-width: 640px) {
  .cmp--duo { min-width: 0; font-size: 12.5px; }
  .cmp--duo th,
  .cmp--duo td { padding: 12px 8px; }
  .cmp--duo th[scope='row'] { width: 44%; padding-left: 12px; }
  .cmp--duo td { width: 28%; white-space: normal; }
  /* .cmp th is nowrap by default, so the long header was forced to fit and
     sat hard against the frame edge. It wraps at these widths instead. */
  .cmp--duo thead th { font-size: 10px; letter-spacing: 0.04em; white-space: normal; }
}

/* A second tier for the narrow phones still in use - 360px and the 320px
   floor. Checked rather than assumed: at the tier above, 360px still pushed
   the compare column off the edge. */
@media (max-width: 400px) {
  .cmp--duo { font-size: 11.5px; }
  .cmp--duo th,
  .cmp--duo td { padding: 11px 5px; }
  .cmp--duo th[scope='row'] { width: 40%; padding-left: 10px; }
  .cmp--duo td { width: 30%; }
  .cmp--duo thead th { font-size: 9px; letter-spacing: 0.02em; }
  .cmp-y, .cmp-n { font-size: 14px; }
  .cmp-inf { font-size: 17px; }
}

/* The Vivre column is the one the section is about, so it reads as a column
   rather than as tinted cells: a brand cap along the top and a hairline down
   each side, drawn on the header and the last row so the band closes. */
.cmp--duo thead .cmp-us {
  border-top: 2px solid var(--brand-500);
  border-left: 1px solid color-mix(in srgb, var(--brand-500) 28%, transparent);
  border-right: 1px solid color-mix(in srgb, var(--brand-500) 28%, transparent);
  color: var(--brand-200);
}
.cmp--duo tbody .cmp-us {
  border-left: 1px solid color-mix(in srgb, var(--brand-500) 28%, transparent);
  border-right: 1px solid color-mix(in srgb, var(--brand-500) 28%, transparent);
}
.cmp--duo tbody tr:last-child .cmp-us {
  border-bottom: 1px solid color-mix(in srgb, var(--brand-500) 28%, transparent);
}

/* Yes in the brand, no in the status red. The green that was here read as a
   third accent on a page that already has two, and it sat oddly beside the
   red - green/red also being the one pairing that colour-blind readers
   cannot separate, which is why both marks carry sr-only text as well. */
.cmp-y,
.cmp-n,
.cmp-inf { font-size: 15px; font-weight: 700; line-height: 1; }
.cmp-y { color: var(--brand-300); }
.cmp-n { color: var(--accent-red); }
/* The infinity sign is a glyph, not a letter: it needs the extra size to
   read at the same weight as a tick beside it. */
.cmp-inf { font-size: 19px; color: var(--brand-300); }


/* Reading a wide table one row at a time is easier with the row under the
   cursor picked out. */
.cmp tbody tr { transition: background-color 0.18s ease; }
.cmp tbody tr:hover { background: rgb(16 18 27 / 0.031); }

/* ---- Hero -------------------------------------------------------------
   The headline was the only thing on the page carrying weight, and
   everything under it - a one-line tagline and two ordinary buttons - fell
   away sharply. That drop is what read as bland: not the headline, but the
   absence of anything holding the eye after it.

   Three things, no new copy:
   1. The accent phrase becomes a gradient rather than one flat tint, so the
      headline has a light source.
   2. The badges get an inset hairline, so they read as objects rather than
      as outlined text.
   3. The whole thing arrives in sequence. Above-the-fold content normally
      should NOT wait on a scroll observer, so this is a plain CSS animation
      on a delay ladder - it runs on load, not on intersection. */

.hero-accent {
  background: linear-gradient(100deg, var(--brand-300) 0%, var(--brand-200) 42%, #8b6bff 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* The gradient is decoration; if background-clip:text is unsupported the
     text would render transparent and vanish, so the paint order here
     matters more than the effect does. */
  -webkit-text-fill-color: transparent;
}
@supports not ((-webkit-background-clip: text) or (background-clip: text)) {
  .hero-accent { color: var(--brand-300); -webkit-text-fill-color: currentColor; }
}

.hero-badge,
.hero-pill {
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.06),
    inset 0 -1px 0 rgb(16 18 27 / 0.04);
}

/* The entrance. Each step is 70ms behind the last, the same interval the
   scroll-reveal stagger uses, so the page opens the way its sections do. */
@media (prefers-reduced-motion: no-preference) {
  .hero-rise {
    animation: hero-rise 0.75s var(--ease-out-quint) both;
  }
  .hero-rise-1 { animation-delay: 0.05s; }
  .hero-rise-2 { animation-delay: 0.12s; }
  .hero-rise-3 { animation-delay: 0.19s; }
  .hero-rise-4 { animation-delay: 0.26s; }
}

@keyframes hero-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* ---- Closing CTA panel --------------------------------------------------
   The page's last section used to be centred text on the bare page with two
   buttons under it - nothing held it, so after eleven bounded sections the
   page simply ran out rather than closing.

   This gives it an edge. Same material as the cards, at a larger radius
   because it is a panel rather than a tile, plus two things no other section
   gets: the hero's grid texture, so the page opens and closes on the same
   note, and a brand wash behind the heading. That wash is the one deliberate
   exception to "no purple glow anywhere" - it is a single closing beat at the
   bottom of the page, not a background treatment, and it is what makes the
   last ask read as the loudest one. */
.cta-panel {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--card-line);
  border-radius: 24px;
  background:
    linear-gradient(177deg,
      color-mix(in srgb, var(--card) 100%, #fff 4%) 0%,
      var(--card) 50%,
      color-mix(in srgb, var(--card) 100%, #0b0d14 3%) 100%);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.07),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 24px 60px -32px rgb(16 18 27 / 0.18);
}

/* The wash. A radial rather than a linear, centred above the heading, so it
   falls off before it reaches the buttons and never sits behind the text it
   would otherwise wash out. */
.cta-panel::before {
  content: '';
  position: absolute;
  inset: -40% 0 auto 0;
  height: 150%;
  pointer-events: none;
  background: radial-gradient(
    56% 46% at 50% 34%,
    color-mix(in srgb, var(--brand-600) 30%, transparent) 0%,
    transparent 68%);
}

/* The grid, masked so it fades out downward into the panel. */
.cta-panel::after {
  content: '';
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgb(16 18 27 / 0.06) 1px, transparent 1px),
    linear-gradient(90deg, rgb(16 18 27 / 0.06) 1px, transparent 1px);
  background-size: 26px 26px;
  -webkit-mask-image: radial-gradient(120% 90% at 50% 0%, #000 12%, transparent 72%);
  mask-image: radial-gradient(120% 90% at 50% 0%, #000 12%, transparent 72%);
}

.cta-panel > * { position: relative; }

/* The closing ask is the largest button on the page, so it is unmistakably
   the one to press. */
.btn-lg { padding: 16px 30px; font-size: 16px; border-radius: 12px; }

/* An arrow that leans into the direction it points. Two pixels, only on
   hover - enough that the button feels like it answered. */
.btn-arrow { transition: transform 0.22s var(--ease-out-quint); }
.btn-solid:hover .btn-arrow,
.btn-primary:hover .btn-arrow,
.btn-ghost:hover .btn-arrow,
.btn-outline:hover .btn-arrow { transform: translateX(3px); }
@media (prefers-reduced-motion: reduce) {
  .btn-arrow { transition: none; }
  .btn-solid:hover .btn-arrow, .btn-primary:hover .btn-arrow,
  .btn-ghost:hover .btn-arrow, .btn-outline:hover .btn-arrow { transform: none; }
}

/* Lift on its own, for cards whose surface is set elsewhere. */
.lift {
  transition: transform 0.25s var(--ease-out-quint), box-shadow 0.25s ease;
}
.lift:hover {
  transform: translateY(-3px);
  box-shadow: 0 18px 36px -18px rgb(16 18 27 / 0.15);
}
@media (prefers-reduced-motion: reduce) {
  .lift:hover { transform: none; }
}

/* ---- Product gallery ----------------------------------------------------
   The rail is a scroll-snap container; these are the bits that cannot be
   expressed as utilities: the expanded state, and hiding the scrollbar
   without losing the scrolling. */
.gallery-slide {
  transition: width 0.45s var(--ease-out-quint), opacity 0.3s ease;
}

/* Expanded: the slide takes the full rail and the peek disappears, so the
   screenshot grows in place instead of opening a modal over the page. */
.gallery-slide--expanded { width: 100%; }

#gallery-rail.is-expanded #gallery-track { scroll-snap-type: none; }

/* A slide that is not centred sits back slightly, which is what makes the rail
   read as a gallery rather than as a row of equal tiles. */
@media (prefers-reduced-motion: no-preference) {
  .gallery-slide .gallery-expand { transition: transform 0.45s var(--ease-out-quint); }
}

/* ---- Buttons ------------------------------------------------------------
   Same material thinking as the cards: a gradient rather than a flat fill, a
   specular hairline along the top edge, a shadow under it. A flat rectangle of
   brand colour is what made these read as unfinished.

   .btn-solid / .btn-ghost are the premium pair; the older .btn-primary /
   .btn-outline still exist for carried-over markup and now inherit the same
   treatment. */
.btn-solid,
.btn-primary {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 13px 24px;
  border: 1px solid color-mix(in srgb, var(--brand-600) 100%, #fff 18%);
  border-radius: 11px;
  background: linear-gradient(175deg,
    color-mix(in srgb, var(--brand-600) 100%, #fff 12%),
    var(--brand-600) 55%,
    color-mix(in srgb, var(--brand-600) 100%, #000 10%));
  color: #fff;
  font-family: inherit;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.2;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.22),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 8px 18px -10px rgb(16 18 27 / 0.16);
  transition: transform 0.22s var(--ease-out-quint), box-shadow 0.22s ease, filter 0.22s ease;
}
.btn-solid:hover,
.btn-primary:hover {
  transform: translateY(-1.5px);
  filter: brightness(1.07);
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.28),
    inset 0 -1px 0 rgb(16 18 27 / 0.04),
    0 14px 26px -12px rgb(16 18 27 / 0.17);
}
.btn-solid:active, .btn-primary:active { transform: translateY(0); }

.btn-ghost,
.btn-outline {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  padding: 13px 24px;
  border: 1px solid var(--card-line-hover);
  border-radius: 11px;
  background: linear-gradient(175deg, var(--ink-800), var(--card));
  color: var(--fg-muted);
  font-family: inherit;
  font-size: 15px;
  font-weight: 500;
  line-height: 1.2;
  cursor: pointer;
  box-shadow:
    inset 0 1px 0 rgb(255 255 255 / 0.05),
    inset 0 -1px 0 rgb(16 18 27 / 0.04);
  transition: transform 0.22s var(--ease-out-quint), border-color 0.22s ease, color 0.22s ease;
}
.btn-ghost:hover,
.btn-outline:hover {
  transform: translateY(-1.5px);
  border-color: var(--ink-500);
  color: var(--fg);
}
.btn-ghost:active, .btn-outline:active { transform: translateY(0); }

/* WhatsApp keeps its own green, on the same material. */
.btn-whatsapp {
  border-color: color-mix(in srgb, #25D366 100%, #000 18%);
  background: linear-gradient(175deg, #2ee578, #25D366 55%, #1eb658);
  color: #06301a;
  font-weight: 700;
}
.btn-whatsapp:hover { filter: brightness(1.05); }

@media (prefers-reduced-motion: reduce) {
  .btn-solid, .btn-primary, .btn-ghost, .btn-outline { transition: filter 0.2s ease, border-color 0.2s ease; }
  .btn-solid:hover, .btn-primary:hover, .btn-ghost:hover, .btn-outline:hover { transform: none; }
}

/* ---- Floating contact strip + back to top -------------------------------
   A STRIP docked flush to the right edge at mid-height, not a bubble and not
   a scatter of circles. At rest it is a 46px column of icons. Hovering
   anywhere on it opens the whole strip at once and every row spells out its
   label; leaving closes it.

   WHY IT GROWS LEFTWARD FROM A DOCKED EDGE. The strip's right edge is the
   screen's right edge and each icon is the right-hand end of its row, so the
   icons do not move by a pixel while the labels arrive. The version before
   this grew each button on its own, which carried the icon about 130px out
   from under the pointer that was resting on it - and near the button's edge
   that is a hover/unhover loop.

   The skin is the nav island's scrolled state, literal for literal: the same
   gradient ramp (ink-750/900/950 with alpha), the same 0.12 border, the same
   blur and saturate, the same shadow pair. It is written out rather than
   shared because the island's is authored in Tailwind utilities in the
   template, and a browser-build Tailwind class only exists once it is in the
   DOM. The right-hand border and radius are dropped, because that edge is
   off-screen.
   ------------------------------------------------------------------------ */
:root {
  --rail-icon-col: 46px;  /* the resting width: one icon column */
  --rail-row: 46px;       /* row height, which is also the tap target */
  --rail-label-max: 240px;/* cap for the open width; longest label is ~150px */
  --to-top-size: 44px;
  --to-top-edge: 18px;
}

.rail {
  position: fixed;
  top: 50%;
  right: 0;               /* docked: auto width, so it grows LEFTWARD */
  z-index: 60;
  transform: translateY(-50%);
}

.rail-strip {
  overflow: hidden;       /* clips the labels while they are folded away */
  border: 1px solid var(--card-line);
  border-right: 0;
  border-radius: 16px 0 0 16px;
  background: linear-gradient(180deg,
    rgb(255 255 255 / 0.94) 0%,
    rgb(252 252 254 / 0.94) 55%,
    rgb(248 248 251 / 0.95) 100%);
  box-shadow:
    -14px 16px 44px -18px rgb(16 18 27 / 0.18),
    inset 0 1px 0 rgb(255 255 255 / 0.1),
    inset 0 -1px 0 rgb(16 18 27 / 0.04);
  backdrop-filter: blur(24px) saturate(1.5);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

/* Open: the whole strip lifts, not just the row under the pointer. */
.rail:hover .rail-strip,
.rail:focus-within .rail-strip {
  border-color: var(--card-line-hover);
  box-shadow:
    -18px 20px 52px -18px rgb(16 18 27 / 0.19),
    inset 0 1px 0 rgb(255 255 255 / 0.14),
    inset 0 -1px 0 rgb(16 18 27 / 0.04);
}

.rail-list {
  display: flex;
  flex-direction: column;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* A hairline between rows, so four icons in one bar do not read as one tall
   smear. It runs the full width rather than being inset, because the strip
   has no padding to inset from. */
.rail-item + .rail-item {
  border-top: 1px solid var(--card-line);
}

/* The row. justify-content:flex-end pins the icon to the right, which is the
   edge that stays put. */
.rail-btn {
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  height: var(--rail-row);
  color: var(--fg-subtle);
  text-decoration: none;
  transition: background-color 0.22s ease, color 0.22s ease;
}

.rail-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--rail-icon-col);
  flex: none;
  transition: transform 0.3s var(--ease-out-quint);
}

/* max-width rather than display or visibility, so the label stays in the
   accessibility tree at every width and is simply clipped by the strip. */
.rail-label {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  color: var(--fg-strong);
  opacity: 0;
  transition:
    max-width 0.36s var(--ease-out-quint),
    opacity 0.22s ease,
    padding 0.36s var(--ease-out-quint);
}

.rail:hover .rail-label,
.rail:focus-within .rail-label {
  max-width: var(--rail-label-max);
  padding-left: 18px;
  opacity: 1;
}

/* Only the row under the pointer takes its destination's colour, so the open
   strip still says which one you are about to click.

   The brand tints are set per icon key rather than per row, because the rows
   are admin-managed now: whatever someone adds, its colour comes from the
   icon they picked. Anything without a rule of its own falls back to the
   brand tint below, so a new icon key can never render an untinted hover.

   The tints are the destinations' own colours, lightened for legibility on
   this background: every text colour here measures at least 7:1 against the
   strip's fill. The 15% background wash is decoration, not the contrast
   carrier. */
.rail-btn:hover, .rail-btn:focus-visible {
  background: color-mix(in srgb, var(--brand-500) 19%, transparent);
  color: var(--brand-200);
}
.rail-btn:hover .rail-icon { transform: scale(1.1); }
.rail-btn:focus-visible { outline: 2px solid var(--brand-500); outline-offset: -2px; }

.rail-btn--whatsapp:hover, .rail-btn--whatsapp:focus-visible {
  background: color-mix(in srgb, #25D366 15%, transparent);
  color: #128c4b;
}
.rail-btn--google:hover, .rail-btn--google:focus-visible {
  background: color-mix(in srgb, #fbbc05 15%, transparent);
  color: #9a6b00;
}
.rail-btn--linkedin:hover, .rail-btn--linkedin:focus-visible {
  background: color-mix(in srgb, #0a66c2 22%, transparent);
  color: #0a66c2;
}
.rail-btn--instagram:hover, .rail-btn--instagram:focus-visible {
  background: color-mix(in srgb, #e1306c 18%, transparent);
  color: #c13584;
}
.rail-btn--youtube:hover, .rail-btn--youtube:focus-visible {
  background: color-mix(in srgb, #ff0033 16%, transparent);
  color: #d00028;
}
.rail-btn--x:hover, .rail-btn--x:focus-visible {
  background: rgb(16 18 27 / 0.14);
  color: var(--fg-strong);
}
.rail-btn--phone:hover, .rail-btn--phone:focus-visible,
.rail-btn--chat:hover, .rail-btn--chat:focus-visible {
  background: color-mix(in srgb, var(--accent-cyan) 15%, transparent);
  color: #0b7fa0;
}

.rail-btn--award:hover, .rail-btn--award:focus-visible {
  background: color-mix(in srgb, #ff6154 15%, transparent);
  color: #cc4b37;
}

/* A badge row shows an image (e.g. Product Hunt's "Featured" badge) where the
   other rows spell out a label. Taller than a text label, so its row is given
   the height; the image keeps its own proportions. */
.rail-label--badge { line-height: 0; }
/* aspect-ratio reserves the width before the image arrives. The badge is
   loaded from Product Hunt, so without it the row is 0px wide until the
   response lands and every row below it jumps. */
.rail-label--badge img { display: block; height: 40px; width: auto; max-width: none;
                         aspect-ratio: 250 / 54; }
.rail:hover .rail-label--badge,
.rail:focus-within .rail-label--badge { padding-left: 10px; }
.rail-btn:has(.rail-label--badge) { height: calc(var(--rail-row) + 6px); }

/* One entrance for one object: it slides in from the edge it is docked to.
   Deliberately NOT per item with animation-fill-mode:both - a finished
   animation's fill outranks a plain declaration, and the previous version's
   per-item fill was what kept its collapsed state painted on screen. */
@media (prefers-reduced-motion: no-preference) {
  .rail-strip { animation: rail-in 0.5s var(--ease-out-quint) 0.35s both; }
}
@keyframes rail-in {
  from { opacity: 0; transform: translateX(100%); }
  to   { opacity: 1; transform: none; }
}

/* No hover means the strip can never open, so it stays an icon column and a
   tap follows the link straight through. Without this the labels would sit
   folded away and the first tap would only be a hover emulation. */
@media (hover: none) {
  .rail:hover .rail-label,
  .rail:focus-within .rail-label { max-width: 0; padding-left: 0; opacity: 0; }
}

/* ---- Back to top ----
   Unchanged in behaviour and position: it is navigation rather than contact,
   and it sits in the corner that control always sits in. */
.to-top {
  position: fixed;
  right: var(--to-top-edge);
  bottom: calc(18px + env(safe-area-inset-bottom, 0px));
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--to-top-size);
  height: var(--to-top-size);
  padding: 0;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  background: linear-gradient(180deg,
    rgb(255 255 255 / 0.94) 0%,
    rgb(252 252 254 / 0.94) 55%,
    rgb(248 248 251 / 0.95) 100%);
  box-shadow:
    0 16px 44px -14px rgb(16 18 27 / 0.18),
    inset 0 1px 0 rgb(255 255 255 / 0.1);
  backdrop-filter: blur(24px) saturate(1.5);
  -webkit-backdrop-filter: blur(24px) saturate(1.5);
  color: var(--fg-subtle);
  cursor: pointer;
  transition: transform 0.28s var(--ease-out-quint), color 0.25s ease,
              border-color 0.25s ease;
}
.to-top[hidden] { display: none; }
.to-top:hover, .to-top:focus-visible {
  color: var(--fg-strong);
  border-color: var(--card-line-hover);
  transform: translateY(-3px);
}

/* The ring is the page's scroll position. stroke-dashoffset only, so it never
   triggers layout on a handler that runs while scrolling. */
.to-top-ring {
  position: absolute;
  inset: -1px;
  width: var(--to-top-size);
  height: var(--to-top-size);
  transform: rotate(-90deg);
  pointer-events: none;
}
.to-top-ring circle { fill: none; stroke-width: 2; }
.to-top-track { stroke: color-mix(in srgb, var(--fg-faint) 22%, transparent); }
.to-top-fill {
  stroke: var(--brand-500);
  stroke-linecap: round;
  stroke-dasharray: 125.6;          /* 2πr at r=20 */
  stroke-dashoffset: 125.6;
  transition: stroke-dashoffset 0.1s linear;
}

.to-top-arrow { position: relative; width: 19px; height: 19px; }

@media (prefers-reduced-motion: reduce) {
  .rail-strip { animation: none; }
  .rail-label { transition: opacity 0.15s ease; }
  .rail-btn:hover .rail-icon { transform: none; }
  .to-top:hover { transform: none; }
  .to-top-fill { transition: none; }
}

/* ---- Phones: no strip ---------------------------------------------------
   A 46px column pinned to the middle of a 390px screen is a tenth of the
   width, permanently, over whatever is being read - and with no hover it
   could never open into the thing it is for. Contact is not lost: the
   WhatsApp CTA panel is in the page content on every template.
   ------------------------------------------------------------------------ */
@media (max-width: 767px) {
  .rail { display: none; }
}

/* ---- Pricing toggles ----------------------------------------------------
   Two segmented controls: billing period and currency. The thumb is the
   pressed option's own background rather than the whole control changing
   colour, so it reads as a switch with a position rather than as two buttons
   one of which happens to be filled.

   40px tall inside a 2px track, so the control clears 44px and a thumb can hit
   either half. These are the one control on the page that changes what every
   price says; a 30px target for that is a coin toss.
   ------------------------------------------------------------------------ */
.price-switch {
  display: inline-flex;
  padding: 2px;
  border: 1px solid var(--ink-700);
  border-radius: 10px;
  background: color-mix(in srgb, var(--card) 70%, transparent);
}

.price-switch__opt {
  min-height: 40px;
  padding: 0 16px;
  border: 0;
  border-radius: 8px;
  background: none;
  color: var(--fg-subtle);
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
  transition: background-color 0.16s ease, color 0.16s ease;
}
.price-switch__opt:hover { color: var(--fg); }
.price-switch__opt[aria-pressed="true"] {
  background: var(--ink-700);
  color: var(--fg);
  box-shadow: 0 1px 2px rgb(16 18 27 / 0.08);
}
.price-switch__opt:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 1px; }

/* "coming soon" and the like, beside a feature that is real but not yet on.
   Muted and boxed so it reads as a status rather than as part of the name. */
/* A status beside a feature that is announced rather than shipped.

   CENTRED BY ITS INK, not by its box. The text is all capitals, which have no
   descenders, so a pill sized to the line box left the empty descender band
   below the letters and they sat visibly high beside "AI". line-height:1 in a
   fixed-height inline-flex drops that band, and the half-pixel of top padding
   puts the capitals on the pill's optical centre. */
.feature-note {
  display: inline-flex;
  align-items: center;
  height: 18px;
  margin-left: 8px;
  padding: 1px 8px 0;
  box-sizing: border-box;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  color: var(--fg-faint);
  font-family: var(--font-mono);
  font-size: 10px;
  line-height: 1;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  vertical-align: middle;
}

/* The label, value and badge as one run of text beside the icon. */
.feature-text { min-width: 0; }
.feature-value { color: var(--fg-faint); }
/* Room after the count: the face's 2px word space vanished after a "1". */
.feature-count { margin-right: 0.2em; }

/* A pending row is one line with a badge in it, so the icon and the badge
   centre on that line. Other rows stay top-aligned, so an icon sits on the
   first line of a label that wraps. */
.feature-item--pending { align-items: center; }
.feature-item--pending .feature-text { display: inline-flex; align-items: center; }

/* The blurb under the price, clamped to a constant height. Plan descriptions
   come from the catalogue and are whatever length someone typed; one wrapping
   to two lines while its neighbours take one put the divider below it 20px
   lower than theirs. Two lines for everybody. */
.pricing-card__blurb { min-height: 2.9em; }

/* A feature that is announced rather than shipped. The label stays legible;
   it is the icon and the badge beside it that say "not yet". */
.feature-item--pending { color: var(--fg-subtle); }
/* Not in this plan: dimmed, beside the cross, so it reads as missing at a glance. */
.feature-item--off { color: var(--fg-faint); }

/* A reserved slot with nothing in it. It has to keep its height - that is the
   whole point - but not draw: .pricing-card__was is struck through, so an
   empty one rendered a stray dash under the plan name. */
.pricing-card__was.is-empty,
.pricing-card__note.is-empty { visibility: hidden; }

/* ---- Pricing grid -------------------------------------------------------
   Sized to the number of plans the catalogue returns. One plan is a single
   centred card, two sit side by side at a card width that reads as a pair
   rather than as two halves of the page, three or more fill the row. */
.pricing-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  align-items: stretch;
  margin-inline: auto;
}
.pricing-grid--1 { max-width: 440px; }
.pricing-grid--2 { max-width: 860px; }
@media (min-width: 768px) {
  .pricing-grid--2 { grid-template-columns: repeat(2, 1fr); }
  .pricing-grid:not(.pricing-grid--1):not(.pricing-grid--2) { grid-template-columns: repeat(3, 1fr); }
}

/* ---- Pricing section: header ---------------------------------------------
   An eyebrow, a heading whose second half carries the hero's accent gradient,
   and the three facts a buyer scans for as chips. They used to be one dense
   subtitle sentence with the tax note run in, which nobody reads. */
.pricing-section { position: relative; overflow: hidden; }
.pricing-glow {
  position: absolute;
  left: 50%;
  top: -220px;
  width: 900px;
  height: 520px;
  transform: translateX(-50%);
  background: radial-gradient(closest-side, rgb(108 71 255 / 0.20), transparent 72%);
  pointer-events: none;
}
.pricing-section > .container { position: relative; }

.pricing-head { max-width: 760px; margin: 0 auto 44px; text-align: center; }
.pricing-head__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  padding: 6px 14px;
  border: 1px solid color-mix(in srgb, var(--brand-500) 38%, transparent);
  border-radius: 999px;
  background: color-mix(in srgb, var(--brand-600) 10%, transparent);
  font-family: var(--font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--brand-200);
}
.pricing-head__eyebrow::before {
  content: "";
  width: 6px;
  height: 6px;
  border-radius: 999px;
  background: var(--brand-500);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 25%, transparent);
}
.pricing-head__title {
  margin: 18px 0 0;
  font-family: var(--font-display);
  font-size: clamp(32px, 4.6vw, 50px);
  font-weight: 800;
  line-height: 1.06;
  letter-spacing: -0.03em;
  color: var(--fg-strong);
  text-wrap: balance;
}

.pricing-foot-note {
  margin: 26px 0 0;
  text-align: center;
  font-size: 12.5px;
  color: var(--fg-faint);
}

/* ---- Pricing card: head --------------------------------------------------- */
.pricing-card__title-row { display: flex; align-items: center; gap: 10px; }
.pricing-card__title-row .pricing-card__name { margin: 0; }
.pricing-card__audience { margin: 6px 0 0; font-size: 13.5px; color: var(--fg-faint); }
.pricing-card__facts { margin: 4px 0 0; font-size: 13.5px; font-weight: 600; color: var(--fg-muted); }
.pricing-card__cycle-tag {
  padding: 3px 10px;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
}

.pricing-card__price { margin-top: 18px; }

/* The discount, highlighted: percentage and struck MRP on one line, in the
   saving colour. Always rendered, empty when there is no discount, so every
   card's price lands on the same baseline. */
.pricing-card__deal {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Fixed, not a minimum: the Save pill is ~31px and an empty row reserved
     at less than that left the free card's price 3px higher than its
     neighbours'. */
  height: 32px;
  margin: 0 0 10px;
}
.pricing-card__deal.is-empty { visibility: hidden; }
.pricing-card__save-pct {
  padding: 5px 11px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent-green) 18%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-green) 50%, transparent);
  font-size: 13px;
  font-weight: 800;
  letter-spacing: 0.01em;
  /* 5.98:1 on the pill's own tinted background. #15803d measured 3.93:1
     there, which reads fine to most eyes but fails WCAG AA for 13px text -
     and the discount is the one number on the card worth being legible. */
  color: #126034;
}
.pricing-card__deal .pricing-card__was { font-size: 15px; }

.pricing-card__price-line { display: flex; align-items: baseline; gap: 6px; margin: 0; }
.pricing-card__amount { font-size: 46px; }
.pricing-card__sub { margin: 8px 0 0; font-size: 13.5px; color: var(--fg-subtle); }

/* What the discount is worth, in money. Green, because it is the reason. */
.pricing-card__savings {
  display: grid;
  gap: 7px;
  margin: 16px 0 0;
  padding: 12px 14px;
  list-style: none;
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent-green) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent-green) 22%, transparent);
  /* Two lines tall on every card, filled or not, so the heads match. */
  min-height: 78px;
  align-content: center;
}
.pricing-card__savings.is-empty { visibility: hidden; }
.pricing-card__savings li.is-nudge { color: var(--brand-200); }
.pricing-card__savings li.is-nudge svg { color: var(--brand-300); }
.pricing-card__savings li {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 13.5px;
  font-weight: 600;
  color: #166534;
}
.pricing-card__savings svg { width: 15px; height: 15px; flex: none; color: #15803d; }
/* The Free card's version: reasons to start, not money saved, so the brand
   tint instead of the savings green. After the base rules, which it overrides. */
.pricing-card__savings--free {
  background: color-mix(in srgb, var(--brand-500) 9%, transparent);
  border-color: color-mix(in srgb, var(--brand-500) 26%, transparent);
}
.pricing-card__savings--free li { color: var(--brand-200); }
.pricing-card__savings--free svg { color: var(--brand-300); }

/* ---- Recommended ---------------------------------------------------------
   A ribbon across the top edge, a brand gradient border, lifted slightly out
   of the row on wide screens. The one card on the section ranked above the
   others, and the only one with a filled button. */
.pricing-card__ribbon {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  margin: 0;
  padding: 9px 12px;
  background: linear-gradient(90deg, var(--brand-700), var(--brand-600) 50%, var(--brand-500));
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #fff;
}
.pricing-card__ribbon svg { width: 15px; height: 15px; }

/* Gradient border: the face is painted to the padding box and the gradient
   shows only through the transparent border. The face keeps the same matte
   ramp as every other card. */
.pricing-card--featured {
  border: 1px solid transparent;
  background:
    linear-gradient(177deg,
      color-mix(in srgb, var(--card) 100%, #fff 3%) 0%,
      var(--card) 46%,
      color-mix(in srgb, var(--card) 100%, #0b0d14 3%) 100%) padding-box,
    linear-gradient(160deg, #b9a6ff, var(--brand-600) 45%,
      color-mix(in srgb, var(--brand-600) 30%, var(--card-line))) border-box;
}
.pricing-card--featured .pricing-card__head {
  background: linear-gradient(180deg, color-mix(in srgb, var(--brand-600) 12%, transparent), transparent 85%);
}
/* The ribbon sits ABOVE the row line on wide screens: the card grows upward
   by exactly the ribbon's height, so its title, price and rules land level
   with its neighbours'. Inside the card, the ribbon pushed all of that 37px
   lower than the cards beside it. */
.pricing-card__ribbon { height: var(--ribbon-h, 36px); box-sizing: border-box; }
@media (min-width: 768px) {
  .pricing-grid .pricing-card--featured { margin-top: calc(-1 * var(--ribbon-h, 36px)); }
}
.pricing-head { margin-bottom: 64px; }

/* Stacked in one column there is no neighbour to line up with, so the rows
   reserved for alignment would only be blank space - two of them on the free
   card. Collapse the empty ones on a phone. */
@media (max-width: 767px) {
  .pricing-card__deal.is-empty,
  .pricing-card__savings.is-empty { display: none; }
}

/* Editor notes, [[note: ...]] in a guide body. Staff preview only: a fact that
   still needs confirming, flagged where it sits rather than in a separate list. */
.prose-inv .editor-note {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 10px 14px;
  border: 1.5px dashed color-mix(in srgb, var(--accent-orange) 60%, transparent);
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent-orange) 7%, transparent);
  font-size: 13.5px;
  line-height: 1.55;
  color: var(--fg-subtle);
}
.editor-note__tag {
  font-family: var(--font-mono);
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent-orange);
}

/* ---- Guide outline -------------------------------------------------------
   A guide's modules, in the module sidebar and the phone "Modules" menu. */
.guide-outline__link {
  display: flex;
  gap: 10px;
  padding: 7px 10px;
  border-left: 2px solid transparent;
  border-radius: 0 8px 8px 0;
  font-size: 13.5px;
  line-height: 1.4;
  color: var(--fg-subtle);
  transition: color 0.15s, background-color 0.15s;
}
.guide-outline__link:hover { color: var(--fg); background: color-mix(in srgb, var(--fg) 4%, transparent); }
.guide-outline__link.is-current {
  color: var(--fg);
  font-weight: 600;
  border-left-color: var(--brand-500);
  background: color-mix(in srgb, var(--brand-500) 12%, transparent);
}
.guide-outline__link .guide-outline__num {
  flex: none;
  min-width: 2.1em;
  font-family: var(--font-mono);
  font-size: 11.5px;
  line-height: 1.7;
  color: var(--fg-faint);
}
.guide-outline__draft {
  margin-left: 6px;
  padding: 0 6px;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--fg-faint);
  vertical-align: 1px;
  white-space: nowrap;
}
.guide-toc-mobile summary::-webkit-details-marker { display: none; }
.guide-toc-mobile[open] .guide-toc-mobile__chev { transform: rotate(180deg); }
.guide-back {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 12px 7px 10px;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  background: var(--card);
  font-size: 13px;
  font-weight: 500;
  color: var(--fg-subtle);
  transition: color 0.15s, border-color 0.15s;
}
.guide-back:hover { color: var(--fg); border-color: var(--card-line-hover); }

/* ---- Guides page -------------------------------------------------------- */
.guide-search {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 6px 6px 16px;
  border: 1px solid var(--card-line);
  border-radius: 14px;
  background: var(--card);
  transition: border-color 0.15s, box-shadow 0.15s;
}
.guide-search:focus-within {
  border-color: var(--brand-500);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--brand-500) 22%, transparent);
}
.guide-search input {
  flex: 1;
  min-width: 0;
  padding: 8px 0;
  border: 0;
  background: transparent;
  font-size: 15px;
  color: var(--fg);
  outline: none;
}
.guide-search input::placeholder { color: var(--fg-faint); }
.guide-search__btn {
  flex: none;
  padding: 9px 16px;
  border-radius: 10px;
  background: var(--brand-600);
  font-size: 14px;
  font-weight: 600;
  color: #fff;
  transition: background-color 0.15s;
}
.guide-search__btn:hover { background: var(--brand-700); }

.guide-chip {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  font-size: 13px;
  color: var(--fg-subtle);
  transition: color 0.15s, border-color 0.15s, background-color 0.15s;
}
.guide-chip:hover { color: var(--fg); border-color: var(--card-line-hover); }
.guide-chip.is-active {
  color: var(--fg);
  border-color: color-mix(in srgb, var(--brand-500) 70%, transparent);
  background: color-mix(in srgb, var(--brand-500) 16%, transparent);
}
.guide-chip--sm { padding: 2px 9px; font-size: 12px; }

/* The whole card is one link (the stretched ::after of .guide-card__link),
   while the tag chips and matching-module links inside sit above it with
   z-index and stay separately clickable. */
.guide-card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: 24px 26px;
  border: 1px solid var(--card-line);
  border-radius: 18px;
  background: var(--card);
  transition: border-color 0.15s, transform 0.15s;
}
.guide-card:hover { border-color: var(--card-line-hover); }
.guide-card__link::after { content: ""; position: absolute; inset: 0; border-radius: inherit; }
.guide-card__link:focus-visible { outline: none; }
.guide-card:has(.guide-card__link:focus-visible) { outline: 2px solid var(--brand-500); outline-offset: 2px; }
.guide-card > span:last-child { margin-top: auto; padding-top: 20px; }

/* ---- Data diagram ---------------------------------------------------------
   The relationship diagram in "How your CRM data connects". Inline SVG styled
   from the tokens, so it follows the theme. Two drawings of the same thing:
   a wide one, and a stacked one for phones, where the wide one's labels
   would shrink below reading size. */
.prose-inv .data-diagram {
  margin: 2em 0;
  padding: 22px 20px 16px;
  border: 1px solid var(--card-line);
  border-radius: 14px;
  background: var(--card);
}
.data-diagram svg { display: block; width: 100%; height: auto; }
.data-diagram .dd-tall { display: none; max-width: 340px; margin: 0 auto; }
@media (max-width: 639px) {
  .data-diagram .dd-wide { display: none; }
  .data-diagram .dd-tall { display: block; }
}
.dd-box { fill: var(--ink-850); stroke: var(--card-line-hover); stroke-width: 1; }
.dd-box--brand { fill: color-mix(in srgb, var(--brand-500) 14%, var(--ink-850)); stroke: var(--brand-500); }
.dd-title { fill: var(--fg); font-family: var(--font-display); font-size: 15px; font-weight: 700; }
.dd-sub { fill: var(--fg-faint); font-size: 12px; }
.dd-edge { fill: none; stroke: var(--fg-faint); stroke-width: 1.5; }
.dd-edge--brand { stroke: var(--brand-500); stroke-dasharray: 5 4; }
.dd-head { fill: var(--fg-faint); }
.dd-head--brand { fill: var(--brand-500); }
.dd-label { fill: var(--fg-subtle); font-size: 11.5px; }
.dd-label--brand { fill: var(--brand-300); font-weight: 600; }
.data-diagram figcaption {
  margin-top: 14px;
  font-size: 13px;
  line-height: 1.55;
  color: var(--fg-faint);
  text-align: center;
}

/* ---- Product tour ----------------------------------------------------------
   Capability tabs in a row above a peek rail of screenshots
   (includes/product_tour.html). */
.tour-tabs {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding: 4px 2px 6px;
  scrollbar-width: none;
}
.tour-tabs::-webkit-scrollbar { display: none; }
/* Centred when the row fits, scrollable from its first tab when it doesn't:
   justify-content: center would push the first tabs out of reach instead. */
.tour-tabs > .tour-tab:first-child { margin-left: auto; }
.tour-tabs > .tour-tab:last-child { margin-right: auto; }

.tour-tab {
  position: relative;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 9px 16px 9px 12px;
  overflow: hidden;
  border: 1px solid var(--card-line);
  border-radius: 999px;
  background: var(--card);
  font-size: 14px;
  font-weight: 500;
  white-space: nowrap;
  color: var(--fg-subtle);
  transition: color 0.2s, border-color 0.2s, background-color 0.2s;
}
.tour-tab:hover { color: var(--fg); border-color: var(--card-line-hover); }
.tour-tab:focus-visible { outline: 2px solid var(--brand-500); outline-offset: 2px; }
.tour-tab.is-active {
  color: var(--fg);
  border-color: color-mix(in srgb, var(--brand-500) 70%, transparent);
  background: color-mix(in srgb, var(--brand-500) 14%, var(--card));
}
.tour-tab__icon { display: inline-flex; flex: none; color: var(--fg-faint); transition: color 0.2s; }
.tour-tab__icon svg { width: 16px; height: 16px; }
.tour-tab.is-active .tour-tab__icon { color: var(--brand-300); }

/* Autoplay progress: fills along the active tab over one slide's time, and
   holds still while the tour is paused. Gone once someone takes over. */
.tour-tab__bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  background: var(--brand-500);
  transform: scaleX(0);
  transform-origin: left;
}
.tour.is-running .tour-tab.is-active .tour-tab__bar {
  animation: tour-progress var(--tour-ms, 7000ms) linear forwards;
}
.tour.is-paused .tour-tab.is-active .tour-tab__bar { animation-play-state: paused; }
@keyframes tour-progress { from { transform: scaleX(0); } to { transform: scaleX(1); } }

.tour-desc {
  min-height: 1.6em;
  margin-top: 14px;
  font-size: 15px;
  line-height: 1.6;
  text-align: center;
  color: var(--fg-subtle);
}

.tour-desc__link {
  margin-left: 6px;
  font-weight: 600;
  color: var(--brand-300);
  white-space: nowrap;
  text-underline-offset: 3px;
}
.tour-desc__link:hover { color: var(--brand-200); text-decoration: underline; }

.tour-frame { margin-top: 24px; min-width: 0; }
/* position: relative makes the track its slides' offsetParent, so a slide's
   offsetLeft is measured from the track, which is what product_tour.js
   scrolls by. Without it the offset counts from the page, and a track that
   doesn't start at the page edge makes every jump overshoot. */
.tour-track {
  position: relative;
  display: flex;
  gap: 16px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  padding-bottom: 4px;
}
.tour-track::-webkit-scrollbar { display: none; }
.tour-slide {
  flex: none;
  width: 86%;
  margin: 0;
  scroll-snap-align: center;
  transition: opacity 0.45s, transform 0.45s;
}
.tour-slide picture { display: block; }
.tour-slide img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--card-line);
  border-radius: 14px;
  background: var(--card);
}
@media (min-width: 640px) { .tour-slide { width: 70%; } }
@media (min-width: 1024px) { .tour-slide { width: 62%; } }
/* In the peek rail the neighbours step back, so the active screenshot is
   plainly the one its tab is talking about. */
.tour-slide:not(.is-active) { opacity: 0.55; transform: scale(0.965); }

@media (prefers-reduced-motion: reduce) {
  .tour-slide { transition: none; }
}


/* ---- Hero: a section of its own -------------------------------------------
   It fills the screen under the header, content centred, and ends on the
   same rule as every other section, so the product tour starts as a new
   section instead of running straight on from the buttons. Capped so a very
   tall screen does not open a void around four lines of text. */
.hero-full {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: clamp(560px, calc(100svh - 84px), 880px);
  padding-block: 48px;
  border-bottom: 1px solid var(--ink-700);
}
/* The content box's own top padding predates the full-height hero; with the
   section centring it, equal space above and below is what centres it. */
.hero-full > .relative { padding-top: 0; padding-bottom: 0; }
@media (max-width: 639px) {
  /* 85% on a phone, so the rule and the next section show at the bottom of
     the first screen instead of a screenful of empty grid. */
  .hero-full { min-height: clamp(480px, calc(85svh - 64px), 680px); padding-block: 40px; }
}

/* ---- Header: Vivre CRM beside the Invrsys mark ---------------------------- */
.nav-product-sep {
  width: 1px;
  height: 18px;
  margin-inline: 2px;
  background: rgb(16 18 27 / 0.224);
}
.nav-product {
  font-family: 'Audiowide', var(--font-display);
  font-size: 14px;
  letter-spacing: 0.02em;
  color: var(--brand-200);
  white-space: nowrap;
}
/* A phone bar holds the mark, the product, Start free and the menu button;
   the company word does not fit beside them, so under 480px the IN mark
   stands for Invrsys (its name stays in the link's aria-label). */
@media (max-width: 479px) {
  .nav-company, .nav-product-sep { display: none; }
}


/* ---- Closing panel: Start free on the left, WhatsApp on the right --------
   On the .cta-panel material. Side by side from 900px, the main ask taking
   the larger share; stacked below that, with a rule between the two. */
.closing { display: grid; }
.closing > * { position: relative; z-index: 1; }
.closing__main { padding: 48px 24px 40px; text-align: center; }
.closing__main .closing__cta { justify-content: center; }
.closing__side {
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 32px 24px 40px;
  text-align: center;
  border-top: 1px solid var(--card-line);
}
@media (min-width: 900px) {
  .closing { grid-template-columns: 1.55fr 1fr; align-items: stretch; }
  .closing--solo { grid-template-columns: 1fr; }
  .closing__main { padding: 64px 56px; text-align: left; }
  .closing--solo .closing__main { text-align: center; }
  .closing__main .closing__cta { justify-content: flex-start; }
  .closing--solo .closing__main .closing__cta { justify-content: center; }
  .closing__main .closing__note { justify-content: flex-start; }
  .closing--solo .closing__main .closing__note { justify-content: center; }
  .closing__side {
    align-items: flex-start;
    justify-content: center;
    padding: 48px 44px;
    text-align: left;
    border-top: 0;
    border-left: 1px solid var(--card-line);
  }
}

/* ---- Offer popup ---------------------------------------------------------
   Bottom right, above the back-to-top button so the two never overlap, and
   on the card material like every other surface. Phones get the full width
   less the page gutter. */
.promo {
  position: fixed;
  right: var(--to-top-edge);
  bottom: calc(18px + var(--to-top-size) + 12px + env(safe-area-inset-bottom, 0px));
  z-index: 70;
  width: 300px;
  padding: 18px 18px 16px;
  border: 1px solid var(--card-line);
  border-radius: 16px;
  background: linear-gradient(177deg, var(--ink-850), var(--card));
  box-shadow: 0 24px 50px -20px rgb(16 18 27 / 0.17), inset 0 1px 0 rgb(255 255 255 / 0.06);
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.35s ease, transform 0.35s var(--ease-out-quint);
}
.promo.is-open { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .promo { transition: none; transform: none; } }
@media (max-width: 479px) {
  .promo { left: 16px; right: 16px; width: auto; }
}

.promo__close {
  position: absolute;
  top: 10px;
  right: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--fg-faint);
  cursor: pointer;
}
.promo__close:hover { background: rgb(16 18 27 / 0.084); color: var(--fg); }

.promo__headline {
  margin: 0;
  padding-right: 28px;
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.015em;
  color: var(--fg-strong);
}
.promo__message {
  margin: 6px 0 0;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--fg-muted);
}

.promo__code {
  display: flex;
  margin-top: 14px;
  overflow: hidden;
  border: 1px dashed color-mix(in srgb, var(--brand-500) 55%, var(--card-line));
  border-radius: 10px;
  background: color-mix(in srgb, var(--brand-600) 10%, transparent);
}
.promo__code-text {
  flex: 1;
  padding: 9px 12px;
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--brand-200);
  user-select: all;
}
.promo__copy {
  padding: 0 14px;
  border: 0;
  border-left: 1px solid color-mix(in srgb, var(--brand-500) 35%, var(--card-line));
  background: transparent;
  font: inherit;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--fg-muted);
  cursor: pointer;
}
.promo__copy:hover { color: var(--fg); background: rgb(16 18 27 / 0.056); }
.promo__copy.is-copied { color: var(--success, #4ade80); }

.promo__cta {
  display: flex;
  justify-content: center;
  width: 100%;
  margin-top: 14px;
  padding: 10px 16px;
  font-size: 14px;
}

/* ---------------------------------------------------------------------------
   Tap targets
   ---------------------------------------------------------------------------
   Text links in the nav and footer are the height of their text - about 20px -
   so the thing you have to hit is half the size of a fingertip. Padding would
   fix it by making the header taller and respacing the footer columns.

   This instead lays an invisible 44px-tall box over each link. The text does
   not move, nothing reflows, and the pointer and the finger both get a target
   that matches WCAG's 44px guidance. The nav island is 60px tall with 28px
   between links, so the boxes fit inside it without touching each other.

   Use on inline text links only. Anything that is already a button with its
   own padding should get that padding raised instead.
   ------------------------------------------------------------------------ */
.tap-target { position: relative; }
.tap-target::after {
  content: "";
  position: absolute;
  left: -8px;
  right: -8px;
  top: 50%;
  height: 44px;
  transform: translateY(-50%);
  /* Purely a hit area. It must never paint, and it must not swallow a
     scroll or a text selection that was not aimed at the link. */
  pointer-events: auto;
}
@media (pointer: fine) {
  /* A mouse is precise; keep the overlay just big enough to be forgiving so
     it cannot cover a neighbouring control on a dense desktop layout. */
  .tap-target::after { height: 32px; }
}
