/* =============================================================================
   Bitsa AI marketing home
   =============================================================================
   Hand-written CSS, no framework. Everything is driven by the custom
   properties in :root, so new sections inherit the same scale and palette.
   Add sections below the hero rather than introducing new tokens.

   The palette, the gradient and the button shapes are deliberately identical
   to public/seo/seo.css, which styles the 31 static landing pages. Any change
   here should be mirrored there, or the site stops looking like one product.
   ========================================================================== */

/* -----------------------------------------------------------------------------
   Tokens
-------------------------------------------------------------------------------*/
:root {
  /* Light theme surfaces. Dark-theme values (still used by seo.css and the 31
     static landing pages) are kept alongside for reference:
     bg #040404, panel #0f0f14, panel-alt #16161d,
     border rgb(255 255 255 / 12%), border-strong rgb(255 255 255 / 22%). */
  --bg: #ffffff;
  --panel: #f5f5f7;
  --panel-alt: #eef0f2;
  --border: rgb(0 0 0 / 10%);
  --border-strong: rgb(0 0 0 / 18%);

  /* Text. Dark-theme values: text #f4f4f4, muted #a3a3ad, subtle #6f6f7a. */
  --text: #141416;
  --muted: #55555f;
  --subtle: #86868f;

  /* Brand. The gradient is the signature: it carries buttons, badges and
     highlighted words across the whole site. The flat purple/orange are for
     borders, icons and hover states where a gradient cannot be used. */
  --grad: linear-gradient(104deg, #b15af8 12%, #ff8951 94%);
  --purple: #b15af8;
  --orange: #ff8951;
  --purple-dim: rgb(177 90 248 / 12%);

  /* Type. Archivo is the site face, loaded from Google Fonts in <head>
     using the exact same URL as the landing pages so it is a cache hit.
     Domaine is the display face, self-hosted from fonts/ and used only by the
     main headings; body copy and the landing pages stay on Archivo. */
  --font: 'Archivo', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --font-display: domaine, ui-sans-serif, system-ui, sans-serif,
    'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';

  /* Layout. --shell is the one place the content width is set: the header, the
     hero, every section and the footer all sit inside a .shell, so changing it
     here moves the whole page together. */
  --shell: 1300px;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --header-h: 64px;

  --radius: 0.5rem;

  --ease: cubic-bezier(0.22, 1, 0.36, 1);
}

/* -----------------------------------------------------------------------------
   Fonts
-------------------------------------------------------------------------------*/
/* Only the 400 weight is shipped: the headings that use Domaine are all set at
   400, so bold/medium cuts would be dead bytes. swap keeps the headline
   readable in Archivo while the file downloads. */
@font-face {
  font-family: domaine;
  src: url('fonts/domaine-regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 16px;
  line-height: 1.7;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* The hero art bleeds past the viewport edge; without this the page gains a
     horizontal scrollbar on narrow screens. */
  overflow-x: hidden;
}

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

a {
  color: inherit;
  text-decoration: none;
}

:where(a, button):focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 3px;
  border-radius: 4px;
}

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  padding: 0.75rem 1rem;
  background: var(--purple);
  color: #fff;
  font-weight: 700;
}

.skip-link:focus {
  left: 0;
}

/* Available to a screen reader, absent from the page. Used where decorative
   artwork carries meaning that has to be stated somewhere: clip rather than
   display:none, because a hidden element is skipped by assistive tech too. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Gradient-filled text, the same treatment the landing pages use on headings.
   The background-clip fallback matters: if a browser drops the clip, the text
   would render transparent and vanish, so colour is set first. */
.grad-text {
  color: var(--purple);
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

/* -----------------------------------------------------------------------------
   Buttons: shape, weight and casing mirror .btn-grad / .btn-outline in seo.css
-------------------------------------------------------------------------------*/
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  padding: 0.85rem 1.6rem;
  border: 2px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font);
  font-size: 0.8rem;
  font-weight: 800;
  line-height: 1;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  white-space: nowrap;
  cursor: pointer;
  transition: transform 0.2s ease, border-color 0.2s ease, color 0.2s ease;
}

.btn--grad {
  background: var(--grad);
  color: #fff;
}

.btn--grad:hover {
  transform: translateY(-2px);
}

.btn--outline {
  border-color: var(--border);
  color: var(--text);
}

.btn--outline:hover {
  border-color: var(--purple);
  color: var(--purple);
}

.btn--sm {
  padding: 0.65rem 1.4rem;
}

.btn__arrow {
  transition: transform 0.2s ease;
}

.btn:hover .btn__arrow {
  transform: translateX(3px);
}

/* -----------------------------------------------------------------------------
   Header: matches .site-header in seo.css (64px, blurred near-black, hairline)
-------------------------------------------------------------------------------*/
.header {
  position: sticky;
  top: 0;
  z-index: 40;
  height: var(--header-h);
  display: flex;
  align-items: center;
  background: rgb(255 255 255 / 88%);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border);
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

/* flex:none on the link, or a narrow header shrinks the anchor and the image
   inside squashes to fit it while its height stays pinned at 36px. */
.header__logo {
  flex: none;
  display: inline-flex;
  align-items: center;
}

.header__logo img {
  height: 36px;
  width: auto;
  border-radius: 8px;
}

/* Seven items now, so the gap flexes: 2rem only once the header is wide
   enough for it, tightening to 1.25rem before the nav collapses. */
.nav {
  display: flex;
  align-items: center;
  gap: clamp(1.25rem, 1.9vw, 2rem);
  font-size: 0.925rem;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}

.nav a {
  position: relative;
  padding-block: 0.25rem;
  transition: color 0.18s var(--ease);
}

.nav a:hover {
  color: var(--text);
}

/* The current page reads as brand-coloured with the signature underline, so
   the header says where you are without a second visual language. */
.nav a[aria-current='page'] {
  color: var(--text);
}

.nav a[aria-current='page']::after {
  content: '';
  position: absolute;
  inset: auto 0 -0.35rem;
  height: 2px;
  border-radius: 2px;
  background: var(--grad);
}

.header__actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.header__login {
  font-size: 0.925rem;
  font-weight: 500;
  color: var(--muted);
  white-space: nowrap;
}

.header__login:hover {
  color: var(--text);
}

/* "Talk to AI" call button. Outline so the gradient trial CTA stays the one
   loud element in the bar; the handset icon carries the meaning on a phone
   where the label is dropped. */
.header__call {
  gap: 0.45rem;
}

.header__call:hover {
  border-color: var(--purple);
  color: var(--purple);
}

/* Icon-only variant of the call button: a round target the size of the menu
   toggle. Used where the bar is too tight for a third labelled button - the
   band just above the nav collapse (1081-1240px) and on phones. */
.header__call--compact,
.header__call-icon {
  flex: none;
}

/* The trial CTA carries two labels; the short one is shown only on phones. */
.header__cta-short {
  display: none;
}

