/* =========================================================================
   GOOGLE PLAY BUTTON
   =========================================================================
   Two shapes, because Site::playStore() renders one of two things:

   1. The official badge PNG, once it is saved to assets/google-play-badge.png.
      Then this rule must add NOTHING visual - no background, no border, no
      padding. Google's brand guidelines forbid framing or altering the badge,
      and a button chrome drawn around it is exactly that.

   2. A plain text button when the PNG is absent. That one needs to look like
      a button, so the styling below applies only when there is no image
      inside the link.

   :has() does the switching. Every browser that runs this site supports it,
   and the fallback if one does not is a text button with slightly more
   padding than intended - not a broken layout.
   ========================================================================= */
.btn-play {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  text-decoration: none;
}

/* Text-only form: give it button chrome. */
.btn-play:not(:has(img)) {
  background: #0a1220;
  color: #fff;
  border-radius: 10px;
  padding: 12px 20px;
  font-weight: 550;
  border: 1px solid #0a1220;
  transition: transform .15s var(--ease, ease), opacity .15s;
}
.btn-play:not(:has(img)):hover { transform: translateY(-1px); opacity: .92; }

.btn-play.btn-lg:not(:has(img)) { padding: 15px 26px; font-size: 1.02rem; }
.btn-play.btn-sm:not(:has(img)) { padding: 9px 15px; font-size: .88rem; }

/* -------------------------------------------------------------------------
   Badge form: no chrome at all, just the artwork.

   The badge is an <img> with baked-in rounded corners and its own black
   background, so anything drawn behind it shows as a rectangle poking out
   from under the curves. Hence no background, no border, no padding.

   line-height:0 removes the few pixels of descender space an inline image
   inherits from the text flow - without it the badge sits visibly lower than
   the button beside it, which is what made the row look misaligned.
   ------------------------------------------------------------------------- */
.btn-play:has(img) {
  background: none;
  border: 0;
  padding: 0;
  line-height: 0;
  border-radius: 8px;
  transition: transform .18s var(--ease, ease), box-shadow .18s var(--ease, ease);
  will-change: transform;
}

/*
 * Scaled from the centre rather than nudged.
 *
 * transform-origin defaults to the middle, so the badge grows evenly instead
 * of drifting - which is the difference between this and the magnet effect it
 * replaces. 1.035 is small on purpose: the badge is artwork with fixed
 * proportions, and a large scale reads as the logo being stretched.
 */
.btn-play:has(img):hover,
.btn-play:has(img):focus-visible {
  transform: scale(1.035);
  box-shadow: 0 10px 26px rgba(15, 23, 42, .22);
}

.btn-play:has(img):active { transform: scale(.985); }

.btn-play:has(img) img { display: block; }

/* Somebody who has asked their system for less motion gets none of it. */
@media (prefers-reduced-motion: reduce) {
  .btn-play:has(img) { transition: none; }
  .btn-play:has(img):hover,
  .btn-play:has(img):focus-visible,
  .btn-play:has(img):active { transform: none; }
}

/* =========================================================================
   KVN Billing - public site
   =========================================================================

   White paper, black ink, one blue. The look is editorial rather than
   "SaaS landing page": hairline rules instead of card shadows, numbered
   sections, an oversized display face, and small monospaced eyebrows. The
   motion is what carries it - see site.js - so the static styles stay quiet
   and get out of the way.

   Everything animated has a resting state defined here that is already
   correct. If the JavaScript never runs, or the visitor has asked for
   reduced motion, the page is finished and readable rather than a set of
   invisible elements waiting for a reveal that never comes. That is the one
   rule this file will not bend on.
   ========================================================================= */

/* -- tokens -------------------------------------------------------------- */

:root {
  --paper: #ffffff;
  --tint: #f5f3ef;          /* warm off-white; the only non-white surface */
  --ink: #0a1220;
  --ink-2: #55607a;
  --ink-3: #8b94a8;
  --rule: #e3e1dc;

  --brand: #0b3c91;
  --accent: #1e6bff;

  --wrap: 1280px;
  --gutter: 28px;

  /* One easing for everything that moves under its own steam. Motion that
     uses several different curves reads as several different websites. */
  --ease: cubic-bezier(0.16, 1, 0.3, 1);

  --font-display: "Bricolage Grotesque", "Segoe UI", system-ui, sans-serif;
  --font-body: "Inter Tight", "Segoe UI", system-ui, -apple-system, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, "Cascadia Mono", monospace;
}

/* -- reset --------------------------------------------------------------- */

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

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

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 17px;
  line-height: 1.6;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; }

h1, h2, h3, h4 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.03em;
  line-height: 0.98;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0; }

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

/* -- type scale ---------------------------------------------------------- */

/* clamp() rather than breakpoints: the display sizes have to shrink
   continuously or a 380px phone gets a headline three words wide. */