@media (min-width: 1081px) and (max-width: 1240px) {
  .header__call {
    width: 40px;
    height: 40px;
    padding: 0;
    border-radius: 50%;
  }

  .header__call-label {
    display: none;
  }
}

/* Mobile menu button, hidden until the nav collapses. */
.nav-toggle {
  display: none;
  width: 40px;
  height: 40px;
  padding: 0;
  align-items: center;
  justify-content: center;
  background: none;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text);
  cursor: pointer;
}

/* -----------------------------------------------------------------------------
   Hero
   -----------------------------------------------------------------------------
   Copy only, centred in a single column. The artwork and the drifting orb layer
   that used to fill the right half were removed: the dashboard showcase
   directly below is now the first thing a visitor sees, and it is a real
   product shot, so a decorative render above it was competing with it rather
   than supporting it.

   That leaves the headline as the LCP element, which is why the Domaine preload
   in <head> matters more here than it did before.
-------------------------------------------------------------------------------*/
/* svh, not vh: on mobile Safari and Chrome vh is the tallest possible viewport,
   so with the address bar showing, 100vh puts the marquee below the fold and
   the hero looks broken until the visitor scrolls. svh is the smallest state,
   which is what is actually on screen at load. The vh line is the fallback for
   browsers that predate the unit.

   The header is sticky, so it takes real space in the flow above this section.
   A flat 100svh would therefore push the hero's own bottom edge, and with it
   the marquee, exactly one header height below the fold. Subtracting it is what
   makes "full screen" mean the screen.

   Four rows: two flexible spacers doing the optical centring, the copy between
   them, and the marquee on its own auto row so it keeps its natural height and
   sits on the bottom edge. Putting the marquee in a 1fr row instead lets a tall
   band overflow a short row, which is the same bug in a different place. */
.hero {
  position: relative;
  display: grid;
  grid-template-rows: 1fr auto 1fr auto;
  min-height: calc(100vh - var(--header-h));
  min-height: calc(100svh - var(--header-h));
  padding-block: 1.5rem 0;
  overflow: hidden;
}

.hero__inner {
  grid-row: 2;
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* Ambient violet wash, the one piece of the old decorative orb layer worth
   keeping, so the hero is not flatly black behind the type. */
.hero__wash {
  position: absolute;
  inset: -30% -10% auto -10%;
  height: 120%;
  background: radial-gradient(closest-side, rgb(177 90 248 / 22%), transparent);
  pointer-events: none;
}

.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  margin: 0 0 1.25rem;
  padding: 0.4rem 0.9rem 0.4rem 0.65rem;
  background: var(--purple-dim);
  border: 1px solid rgb(177 90 248 / 28%);
  border-radius: 999px;
  color: #7c3aed;
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  line-height: 1;
}

.eyebrow__dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 0 0 3px rgb(177 90 248 / 22%);
}

/* Domaine 400, run as large as the viewport allows. The vw term is what makes
   the headline hold the same proportion of the screen at every width instead
   of stranding it in the middle of a desktop monitor; the clamp floor keeps it
   readable on a phone and the ceiling stops it going comical past ~1900px.

   line-height below 1 is deliberate and safe here because the lines are split
   by hand: nothing wraps unexpectedly into a collision. */
.hero__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(3rem, 10.5vw, 9.5rem);
  font-weight: 400;
  line-height: 0.92;
  letter-spacing: -0.035em;
  color: rgb(20 20 22);
}

/* The clip window each line slides up out of. The padding is the reason it is
   not simply overflow:hidden on the line box: Domaine's descenders and the
   gradient fill both extend past the em box, and a tight clip shears them. The
   negative margin takes the added space back out of the layout. */
.hero__line {
  display: block;
  overflow: hidden;
  padding-block: 0.12em;
  margin-block: -0.12em;
}

.hero__line > span {
  display: block;
  will-change: transform;
}

/* Narrower than the headline above it on purpose: at the centred width the
   sub would run to a single very long line and stop reading as a paragraph. */
.hero__sub {
  margin: 1.25rem auto 0;
  max-width: 34rem;
  color: var(--muted);
  font-size: clamp(1rem, 1.15vw, 1.075rem);
  line-height: 1.65;
  text-wrap: pretty;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.75rem;
  margin-top: 2rem;
}

/* --- horizontal scroll track ----------------------------------------------
   Technique from the GSAP homepage's .home-animate__text-track: an over-wide
   strip of oversized type translated on X by a scrubbed ScrollTrigger. Here it
   also loops on its own so the hero is alive before a visitor scrolls at all.

   The band is full-bleed rather than inside .shell, because a marquee that
   stops at the content gutter reads as a component; one that runs off both
   edges reads as the page continuing past the screen. */
.hero__marquee {
  grid-row: 4;
  position: relative;
  z-index: 1;
  width: 100%;
  padding-block: 1.25rem;
  border-top: 1px solid var(--border);
  /* Fades both ends into the page so words enter and leave rather than being
     chopped off at the viewport edge. */
  mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 9%, #000 91%, transparent);
  overflow: hidden;
}

.hero__track {
  display: flex;
  width: max-content;
  will-change: transform;
}

/* The two groups are identical and sit flush, so translating the track by
   exactly -50% is a seamless reset. The gap has to be on the group's inside
   edge as padding, not a gap between the groups, or the seam gains a gutter
   the loop does not account for. */
.hero__group {
  display: flex;
  align-items: center;
  gap: clamp(1.5rem, 3.5vw, 3.25rem);
  padding-right: clamp(1.5rem, 3.5vw, 3.25rem);
  flex: none;
}

.hero__word {
  font-family: var(--font-display);
  font-size: clamp(1.75rem, 4.5vw, 4.25rem);
  line-height: 1;
  letter-spacing: -0.02em;
  white-space: nowrap;
  color: rgb(20 20 22 / 88%);
}

/* Every other word is outlined instead of filled. Alternating the two keeps a
   long strip of large type from turning into a wall, and the outline is what
   makes the filled words read as the emphasis. */
.hero__word--line {
  color: transparent;
  -webkit-text-stroke: 1px rgb(20 20 22 / 38%);
}

.hero__glyph {
  flex: none;
  width: clamp(1rem, 2vw, 1.75rem);
  height: clamp(1rem, 2vw, 1.75rem);
}

/* -----------------------------------------------------------------------------
   Entrance
   -----------------------------------------------------------------------------
   Everything above rests in its FINAL state, so the hero is complete with no
   JavaScript at all. These rules apply the hidden start positions, and they are
   gated on .js-anim, which the inline script in <head> sets and js/hero.js
   removes again if GSAP did not load. Without that gate a failed script would
   leave a permanently blank hero.

   Also gated on no-preference: a visitor who asked for reduced motion never
   gets the hidden state in the first place, so there is nothing to animate back
   and nothing that can strand itself invisible.
-------------------------------------------------------------------------------*/
@media (prefers-reduced-motion: no-preference) {
  /* Opacity, deliberately, not a transform. A CSS transform written as a
     percentage arrives at GSAP already resolved to pixels by getComputedStyle,
     which it then reads as a `y` offset; a later yPercent tween is fighting
     that leftover pixel value and the headline never comes back. Hiding the
     title instead leaves GSAP the sole author of the transform. */
  .js-anim .hero__title {
    opacity: 0;
  }

  .js-anim .eyebrow,
  .js-anim .hero__sub,
  .js-anim .hero__actions,
  .js-anim .hero__marquee {
    opacity: 0;
  }
}