.display  { font-size: clamp(2.6rem, 7.4vw, 6.6rem); }
.h2       { font-size: clamp(2rem, 4.6vw, 3.9rem); }
.h3       { font-size: clamp(1.3rem, 2vw, 1.75rem); }

.lede {
  font-size: clamp(1.05rem, 1.5vw, 1.35rem);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 58ch;
}

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: flex;
  align-items: center;
  gap: 12px;
}

.eyebrow::before {
  content: "";
  width: 28px;
  height: 1px;
  background: currentColor;
  flex: none;
}

.muted { color: var(--ink-2); }
.small { font-size: 0.92rem; }
.accent { color: var(--accent); }

/* -- layout -------------------------------------------------------------- */

section { position: relative; }

.section {
  padding: clamp(72px, 11vw, 160px) 0;
  border-top: 1px solid var(--rule);
}

.section--flush { border-top: 0; }
.section--tint { background: var(--tint); border-top: 0; }

.stack-sm > * + * { margin-top: 12px; }
.stack   > * + * { margin-top: 22px; }
.stack-lg > * + * { margin-top: 48px; }

.cols {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 32px;
}

.col-4 { grid-column: span 4; }
.col-5 { grid-column: span 5; }
.col-6 { grid-column: span 6; }
.col-7 { grid-column: span 7; }
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }

@media (max-width: 900px) {
  .col-4, .col-5, .col-6, .col-7, .col-8 { grid-column: span 12; }
  .cols { gap: 26px; }
}

/* -- navigation ---------------------------------------------------------- */

.nav {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 60;
  padding: 18px 0;
  /* Set by JS as the page scrolls; the default keeps it readable if not. */
  background: rgba(255, 255, 255, var(--nav-bg, 0));
  backdrop-filter: blur(var(--nav-blur, 0px));
  border-bottom: 1px solid rgba(227, 225, 220, var(--nav-rule, 0));
  transition: padding 0.4s var(--ease);
}

.nav.is-stuck { padding: 11px 0; }

.nav-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
}

.brand {
  display: flex;
  align-items: center;
  gap: 11px;
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.02em;
  font-size: 1.05rem;
}

.brand img { width: 34px; height: 34px; }

.nav-links {
  display: flex;
  align-items: center;
  gap: 4px;
}

.nav-link {
  position: relative;
  padding: 8px 14px;
  font-size: 0.93rem;
  color: var(--ink-2);
  transition: color 0.3s var(--ease);
}

.nav-link:hover, .nav-link.is-current { color: var(--ink); }

/* The underline grows from the left on hover and stays put on the current
   page, so the nav always says where you are without a heavy pill. */
.nav-link::after {
  content: "";
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 3px;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.45s var(--ease);
}

.nav-link:hover::after, .nav-link.is-current::after { transform: scaleX(1); }

.nav-toggle {
  display: none;
  width: 42px;
  height: 42px;
  border: 1px solid var(--rule);
  background: none;
  border-radius: 50%;
  cursor: pointer;
  position: relative;
}

.nav-toggle span {
  position: absolute;
  left: 12px;
  right: 12px;
  height: 1.5px;
  background: var(--ink);
  transition: transform 0.4s var(--ease), opacity 0.2s;
}

.nav-toggle span:nth-child(1) { top: 16px; }
.nav-toggle span:nth-child(2) { bottom: 16px; }

body.menu-open .nav-toggle span:nth-child(1) { transform: translateY(4.5px) rotate(45deg); }
body.menu-open .nav-toggle span:nth-child(2) { transform: translateY(-4.5px) rotate(-45deg); }

@media (max-width: 860px) {
  .nav-toggle { display: block; }

  .nav-links {
    position: fixed;
    inset: 0;
    flex-direction: column;
    justify-content: center;
    gap: 6px;
    background: var(--paper);
    /* clip-path rather than opacity so the panel cannot swallow clicks
       while it is invisible - a bug you only find on a real phone. */
    clip-path: inset(0 0 100% 0);
    transition: clip-path 0.7s var(--ease);
  }

  body.menu-open .nav-links { clip-path: inset(0 0 0 0); }

  .nav-link {
    font-family: var(--font-display);
    font-size: clamp(2rem, 9vw, 3rem);
    color: var(--ink);
    padding: 10px 0;
  }

  .nav-link::after { left: 0; right: 0; }
  .nav .btn { display: none; }
}

/* -- buttons ------------------------------------------------------------- */

.btn {
  --btn-fg: var(--paper);
  --btn-bg: var(--ink);
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 15px 26px;
  border: 1px solid var(--btn-bg);
  border-radius: 100px;
  background: transparent;
  color: var(--btn-fg);
  font-size: 0.95rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  cursor: pointer;
  overflow: hidden;
  isolation: isolate;
  will-change: transform;
}

/* The fill wipes up from the bottom on hover and continues upward on the
   way out, so the button never plays the same move backwards. */
.btn::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--btn-bg);
  transform: translateY(0);
  transition: transform 0.55s var(--ease);
}

.btn:hover::before { transform: translateY(-101%); }
.btn:hover { color: var(--btn-bg); }

.btn-outline {
  --btn-fg: var(--ink);
  --btn-bg: var(--ink);
}

.btn-outline::before { transform: translateY(101%); }
.btn-outline:hover::before { transform: translateY(0); }
.btn-outline:hover { color: var(--paper); }

.btn-accent { --btn-bg: var(--accent); }
.btn-sm { padding: 11px 20px; font-size: 0.88rem; }

.btn .arrow {
  display: inline-block;
  transition: transform 0.45s var(--ease);
}

.btn:hover .arrow { transform: translateX(4px); }

/* -- text link ----------------------------------------------------------- */

.tlink {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-weight: 500;
  padding-bottom: 3px;
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-repeat: no-repeat;
  background-position: left bottom;
  transition: background-size 0.5s var(--ease);
}

.tlink:hover { background-size: 100% 1px; }

/* -- hero ---------------------------------------------------------------- */

/*
 * min-height is capped well below a full screen now.
 *
 * 94vh plus a two-column grid meant that on a phone - where the columns stack -
 * the hero was a screen and a half of mostly nothing before the image even
 * began. Content decides the height on small screens; the minimum only holds
 * the shape open on a desktop.
 */
.hero {
  position: relative;
  min-height: clamp(560px, 82vh, 920px);
  display: flex;
  align-items: center;
  padding: 140px 0 80px;
  overflow: hidden;
}

/* Above both decorative layers. */
.hero > .wrap { position: relative; z-index: 2; }

@media (max-width: 900px) {
  /* The stacked hero is tall enough on its own. */
  .hero { min-height: 0; padding: 120px 0 60px; }
}

/* The field behind the hero. Drawn with gradients rather than an image so
   it costs nothing to load and can be moved by the pointer without jank. */
.hero-field {
  position: absolute;
  inset: -20%;
  /* Zero, not -2. A negative z-index paints behind the nearest stacking
     context, so the moment any ancestor gained one - a transform, a filter,
     an opacity - both hero layers vanished. Ordering them above zero and
     lifting the content above them is stable whatever happens upstream. */
  z-index: 0;
  background:
    radial-gradient(38% 38% at 22% 28%, rgba(30, 107, 255, 0.13), transparent 70%),
    radial-gradient(32% 32% at 82% 62%, rgba(11, 60, 145, 0.10), transparent 70%);
  will-change: transform;
}

.hero-grid {
  position: absolute;
  inset: 0;
  z-index: 1;
  background-image:
    linear-gradient(to right, rgba(10, 18, 32, 0.055) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(10, 18, 32, 0.055) 1px, transparent 1px);
  background-size: 82px 82px;
  /* Faded at the edges so the grid suggests a plane rather than ending in
     a hard line halfway down the page. */
  mask-image: radial-gradient(75% 60% at 50% 45%, #000 30%, transparent 100%);
  -webkit-mask-image: radial-gradient(75% 60% at 50% 45%, #000 30%, transparent 100%);
  will-change: transform;
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: center;
}

.hero-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 26px;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* -- split text ---------------------------------------------------------- */

/* Each word gets a clipping mask and the word slides up out of it. The
   resting state is the finished state: JS adds .split-armed to hide the
   words only once it is certain it can also reveal them. */
.split-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  padding-bottom: 0.08em;
  margin-bottom: -0.08em;
}

.split-word > span {
  display: inline-block;
  will-change: transform;
}

.split-armed .split-word > span {
  transform: translateY(105%);
  transition: transform 0.95s var(--ease);
}

.split-armed.is-in .split-word > span { transform: translateY(0); }

/* -- reveal -------------------------------------------------------------- */

.reveal-armed {
  opacity: 0;
  transform: translateY(26px);
  transition: opacity 0.9s var(--ease), transform 0.9s var(--ease);
}

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

/* A hairline that draws itself across the page as it comes into view. */
.rule {
  height: 1px;
  background: var(--rule);
  transform-origin: left;
}

.rule.reveal-armed {
  opacity: 1;
  transform: scaleX(0);
  transition: transform 1.1s var(--ease);
}

.rule.reveal-armed.is-in { transform: scaleX(1); }

/* -- feature rows -------------------------------------------------------- */

/* A list, not a grid of cards. Each row opens on hover to show the detail,
   which keeps the page scannable while still holding real substance. */
.rows { border-top: 1px solid var(--rule); }

.row {
  display: block;
  border-bottom: 1px solid var(--rule);
  padding: clamp(22px, 3vw, 34px) 0;
  position: relative;
}

.row-head {
  display: grid;
  grid-template-columns: 70px 1fr auto;
  align-items: baseline;
  gap: 20px;
}

.row-num {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  color: var(--ink-3);
  letter-spacing: 0.1em;
}

.row-title {
  font-family: var(--font-display);
  font-size: clamp(1.35rem, 3vw, 2.3rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  transition: transform 0.55s var(--ease), color 0.35s var(--ease);
  transform-origin: left;
}

.row:hover .row-title { transform: translateX(14px); color: var(--brand); }

.row-body {
  grid-column: 2 / -1;
  max-width: 62ch;
  color: var(--ink-2);
  margin-top: 10px;
}

@media (max-width: 700px) {
  .row-head { grid-template-columns: 40px 1fr; }
  .row-body { grid-column: 2 / -1; }
  .row-tag { display: none; }
}

.row-tag {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* -- tiles --------------------------------------------------------------- */

.tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(255px, 1fr));
  gap: 1px;
  background: var(--rule);
  border: 1px solid var(--rule);
}

.tile {
  background: var(--paper);
  padding: clamp(24px, 3vw, 38px);
  position: relative;
  overflow: hidden;
  transition: color 0.4s var(--ease);
}

/* The tint sweeps in from the bottom rather than fading, which reads as a
   deliberate movement instead of a hover state that got stuck. */
.tile::before {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--tint);
  transform: translateY(100%);
  transition: transform 0.6s var(--ease);
}