/* -----------------------------------------------------------------------------
   Responsive
-------------------------------------------------------------------------------*/
/* The nav collapses on its own breakpoint, above the 900px one the rest of the
   page uses: seven links plus the logo and the two actions stop fitting at
   roughly 1080px, well before the hero needs to stack. */
@media (max-width: 1080px) {
  .nav,
  .header__login {
    display: none;
  }

  .nav-toggle {
    display: inline-flex;
  }

  /* Open state is toggled by the inline script in index.html. */
  .nav.is-open {
    position: absolute;
    inset: var(--header-h) 0 auto;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0.5rem var(--gutter) 1.25rem;
    background: var(--panel);
    border-bottom: 1px solid var(--border);
  }

  .nav.is-open a {
    padding: 0.85rem 0;
    border-bottom: 1px solid var(--border);
    font-size: 1rem;
  }

  /* The underline is an inline-width cue; stacked full-width rows would draw
     it across the whole sheet, so the open menu marks the page with colour. */
  .nav.is-open a[aria-current='page']::after {
    display: none;
  }

  .nav.is-open a[aria-current='page'] {
    color: #7c3aed;
  }
}

@media (max-width: 900px) {
  .hero__wash {
    inset: -10% -20% auto -20%;
    height: 110%;
  }
}

@media (max-width: 560px) {
  /* Full-bleed CTAs, so the two buttons stack instead of sitting as a pair of
     narrow pills under a headline this large. The hero's own padding is left
     alone: it is a grid row, not a spacer, and overriding it collapses the
     centring. */
  .hero__actions .btn {
    flex: 1 1 100%;
  }

  /* The band gives back vertical space on a phone, where the headline and CTAs
     need every pixel of the one visible screen. */
  .hero__marquee {
    padding-block: 0.9rem;
  }
}

/* Phone header. Three things share ~330px: the logo, the trial button and the
   menu toggle. The logo drops to 28px so it keeps its proportions rather than
   being crushed, the button loses its desktop padding and letterspacing, and
   the gaps tighten so all three sit on one line down to 320px. */
@media (max-width: 480px) {
  .header__inner {
    gap: 0.75rem;
  }

  .header__logo img {
    height: 28px;
  }

  .header__actions {
    gap: 0.5rem;
  }

  .header__actions .btn--sm {
    padding: 0.6rem 0.9rem;
    font-size: 0.7rem;
    letter-spacing: 0.03em;
  }

  /* Icon-only: a 36px round button matching the menu toggle. */
  .header__call {
    width: 36px;
    height: 36px;
    padding: 0;
    border-radius: 50%;
  }

  .header__call-label {
    display: none;
  }

  .header__call-icon {
    width: 16px;
    height: 16px;
  }

  .header__cta-long {
    display: none;
  }

  .header__cta-short {
    display: inline;
  }

  .nav-toggle {
    width: 36px;
    height: 36px;
  }
}

@media (max-width: 360px) {
  .header__inner {
    gap: 0.5rem;
  }

  .header__actions {
    gap: 0.4rem;
  }

  .header__logo img {
    height: 24px;
  }

  .header__actions .btn--sm {
    padding: 0.55rem 0.75rem;
    font-size: 0.66rem;
  }
}

/* =============================================================================
   Below the hero
   =============================================================================
   Everything past this point is the scrolling body of the page. It reuses the
   :root tokens above without adding new ones, so the sections and the hero
   cannot drift apart. Two shapes do all the work: `.section` for vertical
   rhythm and `.grid` for the card rows.
   ========================================================================== */

/* -----------------------------------------------------------------------------
   Section scaffolding
-------------------------------------------------------------------------------*/
.section {
  position: relative;
  padding-block: clamp(3.25rem, 7vh, 5.25rem);
}

/* The header is sticky, so an in-page jump (#pricing, #faq) would otherwise
   land with the heading tucked underneath it. */
.section[id] {
  scroll-margin-top: calc(var(--header-h) + 1.5rem);
}

/* Alternating band. Hairlines rather than a hard colour change, because against a
   near-black page a filled panel the full width reads as a seam. */
.section--alt {
  background: var(--panel);
  border-block: 1px solid var(--border);
}

.section__head {
  max-width: 44rem;
  margin: 0 auto clamp(2rem, 4vw, 3rem);
  text-align: center;
}

.section__eyebrow {
  display: block;
  margin: 0 0 0.75rem;
  color: var(--orange);
  font-size: 0.75rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.15em;
  line-height: 1;
}

/* Same Domaine 400 face and colour as the hero. The size stays on the section
   scale: 96px belongs to the one headline, not to eight of them. */
.section__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.65rem, 3.1vw, 2.35rem);
  font-weight: 400;
  line-height: 1.18;
  letter-spacing: -0.025em;
  color: rgb(20 20 22);
  text-wrap: balance;
}

.section__lead {
  margin: 0.9rem 0 0;
  color: var(--muted);
  font-size: clamp(0.975rem, 1.1vw, 1.05rem);
  text-wrap: pretty;
}

/* auto-fit rather than a fixed column count: the rows below hold 3, 4 and 6
   items and all of them should reflow on the same breakpoints. */
.grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
}

/* Every card is a flex column so anything that must sit on the bottom edge
   (a CTA, a "Explore" link, an attribution) can claim the leftover space with
   margin-top:auto. Grid already stretches cards in a row to a common height,
   so that leftover space is exactly what makes those bottom rows line up
   across cards whose copy runs to different lengths. */
.card {
  display: flex;
  flex-direction: column;
  padding: 1.6rem;
  background: var(--panel-alt);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 2);
  transition: border-color 0.25s ease, transform 0.25s var(--ease);
}

.card:hover {
  border-color: var(--border-strong);
  transform: translateY(-3px);
}

/* On the alternating band the cards would sit panel-on-panel and disappear;
   dropping them to --bg keeps the same amount of contrast, inverted. */
.section--alt .card {
  background: var(--bg);
}