.tile:hover::before { transform: translateY(0); }
.tile > * { position: relative; }

.tile-num {
  font-family: var(--font-mono);
  font-size: 0.68rem;
  color: var(--ink-3);
  letter-spacing: 0.12em;
}

.tile h3 { margin: 14px 0 10px; font-size: 1.22rem; }
.tile p { color: var(--ink-2); font-size: 0.97rem; }

/* -- figures ------------------------------------------------------------- */

.figure { margin: 0; }

/*
 * The frame holds the aspect ratio and does the clipping.
 *
 * Space is reserved from --ratio before anything loads, so a photo arriving
 * late cannot shove the text below it down the page. That reflow is the most
 * irritating thing a slow connection does to a reader, and it is entirely
 * avoidable.
 */
.figure-frame {
  position: relative;
  aspect-ratio: var(--ratio, 4 / 3);
  overflow: hidden;
  background: var(--tint);
  border-radius: 4px;
}

.figure-frame img,
.figure-frame .figure-art {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Slightly oversized so a parallax shift inside the frame never exposes an
     edge. The frame clips the overflow. */
  transform: scale(1.06);
  transition: transform 1.4s var(--ease);
}

.figure:hover .figure-frame img { transform: scale(1.12); }

.figure-credit {
  margin-top: 10px;
  font-family: var(--font-mono);
  font-size: 0.66rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/*
 * The reveal is a wipe, not a fade.
 *
 * A photo that fades in reads as a page still loading. One uncovered by a
 * moving edge reads as a deliberate transition, and it is the move the rest
 * of the site already makes with .tile and .btn.
 */
.figure.reveal-armed {
  opacity: 1;
  transform: none;
}

.figure.reveal-armed .figure-frame {
  clip-path: inset(0 0 100% 0);
  transition: clip-path 1.1s var(--ease);
}

.figure.reveal-armed.is-in .figure-frame { clip-path: inset(0 0 0 0); }

/*
 * A photo bled to the edge of the viewport, for section breaks.
 *
 * Done with margins, NOT with translateX(-50%). It was a transform, and
 * `.figure.reveal-armed { transform: none }` above is two classes to this
 * rule's one - so the reset won, the offset was dropped, and every full-bleed
 * image sat half a screen to the right with a viewport-wide band of white
 * beside it. Margins cannot be clobbered by the reveal, so the two no longer
 * compete for the same property.
 */
.figure-wide {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-right: calc(50% - 50vw);
}

.figure-wide .figure-frame { border-radius: 0; }

/* Overlapping the section above, which stops the page reading as a stack of
   equal blocks. Only where there is room for it to overlap into. */
.figure-lift { margin-top: clamp(-80px, -6vw, 0px); }

@media (max-width: 900px) { .figure-lift { margin-top: 0; } }

/* Text laid over an image needs the image darkened, or the text is only
   legible over whichever part of the photo happens to be dark. */
/*
 * Darkens an image that has text laid over it.
 *
 * Applied by the caller, never inferred. This used to guard itself with
 * :not(:has(.figure-caption-over)), which is dead weight now that nothing
 * applies figure-overlay without also placing a caption - and :has() is only
 * supported from 2023 onward, so the guard would have failed open on an older
 * browser anyway.
 */
.figure-overlay .figure-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(to top, rgba(10, 18, 32, 0.72), rgba(10, 18, 32, 0.15));
}

.figure-caption-over {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2;
  padding: clamp(20px, 4vw, 46px);
  color: #fff;
}

.figure-caption-over .eyebrow { color: rgba(255, 255, 255, 0.7); }

/* -- media rows ---------------------------------------------------------- */

/* An image and a block of text, alternating sides down the page. */
.media {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(28px, 5vw, 72px);
  align-items: center;
}

.media--flip .media-text { order: -1; }

@media (max-width: 900px) {
  .media { grid-template-columns: 1fr; }
  .media--flip .media-text { order: 0; }
}

/*
 * A standalone figure sitting between two sections.
 *
 * Both neighbours already contribute a section's worth of padding, so left
 * alone the image floats in the middle of roughly 320px of white. It supplies
 * its own small margin and the intro above it gives some of its padding back.
 */
.wrap > .figure { margin-block: clamp(6px, 1.6vw, 20px); }

.section--flush { padding-bottom: clamp(40px, 5vw, 76px); }

/*
 * A full-bleed band between two sections carries its own separation, so the
 * section that follows drops its top rule and some of its padding. Otherwise
 * the eye sees the image, then a rule, then 160px of white, then a heading.
 */
.figure-wide + .section {
  border-top: 0;
  padding-top: clamp(56px, 7vw, 110px);
}

/* -- marquee ------------------------------------------------------------- */

.marquee {
  overflow: hidden;
  white-space: nowrap;
  padding: 26px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  user-select: none;
}

.marquee-track {
  display: inline-flex;
  gap: 44px;
  will-change: transform;
}

.marquee-item {
  font-family: var(--font-display);
  font-size: clamp(1.5rem, 3.6vw, 2.8rem);
  font-weight: 600;
  letter-spacing: -0.03em;
  color: var(--ink);
  display: inline-flex;
  align-items: center;
  gap: 44px;
}

.marquee-item::after {
  content: "";
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

/* -- counters ------------------------------------------------------------ */

.stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 40px;
}