.card__title {
  margin: 0 0 0.5rem;
  font-size: 1.05rem;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

.card__text {
  margin: 0;
  color: var(--muted);
  font-size: 0.925rem;
  line-height: 1.6;
}

/* Icon plate. The artwork in img/icons/ already carries its own glossy black
   squircle and neon rim, so this element only positions it: no fill, no border
   and no radius, or they would double up around the rendered tile. */
/* align-self keeps the plate square: as a flex item it would otherwise stretch
   to the card's full width. flex:none stops it being squashed when the copy
   above it is long. */
.card__icon {
  display: inline-flex;
  align-self: flex-start;
  flex: none;
  width: 56px;
  height: 56px;
  margin-bottom: 1rem;
}

/* The files are 128px so they stay sharp on a 2x display at this 56px box.
   The glow in the artwork bleeds to transparent at the edges, so it blends
   into the card rather than sitting on a visible square. */
.card__icon img {
  display: block;
  width: 100%;
  height: 100%;
  transition: transform 0.35s var(--ease);
}

/* Matches the lift the card itself gets on hover, so the tile reads as part of
   the same surface instead of a sticker pinned to it. */
.card:hover .card__icon img {
  transform: translateY(-2px) scale(1.04);
}

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

/* -----------------------------------------------------------------------------
   Product showcase
   -----------------------------------------------------------------------------
   A browser frame around the real dashboard capture. The screenshot is dark on
   a dark page, so the separation comes from a hairline that catches the brand
   gradient rather than from a filled panel behind it.
-------------------------------------------------------------------------------*/
.showcase {
  overflow: hidden; /* the glow is wider than the shell */
}

/* Two brand-coloured pools sitting behind the frame. One element with two
   radial gradients rather than two elements, so it costs a single paint. */
.showcase__glow {
  position: absolute;
  inset: 18% -10% 0;
  z-index: 0;
  pointer-events: none;
  background:
    radial-gradient(42% 55% at 28% 30%, rgb(177 90 248 / 26%), transparent 70%),
    radial-gradient(38% 50% at 76% 42%, rgb(255 137 81 / 16%), transparent 70%);
  filter: blur(28px);
}

.showcase .shell {
  position: relative;
  z-index: 1;
}

/* The gradient lives on the border box and the frame's own background is
   painted over the padding box, which leaves the gradient showing as a 1px
   edge. A plain border cannot hold a gradient. */
/* The two custom properties are driven from the scroll handler in index.html:
   the frame enters pitched back and a little oversized, then straightens as it
   travels up the viewport. They default to flat, so with scripting off or
   reduced motion on, the frame simply sits square and nothing else is needed.
   The perspective is on the element itself rather than an extra wrapper, which
   is why the rotation reads as depth without a parent to hold it. */
.showcase__frame {
  --tilt: 0deg;
  --zoom: 1;

  position: relative;
  max-width: 1220px;
  margin-inline: auto;
  padding: 1px;
  border-radius: 14px;
  background: linear-gradient(160deg, rgb(177 90 248 / 55%), rgb(255 137 81 / 22%) 45%, var(--border) 75%);
  box-shadow: 0 30px 80px -30px rgb(0 0 0 / 20%), 0 0 60px -20px rgb(177 90 248 / 35%);
  transform: perspective(1600px) rotateX(var(--tilt)) scale(var(--zoom));
  transform-origin: center 40%;
}

/* Fake browser chrome. Sells the screenshot as a real product surface and, more
   practically, gives the top edge something to be, since the capture starts
   flush against the app's own header. */
.showcase__bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.6rem 0.85rem;
  border-radius: 13px 13px 0 0;
  background: var(--panel);
  border-bottom: 1px solid var(--border);
}

.showcase__dots {
  display: flex;
  flex: none;
  gap: 0.375rem;
}

.showcase__dots i {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgb(0 0 0 / 16%);
}

.showcase__url {
  flex: 1;
  min-width: 0;
  padding: 0.2rem 0.7rem;
  border-radius: 999px;
  background: rgb(0 0 0 / 5%);
  color: var(--subtle);
  font-size: 0.75rem;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The capture is 2.1:1 and very tall once it spans the shell, so the lower
   third is clipped and faded out. The visitor gets the metrics row and the
   chart, which is the part worth showing, and the page keeps moving. */
.showcase__shot {
  position: relative;
  border-radius: 0 0 13px 13px;
  overflow: hidden;
  background: var(--bg);
}

.showcase__shot img {
  width: 100%;
  /* Anything past this is legend and whitespace in the capture. */
  max-height: 560px;
  object-fit: cover;
  object-position: top center;
}

/* Dissolves the cut edge instead of ending on a hard line. Purely decorative,
   so it sits in a pseudo-element and never blocks a click. */
.showcase__shot::after {
  content: '';
  position: absolute;
  inset: auto 0 0;
  height: 140px;
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--bg));
}

/* The one claim the screenshot cannot make, because it happens on a phone.
   Overlapping the frame's bottom edge marks it as an annotation rather than
   part of the dashboard UI. */
.showcase__alert {
  position: absolute;
  left: 50%;
  bottom: -1.35rem;
  z-index: 2;
  transform: translateX(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.55rem;
  margin: 0;
  padding: 0.65rem 1.15rem;
  width: max-content;
  max-width: calc(100% - 1.5rem);
  border: 1px solid rgb(177 90 248 / 35%);
  border-radius: 999px;
  background: rgb(15 15 20 / 94%);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
  box-shadow: 0 16px 40px -16px rgb(0 0 0 / 45%), 0 0 24px -6px rgb(177 90 248 / 25%);
  /* This chip keeps its dark background regardless of page theme (see
     background above), so its text stays fixed light rather than following
     --text, which is now dark for the light theme. */
  color: #f4f4f4;
  font-size: clamp(0.74rem, 2.2vw, 0.82rem);
  font-weight: 600;
  text-align: center;
  white-space: nowrap;
  box-sizing: border-box;
}

.showcase__alert-text {
  display: inline-block;
}

.showcase__pulse {
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #34d399;
  box-shadow: 0 0 0 0 rgb(52 211 153 / 70%);
  animation: showcase-pulse 2.4s ease-out infinite;
}

@keyframes showcase-pulse {
  70% {
    box-shadow: 0 0 0 9px rgb(52 211 153 / 0%);
  }
  100% {
    box-shadow: 0 0 0 0 rgb(52 211 153 / 0%);
  }
}

@media (prefers-reduced-motion: reduce) {
  .showcase__pulse {
    animation: none;
  }
}

@media (max-width: 640px) {
  .showcase__alert {
    bottom: -1.2rem;
    padding: 0.55rem 0.95rem;
    gap: 0.5rem;
  }
}

@media (max-width: 480px) {
  .showcase__alert {
    bottom: -1.25rem;
    padding: 0.5rem 0.85rem;
    white-space: normal;
    text-align: center;
    border-radius: 14px;
    line-height: 1.35;
    width: auto;
    max-width: calc(100% - 1.25rem);
  }

  .showcase__alert-text {
    display: inline;
  }

  .showcase__shot img {
    max-height: 300px;
  }
}

/* -----------------------------------------------------------------------------
   Trust / integration strip
   -----------------------------------------------------------------------------
   Text wordmarks, not logo files: the real marks are trademarked, and six
   image requests immediately under the LCP element would compete with it.
-------------------------------------------------------------------------------*/
.strip {
  padding-block: 1.75rem;
  border-block: 1px solid var(--border);
}

.strip__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 1rem clamp(1.5rem, 4vw, 3rem);
  text-align: center;
}

.strip__label {
  margin: 0;
  color: var(--subtle);
  font-size: 0.75rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  line-height: 1.4;
}

.strip__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.75rem clamp(1.25rem, 3vw, 2.25rem);
  margin: 0;
  padding: 0;
  list-style: none;
}

.strip__list li {
  color: var(--muted);
  font-size: 0.975rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  white-space: nowrap;
  transition: color 0.2s ease;
}

.strip__inner:hover .strip__list li {
  color: var(--text);
}

/* -----------------------------------------------------------------------------
   How it works: numbered steps
-------------------------------------------------------------------------------*/
.steps {
  counter-reset: step;
  margin: 0;
  padding: 0;
  list-style: none;
}

.steps > li {
  counter-increment: step;
  position: relative;
  padding: 1.75rem 1.6rem 1.6rem;
  background: var(--panel-alt);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 2);
}

.section--alt .steps > li {
  background: var(--bg);
}

/* The number is generated, so the markup stays a plain ordered list and the
   step order survives with CSS off. */
.steps > li::before {
  content: counter(step);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin-bottom: 0.9rem;
  background: var(--grad);
  border-radius: 50%;
  color: #fff;
  font-size: 0.9rem;
  font-weight: 900;
  line-height: 1;
}

/* Connector between the cards on desktop only, because once the row wraps to a
   single column it would point sideways into empty space. */
@media (min-width: 861px) {
  .steps > li:not(:last-child)::after {
    content: '';
    position: absolute;
    top: 2.65rem;
    right: calc(-1rem - 1px);
    width: 1rem;
    height: 1px;
    background: var(--border-strong);
  }
}

/* -----------------------------------------------------------------------------
   Live demo
   -----------------------------------------------------------------------------
   A scripted preview of the widget, not the widget itself: the real embed
   needs a client id and a network round trip, and this sits high enough on
   the page that it must paint instantly. Swap in the real snippet when a
   public demo bot exists. See the note in index.html.
-------------------------------------------------------------------------------*/
.demo {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1fr);
  align-items: center;
  gap: clamp(1.75rem, 4vw, 3.5rem);
}

.demo__points {
  margin: 1.5rem 0 0;
  padding: 0;
  list-style: none;
}

.demo__points li {
  position: relative;
  margin-bottom: 0.75rem;
  padding-left: 1.85rem;
  color: var(--muted);
  font-size: 0.95rem;
}

/* The mask keeps only the tick's stroke, so what shows through is the brand
   gradient in the shape of a checkmark: one element, no glyph, and it stays
   crisp at any zoom. */
.demo__points li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.45rem;
  width: 18px;
  height: 18px;
  background: var(--grad);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M4 9.4l3 3L14 5.6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 100% no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 18 18'%3E%3Cpath d='M4 9.4l3 3L14 5.6' fill='none' stroke='%23000' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") center / 100% no-repeat;
}

.demo__preview-container {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 100%;
  position: relative;
}

.demo__preview-img,
.demo__preview-iframe {
  width: 100%;
  max-width: 380px;
  height: 550px;
  border-radius: calc(var(--radius) * 2.5);
  box-shadow: 0 24px 60px rgb(0 0 0 / 18%), 0 0 40px rgb(177 90 248 / 15%);
  border: 1px solid var(--border-strong);
  background: var(--panel-alt);
  transition: transform 0.4s var(--ease), box-shadow 0.4s var(--ease);
}

.demo__preview-img {
  height: auto;
  object-fit: contain;
}

.demo__preview-img:hover,
.demo__preview-iframe:hover {
  transform: translateY(-4px);
  box-shadow: 0 32px 70px rgb(0 0 0 / 22%), 0 0 50px rgb(177 90 248 / 25%);
}

.section--alt .chat {
  background: var(--bg);
}

.chat__bar {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.85rem 1.1rem;
  background: var(--grad);
  color: #fff;
}

.chat__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  background: rgb(255 255 255 / 22%);
  border-radius: 50%;
  font-size: 0.8rem;
  font-weight: 900;
  line-height: 1;
}

.chat__name {
  font-size: 0.9rem;
  font-weight: 800;
  line-height: 1.2;
}

.chat__status {
  display: block;
  font-size: 0.7rem;
  font-weight: 600;
  opacity: 0.85;
}

/* Fixed height so the card does not jump as messages are appended. */
.chat__log {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  height: 19rem;
  padding: 1.1rem;
  overflow-y: auto;
  scrollbar-width: thin;
}

.chat__msg {
  max-width: 84%;
  padding: 0.65rem 0.9rem;
  border-radius: calc(var(--radius) * 2);
  font-size: 0.9rem;
  line-height: 1.55;
}

.chat__msg--bot {
  align-self: flex-start;
  background: rgb(0 0 0 / 5%);
  border-bottom-left-radius: 4px;
  color: var(--text);
}

.chat__msg--user {
  align-self: flex-end;
  background: var(--grad);
  border-bottom-right-radius: 4px;
  color: #fff;
  font-weight: 500;
}

.chat__msg--lead {
  align-self: flex-start;
  width: 100%;
  max-width: 100%;
  background: var(--purple-dim);
  border: 1px dashed rgb(177 90 248 / 45%);
  color: #7c3aed;
  font-size: 0.8rem;
  font-weight: 700;
}

/* Typing indicator: three dots matching the hero entrance stagger. */
.chat__typing {
  display: flex;
  align-self: flex-start;
  gap: 4px;
  padding: 0.8rem 0.9rem;
  background: rgb(0 0 0 / 5%);
  border-radius: calc(var(--radius) * 2);
  border-bottom-left-radius: 4px;
}

.chat__typing span {
  width: 6px;
  height: 6px;
  background: var(--muted);
  border-radius: 50%;
  animation: blink 1.2s infinite;
}

.chat__typing span:nth-child(2) { animation-delay: 0.2s; }
.chat__typing span:nth-child(3) { animation-delay: 0.4s; }

@keyframes blink {
  0%, 60%, 100% { opacity: 0.25; }
  30% { opacity: 1; }
}

.chat__prompts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  padding: 0.9rem 1.1rem 1.1rem;
  border-top: 1px solid var(--border);
}

.chat__prompt {
  padding: 0.45rem 0.85rem;
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--muted);
  font-family: var(--font);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.chat__prompt:hover:not(:disabled) {
  border-color: var(--purple);
  color: var(--purple);
}

.chat__prompt:disabled {
  opacity: 0.4;
  cursor: default;
}

/* -----------------------------------------------------------------------------
   Use cases: whole card is the link
-------------------------------------------------------------------------------*/
.usecase:hover {
  border-color: var(--purple);
}

/* auto pushes the link to the bottom of the card, so the "Explore" row sits
   on one line across the whole grid however long each description runs.
   align-self stops the inline-flex link stretching the full card width, which
   would make the whole row a click target instead of just the label. */