.stat-num {
  font-family: var(--font-display);
  font-size: clamp(2.4rem, 5.5vw, 4.4rem);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.stat-label {
  margin-top: 10px;
  color: var(--ink-2);
  font-size: 0.95rem;
  max-width: 30ch;
}

/* -- sticky pair --------------------------------------------------------- */

.pair { display: grid; grid-template-columns: 1fr 1fr; gap: 60px; align-items: start; }
.pair-sticky { position: sticky; top: 130px; }

@media (max-width: 900px) {
  .pair { grid-template-columns: 1fr; gap: 34px; }
  .pair-sticky { position: static; }
}

/* -- phone mockup -------------------------------------------------------- */

.phone {
  width: min(300px, 78vw);
  aspect-ratio: 9 / 19;
  border: 1px solid var(--ink);
  border-radius: 34px;
  padding: 9px;
  background: var(--paper);
  margin-inline: auto;
  will-change: transform;
  transform-style: preserve-3d;
}

.phone-screen {
  height: 100%;
  border-radius: 26px;
  background: var(--tint);
  padding: 20px 16px;
  display: flex;
  flex-direction: column;
  gap: 11px;
  overflow: hidden;
}

.phone-bar {
  height: 10px;
  border-radius: 3px;
  background: rgba(10, 18, 32, 0.12);
}

.phone-bar.w-70 { width: 70%; }
.phone-bar.w-45 { width: 45%; }
.phone-bar.w-90 { width: 90%; }
.phone-bar.tall { height: 34px; }
.phone-bar.solid { background: var(--brand); }
.phone-bar.accent { background: var(--accent); }

/* -- security panel ------------------------------------------------------ */

.spec {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 24px;
  padding: 26px 0;
  border-bottom: 1px solid var(--rule);
}

.spec dt {
  font-family: var(--font-mono);
  font-size: 0.74rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.spec dd { margin: 0; color: var(--ink-2); max-width: 66ch; }
.spec dd strong { color: var(--ink); font-weight: 600; }

@media (max-width: 760px) { .spec { grid-template-columns: 1fr; gap: 10px; } }

/* -- callout ------------------------------------------------------------- */

.callout {
  border: 1px solid var(--ink);
  padding: clamp(26px, 4vw, 44px);
}

.callout--warn { border-color: #d99a2b; background: #fffaf1; }

/* -- form ---------------------------------------------------------------- */

.field { position: relative; padding-top: 22px; }

.field input,
.field select,
.field textarea {
  width: 100%;
  border: 0;
  border-bottom: 1px solid var(--rule);
  background: transparent;
  padding: 10px 0;
  font: inherit;
  color: var(--ink);
  border-radius: 0;
  transition: border-color 0.4s var(--ease);
}

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-bottom-color: var(--ink);
}

.field label {
  position: absolute;
  top: 22px;
  left: 0;
  color: var(--ink-3);
  font-size: 1rem;
  pointer-events: none;
  transition: transform 0.4s var(--ease), font-size 0.4s var(--ease), color 0.4s var(--ease);
  transform-origin: left;
}

.field input:focus + label,
.field textarea:focus + label,
.field input:not(:placeholder-shown) + label,
.field textarea:not(:placeholder-shown) + label {
  transform: translateY(-24px);
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.field textarea { resize: vertical; min-height: 110px; }

/* -- footer -------------------------------------------------------------- */

.footer {
  border-top: 1px solid var(--rule);
  padding: 70px 0 34px;
}

.footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr;
  gap: 40px;
}

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

.footer h4 {
  font-family: var(--font-mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 16px;
}

.footer-links { display: flex; flex-direction: column; gap: 9px; }
.footer-links a { color: var(--ink-2); width: fit-content; }
.footer-links a:hover { color: var(--ink); }

.footer-base {
  margin-top: 54px;
  padding-top: 22px;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: space-between;
  color: var(--ink-3);
  font-size: 0.85rem;
}

/* Oversized wordmark at the foot. Purely typographic, and it fills what
   would otherwise be dead space at the bottom of every page. */
/*
 * Smaller and darker than it was.
 *
 * At 13rem and 7% opacity this was a band of near-white the height of a phone
 * screen - indistinguishable from the page having run out, which is exactly
 * what it looked like. Enough contrast to read as a deliberate mark, and
 * enough restraint not to shout.
 */
.footer-mark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: clamp(2.2rem, 9vw, 6.5rem);
  letter-spacing: -0.05em;
  line-height: 0.85;
  color: var(--ink);
  opacity: 0.12;
  margin-top: 40px;
  text-align: center;
  user-select: none;
  overflow: hidden;
}

@media (max-width: 700px) { .footer-mark { display: none; } }

/* -- cursor -------------------------------------------------------------- */

/* Hidden until JS positions it, so it never flashes in the top-left corner
   on load. Only ever created on a device with a real pointer. */
.cursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 200;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.3s;
}

.cursor.is-live { opacity: 1; }

.cursor-dot {
  position: fixed;
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  border-radius: 50%;
  background: var(--ink);
}

.cursor-ring {
  position: fixed;
  width: 38px;
  height: 38px;
  margin: -19px 0 0 -19px;
  border: 1px solid rgba(10, 18, 32, 0.4);
  border-radius: 50%;
  transition: width 0.35s var(--ease), height 0.35s var(--ease),
              margin 0.35s var(--ease), background 0.35s var(--ease),
              border-color 0.35s var(--ease);
}

body.cursor-hot .cursor-ring {
  width: 62px;
  height: 62px;
  margin: -31px 0 0 -31px;
  background: rgba(30, 107, 255, 0.08);
  border-color: var(--accent);
}

/* -- scroll progress ----------------------------------------------------- */

.progress {
  position: fixed;
  top: 0;
  left: 0;
  height: 2px;
  width: 100%;
  z-index: 70;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
}

/* -- utilities ----------------------------------------------------------- */

.tilt { will-change: transform; }
.parallax { will-change: transform; }
.center { text-align: center; }
.mx-auto { margin-inline: auto; }
.maxw-60 { max-width: 60ch; }
.pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 6px 14px;
  border: 1px solid var(--rule);
  border-radius: 100px;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: #12a150;
  flex: none;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--paper);
  padding: 12px 18px;
  z-index: 300;
}

.skip:focus { left: 12px; top: 12px; }

/* -- reduced motion ------------------------------------------------------ */

/*
 * Not a courtesy. Scroll-linked and pointer-linked movement of this kind is
 * a genuine trigger for people with vestibular disorders, and this site has
 * a great deal of it. Everything is switched off at the source: the classes
 * below are what JS looks for, and it also refuses to arm anything at all
 * when the query matches, so nothing is left mid-transition.
 */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }

  .reveal-armed, .split-armed .split-word > span {
    opacity: 1 !important;
    transform: none !important;
  }

  .rule.reveal-armed { transform: scaleX(1) !important; }
  .cursor { display: none; }
  .hero-field, .hero-grid { transform: none !important; }
}


/* =========================================================================
   PRODUCT MARKETING LAYER
   =========================================================================

   The editorial base above stays - it owns the type scale, the grid and the
   motion. This layer adds what a software product page needs and an essay
   does not: coloured feature tiles, a stats band, numbered setup steps, an
   industry grid and trust badges.

   The reference is vyaparapp.in, which is the shape Indian SME buyers now
   expect a billing product to take: dense, scannable, every claim on a card
   with an icon, and the download button never more than a screen away. The
   earlier layout asked people to read; this one lets them scan.
   ========================================================================= */