.usecase__more {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  gap: 0.35rem;
  margin-top: auto;
  padding-top: 0.9rem;
  color: var(--purple);
  font-size: 0.8rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.usecase:hover .usecase__more svg {
  transform: translateX(3px);
}

.usecase__more svg {
  transition: transform 0.2s ease;
}

/* -----------------------------------------------------------------------------
   Services
   -----------------------------------------------------------------------------
   Six offers on a fixed three-column grid rather than .grid's auto-fit, so
   the rows always break 3 / 3 (and 2 / 2 / 2 on a tablet) instead of leaving
   one card orphaned. The AI card is marked out by a gradient border and a tag
   rather than by size, which is what keeps the grid even. Card anatomy is
   icon, title, text, a checked list of what is included, then a technology
   row pinned to the bottom edge so the chips line up across the row however
   long each list runs.
-------------------------------------------------------------------------------*/
.services {
  display: grid;
  gap: 1rem;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.service {
  position: relative;
  overflow: hidden;
}

/* The featured card carries the brand gradient as a hairline border. A padding
   box with the gradient underneath is the only way to draw a gradient border
   on a rounded corner without an extra wrapper element. */
.service--featured {
  border: 1px solid transparent;
  background:
    linear-gradient(var(--panel-alt), var(--panel-alt)) padding-box,
    var(--grad) border-box;
}

.service--featured:hover {
  border-color: transparent;
}

/* Soft brand wash in the top-right corner so the wide card does not read as a
   large empty slab next to five dense ones. */
.service--featured::before {
  content: '';
  position: absolute;
  inset: -40% -20% auto auto;
  width: 60%;
  aspect-ratio: 1;
  background: radial-gradient(closest-side, rgb(177 90 248 / 16%), rgb(255 137 81 / 8%) 55%, transparent);
  pointer-events: none;
}

.service > * {
  position: relative;
}

/* Glossy black squircle, the same family as the rendered tiles in img/icons/:
   a near-black plate, a faint highlight across the top, and a gradient rim
   drawn on the border box so it sits under the padding box. */
.service__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  align-self: flex-start;
  flex: none;
  width: 56px;
  height: 56px;
  margin-bottom: 1rem;
  border: 1px solid transparent;
  border-radius: 16px;
  background:
    linear-gradient(160deg, #26262e, #0b0b0f 55%) padding-box,
    linear-gradient(150deg, rgb(177 90 248 / 90%), rgb(255 137 81 / 60%) 60%, rgb(255 255 255 / 12%)) border-box;
  box-shadow:
    0 8px 20px rgb(20 20 22 / 18%),
    inset 0 1px 0 rgb(255 255 255 / 14%);
  color: #ffffff;
  transition: transform 0.35s var(--ease), box-shadow 0.35s var(--ease);
}

.service__icon svg {
  width: 26px;
  height: 26px;
}

.card:hover .service__icon {
  transform: translateY(-2px) scale(1.04);
  box-shadow:
    0 12px 24px rgb(177 90 248 / 22%),
    inset 0 1px 0 rgb(255 255 255 / 14%);
}

/* "Most requested" pill, top-right of the featured card. */
.service__tag {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  padding: 0.3rem 0.65rem;
  border-radius: 999px;
  background: var(--purple-dim);
  color: #7c3aed;
  font-size: 0.68rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  line-height: 1;
}

.service__list {
  margin: 0.9rem 0 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.45rem;
  color: var(--text);
  font-size: 0.9rem;
  line-height: 1.45;
}

.service__list li {
  position: relative;
  padding-left: 1.4rem;
}

/* Gradient check mark, drawn with a rotated border so no icon file is needed. */
.service__list li::before {
  content: '';
  position: absolute;
  left: 0.15rem;
  top: 0.42em;
  width: 0.45rem;
  height: 0.22rem;
  border-left: 2px solid var(--purple);
  border-bottom: 2px solid var(--purple);
  transform: rotate(-45deg);
}

/* Technology chips. margin-top:auto pins the row to the bottom of the flex
   card, so the rows align across the grid regardless of copy length. */
.service__stack {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 1.25rem 0 0;
  padding: 1rem 0 0;
  list-style: none;
  border-top: 1px solid var(--border);
  margin-top: auto;
}

.service__stack li {
  padding: 0.3rem 0.6rem;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg);
  color: var(--muted);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  white-space: nowrap;
  transition: border-color 0.2s ease, color 0.2s ease;
}

.card:hover .service__stack li {
  border-color: rgb(177 90 248 / 45%);
  color: var(--text);
}

/* Scale band under the grid: copy and CTAs on the left, four proof points on
   the right. Dark on purpose, the one inverted surface on the page, so the
   "we handle big projects" claim is the thing the eye lands on after the six
   equal cards. Colours are the dark-theme values noted against :root. */
.services__proof {
  display: grid;
  grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: center;
  margin-top: clamp(1.5rem, 3vw, 2.5rem);
  padding: clamp(1.75rem, 4vw, 3rem);
  border-radius: calc(var(--radius) * 3);
  background: #0f0f14;
  color: #f4f4f4;
  position: relative;
  overflow: hidden;
}

.services__proof::before {
  content: '';
  position: absolute;
  inset: auto auto -50% -10%;
  width: 55%;
  aspect-ratio: 1;
  background: radial-gradient(closest-side, rgb(177 90 248 / 35%), rgb(255 137 81 / 12%) 55%, transparent);
  pointer-events: none;
}

.services__proof > * {
  position: relative;
}

.services__proof-title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.4rem, 2.4vw, 1.9rem);
  font-weight: 400;
  line-height: 1.2;
  letter-spacing: -0.02em;
  color: #f4f4f4;
  text-wrap: balance;
}

.services__proof-text {
  margin: 0.9rem 0 0;
  color: #a3a3ad;
  font-size: 0.95rem;
  line-height: 1.65;
  text-wrap: pretty;
}

.services__proof-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.5rem;
}

/* The outline button is designed for a white page; on the dark band it needs
   its own border and text colour to stay visible. */
.services__proof .btn--outline {
  border-color: rgb(255 255 255 / 22%);
  color: #f4f4f4;
}

.services__proof .btn--outline:hover {
  border-color: rgb(255 255 255 / 45%);
  background: rgb(255 255 255 / 6%);
}

.services__proof-list {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.services__proof-list li {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 1.1rem 1.2rem;
  border: 1px solid rgb(255 255 255 / 12%);
  border-radius: calc(var(--radius) * 2);
  background: rgb(255 255 255 / 4%);
}

.services__proof-list strong {
  font-size: 0.95rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  background: var(--grad);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
}

.services__proof-list span {
  color: #a3a3ad;
  font-size: 0.85rem;
  line-height: 1.5;
}

@media (max-width: 900px) {
  .services {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .services__proof {
    grid-template-columns: 1fr;
  }
}

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

  .services__proof-list {
    grid-template-columns: 1fr;
  }

  .services__proof-actions .btn {
    flex: 1 1 100%;
  }
}

@media (prefers-reduced-motion: reduce) {
  .service__icon,
  .service__stack li {
    transition: none;
  }
}

/* -----------------------------------------------------------------------------
   Pricing
-------------------------------------------------------------------------------*/
/* No align-items override here on purpose. Grid's default `stretch` is what
   makes all four plans the same height regardless of how many features each
   lists, which is the whole reason the CTAs can line up. */

/* The featured plan is lifted with a border and a wash rather than a scale
   transform, which would blur its text on non-retina screens. */
.plan--featured {
  border-color: var(--purple);
  background: linear-gradient(180deg, rgb(177 90 248 / 10%), transparent 55%), var(--panel-alt);
}

.section--alt .plan--featured {
  background: linear-gradient(180deg, rgb(177 90 248 / 10%), transparent 55%), var(--bg);
}

.plan__badge {
  align-self: flex-start;
  margin-bottom: 0.75rem;
  padding: 0.25rem 0.7rem;
  background: var(--grad);
  border-radius: 4px;
  color: #fff;
  font-size: 0.65rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  line-height: 1.6;
}

.plan__name {
  margin: 0;
  font-size: 0.8rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--muted);
}

.plan__price {
  display: flex;
  align-items: baseline;
  gap: 0.35rem;
  margin: 0.5rem 0 0.25rem;
  font-size: 2rem;
  font-weight: 900;
  letter-spacing: -0.03em;
  line-height: 1.1;
}

.plan__period {
  color: var(--subtle);
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0;
}

.plan__note {
  margin: 0 0 1.25rem;
  color: var(--muted);
  font-size: 0.85rem;
  line-height: 1.5;
}

/* Exactly one auto margin per card, and it lives on .plan__cta below. A
   second one here would make the two items share the free space instead of
   the button taking all of it, and the CTAs would drift apart again. */
.plan__features {
  margin: 0 0 1.5rem;
  padding: 1.25rem 0 0;
  border-top: 1px solid var(--border);
  list-style: none;
}

.plan__features li {
  position: relative;
  margin-bottom: 0.55rem;
  padding-left: 1.4rem;
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.5;
}

.plan__features li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.55rem;
  width: 5px;
  height: 5px;
  background: var(--purple);
  border-radius: 50%;
}

/* margin-top:auto pins every CTA to the bottom edge, so buttons line up
   across cards even when the feature lists differ in length. */
.plan__cta {
  margin-top: auto;
  width: 100%;
}

.plans__footnote {
  margin: 1.75rem 0 0;
  color: var(--subtle);
  font-size: 0.875rem;
  text-align: center;
}

.plans__footnote a {
  color: var(--purple);
  font-weight: 700;
}

.plans__footnote a:hover {
  text-decoration: underline;
}

/* -----------------------------------------------------------------------------
   Testimonials
   -----------------------------------------------------------------------------
   A snap-scrolling track rather than a grid: eight quotes in a grid is a wall,
   three at a time with the next one peeking in is an invitation to keep going.
   The track is a real overflow container, so it scrolls by touch, trackpad and
   keyboard with scripting off; the arrows, dots and auto-advance are layered
   on by the inline script. Bleeds to the shell edge on a phone (negative
   gutter margin) so the peeking card is visible instead of clipped at the
   padding line.
-------------------------------------------------------------------------------*/
.reviews {
  position: relative;
}

.reviews__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - 2rem) / 3);
  gap: 1rem;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  padding-block: 0.5rem 1rem;
  scrollbar-width: none;
}

.reviews__track::-webkit-scrollbar {
  display: none;
}

.reviews__track:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 4px;
  border-radius: calc(var(--radius) * 2);
}

.review {
  margin: 0;
  scroll-snap-align: start;
  min-width: 0;
}

.review:hover {
  border-color: rgb(177 90 248 / 45%);
  box-shadow: 0 16px 36px rgb(177 90 248 / 12%);
}

.review__head {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  margin-bottom: 1rem;
}

/* Initials on the brand gradient. Cards alternate the gradient angle so a row
   of three does not read as three copies of the same badge. */
.review__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 46px;
  height: 46px;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 0 0 3px var(--bg), 0 0 0 4px rgb(177 90 248 / 35%);
  color: #ffffff;
  font-size: 0.9rem;
  font-weight: 800;
  letter-spacing: 0.04em;
  transition: transform 0.35s var(--ease);
}

.review:nth-child(3n + 2) .review__avatar {
  background: linear-gradient(200deg, #b15af8 10%, #ff8951 90%);
}

.review:nth-child(3n) .review__avatar {
  background: linear-gradient(320deg, #ff8951 10%, #b15af8 90%);
}

.review:hover .review__avatar {
  transform: scale(1.06);
}

.review__head figcaption {
  min-width: 0;
}

.review__head strong {
  display: block;
  color: var(--text);
  font-size: 0.975rem;
  font-weight: 700;
  line-height: 1.25;
}

.review__head figcaption span {
  display: block;
  margin-top: 0.15rem;
  color: var(--subtle);
  font-size: 0.8rem;
  line-height: 1.4;
}

.review__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 0.9rem;
}

.review__stars {
  display: inline-flex;
  gap: 2px;
}

.review__stars svg {
  width: 15px;
  height: 15px;
  fill: var(--orange);
}

.review__tag {
  padding: 0.28rem 0.6rem;
  border-radius: 999px;
  background: var(--purple-dim);
  color: #7c3aed;
  font-size: 0.66rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  line-height: 1;
  white-space: nowrap;
}

/* Quotation mark as a watermark, drawn with the display face so it matches
   the headings rather than the body copy. */
.review blockquote {
  position: relative;
  margin: 0;
  padding-top: 0.25rem;
  color: var(--text);
  font-size: 0.95rem;
  line-height: 1.65;
  text-wrap: pretty;
}

.review blockquote::before {
  content: '\201C';
  position: absolute;
  top: -0.9rem;
  right: 0;
  font-family: var(--font-display);
  font-size: 4rem;
  line-height: 1;
  color: var(--purple);
  opacity: 0.12;
  pointer-events: none;
}

.reviews__nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: 0.75rem;
}

.reviews__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: 1px solid var(--border-strong);
  border-radius: 50%;
  background: var(--bg);
  color: var(--text);
  cursor: pointer;
  transition: border-color 0.2s ease, color 0.2s ease, transform 0.2s var(--ease), background 0.2s ease;
}

.reviews__btn:hover {
  border-color: var(--purple);
  color: var(--purple);
  transform: translateY(-1px);
}

.reviews__btn:disabled {
  opacity: 0.35;
  cursor: default;
  transform: none;
}

.reviews__dots {
  display: flex;
  gap: 0.4rem;
}

.reviews__dots button {
  width: 8px;
  height: 8px;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: var(--border-strong);
  cursor: pointer;
  transition: width 0.25s var(--ease), background 0.25s ease;
}

.reviews__dots button.is-active {
  width: 22px;
  background: var(--grad);
}

@media (max-width: 1080px) {
  .reviews__track {
    grid-auto-columns: calc((100% - 1rem) / 2);
  }
}