:root {
  /* One warm accent against the existing blue. Vyapar leans on red for its
     calls to action, and a single warm colour against a cool page is what
     makes a button unmissable without shouting. */
  --hot: #e0353f;
  --hot-dark: #b8272f;
  --mint: #0f9d58;
  --sky: #eef4ff;
  --sun: #fff6e6;
  --rose: #fff0f1;
  --card-shadow: 0 1px 2px rgba(10, 18, 32, .04), 0 8px 24px rgba(10, 18, 32, .06);
}

/* -- buttons ------------------------------------------------------------- */

.btn-hot {
  --btn-bg: var(--hot);
  --btn-fg: #fff;
  border-color: var(--hot);
  font-weight: 600;
}

.btn-hot:hover { color: var(--hot); }
.btn-lg { padding: 18px 34px; font-size: 1.02rem; }

/* -- hero ---------------------------------------------------------------- */

.hero-product {
  padding: 150px 0 70px;
  background:
    radial-gradient(60% 50% at 15% 0%, rgba(30, 107, 255, .07), transparent 70%),
    radial-gradient(50% 40% at 95% 20%, rgba(224, 53, 63, .06), transparent 70%),
    var(--paper);
}

.badge-row {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  border: 1px solid var(--rule);
  border-radius: 100px;
  background: var(--paper);
  font-size: 0.82rem;
  font-weight: 500;
  box-shadow: var(--card-shadow);
}

.badge b { font-weight: 600; }
.badge .dot { background: var(--mint); }

/* -- trust strip --------------------------------------------------------- */

.trust {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
  margin-top: 44px;
}

.trust-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 16px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: var(--paper);
  box-shadow: var(--card-shadow);
  font-size: 0.9rem;
  font-weight: 500;
}

.trust-item .ico {
  width: 38px;
  height: 38px;
  border-radius: 10px;
  display: grid;
  place-items: center;
  flex: none;
  background: var(--sky);
  color: var(--brand);
  /* Sized for the label, not guessed. "AES" is three characters and was
     spilling out of the chip at the inherited body size. */
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}

/* -- stats band ---------------------------------------------------------- */

.band {
  background: var(--ink);
  color: #fff;
  padding: clamp(44px, 6vw, 80px) 0;
}

.band .eyebrow { color: rgba(255, 255, 255, .55); }
.band h2 { color: #fff; }
.band p { color: rgba(255, 255, 255, .72); }

.band-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 28px;
  margin-top: 42px;
}

.band-stat .n {
  font-family: var(--font-display);
  font-size: clamp(2rem, 4.4vw, 3.2rem);
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1;
  color: #fff;
}

.band-stat .l {
  margin-top: 8px;
  font-size: 0.9rem;
  color: rgba(255, 255, 255, .68);
}

/* -- feature tiles ------------------------------------------------------- */

.grid-tiles {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(258px, 1fr));
  gap: 16px;
}

.ftile {
  padding: 24px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: var(--paper);
  transition: transform .45s var(--ease), box-shadow .45s var(--ease),
              border-color .45s var(--ease);
}

.ftile:hover {
  transform: translateY(-4px);
  box-shadow: var(--card-shadow);
  border-color: var(--ink-3);
}

/*
 * A colour emoji font is named explicitly.
 *
 * Left to inherit, the icons fall back to whatever the platform picks -
 * flat monochrome glyphs on some Windows builds, a different metric on
 * Android - so the same tile looks like two different designs. Naming the
 * emoji fonts first keeps them consistent, and the sans fallback means a
 * device with none still shows something.
 */
.ftile .ico,
.industry .ico {
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
               var(--font-body), sans-serif;
  font-style: normal;
  line-height: 1;
}

.ftile .ico {
  width: 44px;
  height: 44px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  font-size: 1.15rem;
  background: var(--sky);
  color: var(--brand);
  margin-bottom: 16px;
}

.ftile:nth-child(3n+2) .ico { background: var(--rose); color: var(--hot); }
.ftile:nth-child(3n+3) .ico { background: var(--sun); color: #b26a00; }

.ftile h3 { font-size: 1.06rem; margin-bottom: 8px; letter-spacing: -0.02em; }
.ftile p { color: var(--ink-2); font-size: 0.94rem; }

/* -- numbered steps ------------------------------------------------------ */

.steps {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 18px;
  counter-reset: step;
}

.step {
  position: relative;
  padding: 26px 22px 22px;
  border: 1px solid var(--rule);
  border-radius: 14px;
  background: var(--paper);
}

.step::before {
  counter-increment: step;
  content: "0" counter(step);
  position: absolute;
  top: -14px;
  left: 22px;
  padding: 4px 12px;
  border-radius: 100px;
  background: var(--ink);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
}

.step h3 { font-size: 1.02rem; margin-bottom: 8px; }
.step p { color: var(--ink-2); font-size: 0.93rem; }

/* -- industries ---------------------------------------------------------- */

.industries {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px;
}

.industry {
  padding: 22px 18px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  text-align: center;
  background: var(--paper);
  transition: background .35s var(--ease), border-color .35s var(--ease);
}

.industry:hover { background: var(--tint); border-color: var(--ink-3); }
.industry .ico { font-size: 1.6rem; display: block; margin-bottom: 10px; }
.industry span { font-size: 0.92rem; font-weight: 500; }

/* -- split feature rows -------------------------------------------------- */

.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(28px, 5vw, 70px);
  align-items: center;
}