@media (max-width: 640px) {
  /* Bleed to the viewport edge so the next card peeks in, which is the cue
     that the row scrolls. scroll-padding keeps the snapped card off the
     edge by the same gutter the rest of the page uses. */
  .reviews__track {
    grid-auto-columns: min(86%, 22rem);
    margin-inline: calc(var(--gutter) * -1);
    padding-inline: var(--gutter);
    scroll-padding-inline: var(--gutter);
  }
}

@media (prefers-reduced-motion: reduce) {
  .reviews__track {
    scroll-behavior: auto;
  }

  .review__avatar,
  .reviews__dots button {
    transition: none;
  }
}

/* -----------------------------------------------------------------------------
   FAQ: native <details>, so it opens with JavaScript disabled and Google can
   read every answer in the initial HTML.
-------------------------------------------------------------------------------*/
.faq {
  max-width: 50rem;
  margin-inline: auto;
}

.faq details {
  margin-bottom: 0.75rem;
  background: var(--panel-alt);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 1.5);
}

.section--alt .faq details {
  background: var(--bg);
}

.faq details[open] {
  border-color: var(--border-strong);
}

.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.05rem 1.3rem;
  font-size: 0.975rem;
  font-weight: 700;
  line-height: 1.45;
  cursor: pointer;
  /* Both are required: Safari uses the -webkit- pseudo-element, everything
     else honours list-style. */
  list-style: none;
}

.faq summary::-webkit-details-marker {
  display: none;
}

.faq summary::after {
  content: '';
  flex: none;
  width: 24px;
  height: 24px;
  background: var(--grad);
  border-radius: 50%;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 8v8M8 12h8' stroke='%23000' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / 100% no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 8v8M8 12h8' stroke='%23000' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / 100% no-repeat;
  transition: transform 0.25s var(--ease);
}

/* Rotating the plus into a minus keeps one mask instead of two. */
.faq details[open] summary::after {
  transform: rotate(45deg);
}

.faq__body {
  margin: 0;
  padding: 0 1.3rem 1.2rem;
  color: var(--muted);
  font-size: 0.925rem;
  line-height: 1.65;
}

.faq__body a {
  color: var(--purple);
  font-weight: 600;
}

.faq__body a:hover {
  text-decoration: underline;
}

/* -----------------------------------------------------------------------------
   Closing CTA
-------------------------------------------------------------------------------*/
.cta {
  position: relative;
  padding: clamp(2.75rem, 6vw, 4.25rem) clamp(1.5rem, 5vw, 3.5rem);
  background: var(--panel-alt);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) * 3);
  text-align: center;
  overflow: hidden;
}

/* Same violet bloom as the hero, so the page opens and closes on the same
   note. Sits behind the copy via a stacking context, not a z-index war. */
.cta::before {
  content: '';
  position: absolute;
  inset: -60% -10% auto;
  height: 160%;
  background: radial-gradient(closest-side, rgb(177 90 248 / 18%), transparent);
  pointer-events: none;
}

.cta > * {
  position: relative;
}

.cta__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 3.4vw, 2.5rem);
  font-weight: 400;
  line-height: 1.15;
  letter-spacing: -0.025em;
  color: rgb(20 20 22);
  text-wrap: balance;
}

.cta__text {
  max-width: 34rem;
  margin: 1rem auto 0;
  color: var(--muted);
  text-wrap: pretty;
}

.cta__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  justify-content: center;
  margin-top: 2rem;
}

/* -----------------------------------------------------------------------------
   Footer
-------------------------------------------------------------------------------*/
.footer {
  border-top: 1px solid var(--border);
  padding-block: clamp(2.5rem, 5vw, 3.5rem) 1.5rem;
}

.footer__cols {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(3, minmax(0, 1fr));
  gap: 2rem clamp(1.5rem, 3vw, 3rem);
}

.footer__brand img {
  height: 34px;
  width: auto;
  border-radius: 8px;
}

.footer__tagline {
  margin: 1rem 0 0;
  max-width: 22rem;
  color: var(--muted);
  font-size: 0.875rem;
  line-height: 1.6;
}

.footer__contact {
  margin: 1rem 0 0;
  padding: 0;
  list-style: none;
  color: var(--muted);
  font-size: 0.875rem;
}

.footer__contact a:hover {
  color: var(--text);
}

.footer__heading {
  margin: 0 0 0.9rem;
  color: var(--text);
  font-size: 0.75rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.12em;
}

.footer__list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 0.55rem;
}

.footer__list a {
  color: var(--muted);
  font-size: 0.875rem;
}

.footer__list a:hover {
  color: var(--text);
}

/* Wide link block for the 10 landing pages, because a grid column each would make
   them unreadably narrow, so they run as a wrapping row instead. */
.footer__solutions {
  margin-top: 2.5rem;
  padding-top: 1.75rem;
  border-top: 1px solid var(--border);
}

.footer__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem 1.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.footer__tags a {
  color: var(--subtle);
  font-size: 0.825rem;
}

.footer__tags a:hover {
  color: var(--text);
}

.footer__bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem 2rem;
  margin-top: 2.5rem;
  padding-top: 1.5rem;
  border-top: 1px solid var(--border);
  color: var(--subtle);
  font-size: 0.825rem;
}

.footer__legal {
  display: flex;
  flex-wrap: wrap;
  gap: 1.25rem;
}

.footer__legal a:hover {
  color: var(--text);
}

.footer__social {
  display: flex;
  gap: 0.6rem;
}

.footer__social a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--muted);
  transition: border-color 0.2s ease, color 0.2s ease;
}

.footer__social a:hover {
  border-color: var(--purple);
  color: var(--purple);
}

/* -----------------------------------------------------------------------------
   Scroll reveal: progressive enhancement. The `is-reveal` class is added by
   the script in index.html, so with JavaScript off nothing is ever hidden.
-------------------------------------------------------------------------------*/
@media (prefers-reduced-motion: no-preference) {
  .is-reveal {
    opacity: 0;
    transform: translateY(18px);
    transition: opacity 0.6s var(--ease), transform 0.6s var(--ease);
  }

  .is-reveal.is-visible {
    opacity: 1;
    transform: none;
  }
}

/* -----------------------------------------------------------------------------
   Responsive: below-hero sections
-------------------------------------------------------------------------------*/
@media (max-width: 900px) {
  .demo {
    grid-template-columns: 1fr;
  }

  .demo__copy {
    max-width: 38rem;
  }

  .demo__preview-container {
    margin-top: 1rem;
  }

  .footer__cols {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  }

  .footer__brand {
    grid-column: 1 / -1;
  }
}

@media (max-width: 560px) {
  .strip__inner {
    gap: 0.9rem;
  }

  .cta__actions .btn {
    flex: 1 1 100%;
  }

  .demo__preview-img,
  .demo__preview-iframe {
    max-width: 100%;
    height: 500px;
    border-radius: calc(var(--radius) * 2);
  }

  .footer__cols {
    grid-template-columns: 1fr;
  }
}