.split--flip > *:first-child { order: 2; }

@media (max-width: 900px) {
  .split { grid-template-columns: 1fr; }
  .split--flip > *:first-child { order: 0; }
}

.checks { display: flex; flex-direction: column; gap: 12px; margin-top: 22px; }

.check {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  font-size: 0.96rem;
}

.check::before {
  content: "";
  width: 20px;
  height: 20px;
  border-radius: 50%;
  flex: none;
  margin-top: 2px;
  background: var(--mint);
  /* A tick drawn as a mask so it needs no icon font and no image. */
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z' fill='%23000'/%3E%3C/svg%3E") center/16px no-repeat, linear-gradient(#000, #000);
  -webkit-mask-composite: source-in;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9 16.2 4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4z' fill='%23000'/%3E%3C/svg%3E") center/16px no-repeat;
}

/* -- final call ---------------------------------------------------------- */

/* The gradient block supplies its own separation, so the section holding it
   drops the rule that would otherwise sit just above it. A class rather than
   :has(), which is only supported from 2023 onward - a stray hairline on an
   older browser is exactly the kind of small wrongness this pass is for. */
.section--finale { border-top: 0; }

.finale {
  background: linear-gradient(135deg, var(--brand), #061f4d);
  color: #fff;
  border-radius: 20px;
  padding: clamp(34px, 6vw, 72px);
  text-align: center;
}

.finale h2 { color: #fff; }
.finale p { color: rgba(255, 255, 255, .78); }

/*
 * The secondary button on the dark block gets its own rule.
 *
 * It was .btn-outline with the colour variables overridden inline, which
 * produced a white fill under white text on hover - a button that vanished
 * the moment anyone pointed at it. Overriding a component's variables to put
 * it on a different background is how that keeps happening; a variant is
 * cheaper to reason about.
 */
.finale .btn-ghost-light {
  border-color: rgba(255, 255, 255, .55);
  color: #fff;
  background: transparent;
}

.finale .btn-ghost-light::before { background: #fff; transform: translateY(101%); }
.finale .btn-ghost-light:hover::before { transform: translateY(0); }
.finale .btn-ghost-light:hover { color: var(--brand); border-color: #fff; }


/* -- roadmap ------------------------------------------------------------- */

/*
 * Coming-soon cards.
 *
 * Visually distinct from the shipped feature tiles ON PURPOSE. A roadmap item
 * dressed to look like a delivered one is how a customer buys the app for
 * something it cannot yet do, and finds out at the counter. So these sit on
 * the tinted surface, carry a dashed border and a "Planned" chip, and never
 * borrow the solid card the real features use.
 */
.roadmap {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(268px, 1fr));
  gap: 16px;
}

.rcard {
  position: relative;
  padding: 26px 24px 24px;
  border: 1.5px dashed var(--ink-3);
  border-radius: 14px;
  background: var(--paper);
  transition: border-color .4s var(--ease), transform .4s var(--ease);
}

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

.rcard .ico {
  width: 42px;
  height: 42px;
  border-radius: 12px;
  display: grid;
  place-items: center;
  font-size: 1.1rem;
  background: var(--sky);
  color: var(--brand);
  margin-bottom: 15px;
  font-family: "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji",
               var(--font-body), sans-serif;
  line-height: 1;
}

.rcard h3 { font-size: 1.04rem; margin-bottom: 8px; letter-spacing: -0.02em; }
.rcard p { color: var(--ink-2); font-size: 0.93rem; }

/* The chip that stops this reading as a shipped feature. */
.soon {
  position: absolute;
  top: 14px;
  right: 14px;
  padding: 4px 11px;
  border-radius: 100px;
  background: var(--sun);
  color: #8a5300;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  font-weight: 500;
}

/* A plain statement above the grid, not decoration - see the section copy. */
.roadmap-note {
  margin-top: 30px;
  padding: 16px 20px;
  border-left: 3px solid var(--ink-3);
  background: var(--tint);
  border-radius: 0 10px 10px 0;
  font-size: 0.93rem;
  color: var(--ink-2);
}
