/* ══════════════════════════════════════════════════════════════════════════
   Antri Online — marketing site
   ══════════════════════════════════════════════════════════════════════════

   Ported from the "Antri Online Landing v2" design. The design was authored
   with inline styles; here it becomes a token layer plus classes so the two
   pages (index, masuk) stay in step and the app can borrow the same taste.

   Deliberately light-only. A marketing page is a single committed statement,
   and the design specifies one look; the product itself (app.antri.online)
   carries the light/dark switch.

   Contrast: the source design's grey ramp was tuned by eye and lands its body
   copy at 3.25:1 — below the 4.5:1 AA needs for text. Every step that carries
   real words is re-derived here at the SAME hue and chroma, walked down in
   oklab only until it clears 4.5:1. The palette reads identically; it is just
   legible now. Purely decorative greys (dashed placeholder frames, the giant
   footer wordmark) keep the original values.
   ══════════════════════════════════════════════════════════════════════════ */

:root {
  /* ── surfaces ─────────────────────────────────────────────────────── */
  --canvas: #f3f4f6;
  --surface: #fff;
  --well: #f6f7f9;
  --ink: #0b0b0f;

  /* ── text ─────────────────────────────────────────────────────────────
     Fitted against the DARKEST ground each one actually lands on, not just
     white: these sit on the page canvas and inside the pale tints too, and a
     value that clears 4.5:1 on white is only ~4.1:1 on #F3F4F6.
     Worst case — ink-2 5.52:1, ink-3 4.51:1. ink-3 replaces the design's
     #8A8F98 (3.25:1), which carried nearly every paragraph. */
  --ink-2: #5b616b;
  --ink-3: #6b7078;
  /* Decorative only — never words the reader needs. */
  --ink-faint: #a6abb3;

  --hairline: rgba(11, 11, 15, 0.06);
  --hairline-2: rgba(11, 11, 15, 0.1);

  /* ── accent ───────────────────────────────────────────────────────────
     The coral has three jobs and one value cannot do all of them.

       --coral       decoration — the footer wordmark, the "paling banyak
                     dipilih" flag, tint derivation, the float shadows.
                     Exactly as designed.
       --coral-text  labels and links. As text the bright coral is 2.56:1,
                     so this is the same hue walked down until it clears
                     4.5:1 on white, on the canvas, AND on its own tint.
       --coral-btn   the primary button fill. Deeper again, because the
                     button wears a WHITE label over a top-lit gradient and
                     the label has to clear AA at the gradient's LIGHTEST
                     point, not its average. Under the panel's 88%-white top
                     stop, the bright coral leaves white at 2.45:1; this
                     value puts it at 4.52:1. It lands at oklch L 0.53 —
                     the same lightness band as the app's default accent, so
                     the marketing CTA and the in-product primary button now
                     read as the same button.

     Same `-text` fitting for the violet / amber / sky / green steps. */
  --coral: #ff6b4a;
  --coral-deep: #f2542f;
  --coral-text: #cd3b19;
  --coral-tint: #fff1ed;

  /* Primary-button gradient. Same recipe as the admin panel's `.btn-primary`:
     a touch lighter at the top, a touch darker at the bottom, so the control
     reads as lit from above rather than as a flat rectangle. */
  --coral-btn: #bf2d04;
  --coral-btn-top: color-mix(in oklab, var(--coral-btn) 88%, white);
  --coral-btn-bottom: color-mix(in oklab, var(--coral-btn) 92%, black);

  --violet: #7c5cff;
  --violet-light: #a78bfa;
  --violet-text: #6f4cf5;
  --violet-tint: #f1edff;

  --amber: #ffc53d;
  --amber-light: #ffe066;
  --amber-text: #9d6400;
  --amber-tint: #fff8e1;

  --sky: #38bdf8;
  --sky-light: #7dd3fc;
  --sky-text: #0075b7;
  --sky-tint: #e8f6ff;

  --green: #22c55e;
  --green-light: #86efac;
  --green-text: #008238;
  --green-tint: #e9f9ef;

  /* ── radii ────────────────────────────────────────────────────────── */
  --r-pill: 999px;
  --r-xl: 36px;
  --r-lg: 30px;
  --r-md: 22px;
  --r-sm: 14px;
  --r-xs: 9px;

  /* ── elevation ────────────────────────────────────────────────────── */
  --lift-1: 0 6px 18px rgba(16, 24, 40, 0.05);
  --lift-2: 0 10px 30px rgba(16, 24, 40, 0.07);
  --lift-3: 0 24px 60px rgba(16, 24, 40, 0.09);
}

/* ══ base ══════════════════════════════════════════════════════════════ */

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

html {
  scroll-behavior: smooth;
  /* The sticky header would otherwise cover an anchored heading. */
  scroll-padding-top: 96px;
  /* `clip`, not `hidden`: the hero's 132vw colour wash hangs 16vw past each
     edge by design, and something has to contain it. `hidden` would make the
     root a scroll container and break `position: sticky` on the header;
     `clip` trims the overflow without that side effect. */
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: Figtree, ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: clip;
}

h1,
h2,
h3,
h4 {
  margin: 0;
  font-weight: 800;
  letter-spacing: -0.04em;
  line-height: 1.1;
  text-wrap: balance;
}

p {
  margin: 0;
  text-wrap: pretty;
}

a {
  color: var(--coral-text);
  text-decoration: none;
}

a:hover {
  color: var(--coral-deep);
}

/* `height: auto` is the load-bearing line here.
   Every screenshot carries width/height attributes so the browser can reserve
   its space before the bytes arrive. But once CSS sets `width: 100%` without
   saying anything about height, the attribute height stays a *used value* —
   so the image renders at its natural width-scaled... no: at 100% width and
   a fixed pixel height, i.e. stretched. Four of the five shots on this page
   were being pulled vertically that way, because their attributes had gone
   stale relative to the files. `auto` makes the ratio always come from the
   image itself, so a wrong attribute can only mis-reserve space for a moment
   rather than deform the picture permanently. */
img {
  max-width: 100%;
  height: auto;
  display: block;
}

/* Anything visible only to screen readers. */
.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

:focus-visible {
  outline: 2px solid var(--coral-text);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Skip link — first tab stop, hidden until focused. */
.skip {
  position: absolute;
  left: 50%;
  top: 8px;
  transform: translate(-50%, -200%);
  z-index: 100;
  background: var(--ink);
  color: #fff;
  padding: 12px 20px;
  border-radius: var(--r-sm);
  font-weight: 600;
  transition: transform 0.15s;
}

.skip:focus {
  transform: translate(-50%, 0);
  color: #fff;
}

/* ══ layout ════════════════════════════════════════════════════════════ */

.page {
  padding: 18px;
}

.wrap {
  max-width: 1080px;
  margin: 0 auto;
}

.wrap-narrow {
  max-width: 880px;
  margin: 0 auto;
}

section {
  padding: 70px 20px;
}

.center {
  text-align: center;
}

/* ══ header ════════════════════════════════════════════════════════════ */

.header {
  position: sticky;
  top: 18px;
  z-index: 60;
  display: flex;
  justify-content: center;
  margin-bottom: 24px;
}

.header-bar {
  display: flex;
  align-items: center;
  gap: 26px;
  flex-wrap: wrap;
  justify-content: center;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  padding: 10px 12px 10px 26px;
  box-shadow: var(--lift-2);
}

.brand {
  display: flex;
  align-items: center;
  gap: 9px;
  color: var(--ink);
  font-weight: 800;
  font-size: 19px;
  letter-spacing: -0.05em;
}

.brand:hover {
  color: var(--ink);
}

/* The wordmark's icon. Was a violet gradient tile with an "A"; now the real
   Antri Online mark — animated in the header, static in the footer. White
   plate because the artwork was drawn against white and its lighter strokes
   disappear on a tint. */
.brand-mark {
  width: 26px;
  height: 26px;
  border-radius: var(--r-xs);
  background: #fff;
  display: grid;
  place-items: center;
  flex: none;
  overflow: hidden;
  object-fit: contain;
}

#brand-lottie svg,
#brand-lottie > div {
  width: 100% !important;
  height: 100% !important;
  display: block;
}

.brand-soft {
  font-weight: 500;
  color: var(--ink-3);
}

.header-nav {
  display: flex;
  gap: 22px;
  font-size: 15px;
  font-weight: 500;
}

.header-nav a {
  color: var(--ink-2);
}

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

.header-cta {
  display: flex;
  gap: 8px;
  align-items: center;
}

/* ══ buttons ═══════════════════════════════════════════════════════════ */

.btn {
  display: inline-block;
  /* `<button>` defaults to `cursor: default`; only `<a href>` gets the hand
     for free. Both wear .btn here, so it is set explicitly. */
  cursor: pointer;
  font-weight: 600;
  font-size: 16px;
  padding: 16px 28px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  cursor: pointer;
  font-family: inherit;
  text-align: center;
  transition:
    background-color 0.15s,
    transform 0.06s,
    box-shadow 0.15s;
}

.btn:active {
  transform: translateY(1px);
}

/* Primary CTA — matches the admin panel's `.btn-primary`: vertical gradient,
   white label, inner top highlight, and a pressed state that inverts the
   shadow inward so the control feels physically depressed. */
.btn-primary {
  background-color: var(--coral-btn);
  background-image: linear-gradient(to bottom, var(--coral-btn-top), var(--coral-btn-bottom));
  color: #fff;
  box-shadow:
    0 12px 28px rgba(191, 45, 4, 0.3),
    inset 0 1px 0 rgba(255, 255, 255, 0.16);
}

.btn-primary:hover {
  color: #fff;
  background-image: linear-gradient(
    to bottom,
    color-mix(in oklab, var(--coral-btn-top) 92%, white),
    var(--coral-btn-top)
  );
  box-shadow:
    0 16px 34px rgba(191, 45, 4, 0.36),
    inset 0 1px 0 rgba(255, 255, 255, 0.2);
}

.btn-primary:active {
  background-image: none;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.22);
}

/* The dark button gets the same treatment so the two never look like they
   came from different kits sitting side by side in the header. */
.btn-dark {
  background-image: linear-gradient(to bottom, #24262e, var(--ink));
}

.btn-dark:hover {
  background-image: linear-gradient(to bottom, #34373f, #24262e);
}

.btn-dark:active {
  background-image: none;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.4);
}

.btn-dark {
  background: var(--ink);
  color: #fff;
  box-shadow: 0 8px 20px rgba(11, 11, 15, 0.22);
}

.btn-dark:hover {
  background: #22242b;
  color: #fff;
}

.btn-ghost {
  background: var(--surface);
  border-color: var(--hairline-2);
  color: var(--ink);
}

.btn-ghost:hover {
  background: #edeff2;
  color: var(--ink);
}

.btn-well {
  background: var(--canvas);
  border-color: var(--hairline);
  color: var(--ink);
}

.btn-well:hover {
  background: #e9ebef;
  color: var(--ink);
}

/* Compact variants used in the header and pricing cards. */
.btn-sm {
  font-size: 15px;
  padding: 11px 20px;
  border-radius: var(--r-pill);
}

.btn-block {
  display: block;
  width: 100%;
  font-size: 15px;
  padding: 12px;
  border-radius: 12px;
}

/* ══ cards ═════════════════════════════════════════════════════════════ */

.card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  padding: 28px;
}

.card-md {
  border-radius: var(--r-md);
  padding: 24px;
}

.card-dark {
  background: var(--ink);
  color: #fff;
  border-radius: var(--r-lg);
  padding: 32px;
}

.card-dark p {
  color: #c8ccd3;
}

/* ══ grids ═════════════════════════════════════════════════════════════ */

.grid {
  display: grid;
  gap: 16px;
}

.grid > * {
  min-width: 0;
}

.g-190 {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: 14px;
}

.g-230 {
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
}

.g-240 {
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 14px;
}

.g-260 {
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  /* Cards size to their own content. Without this every single-width tile
     stretches to match the two-wide tile beside it, which left the short
     ones as tall boxes of empty white. */
  align-items: start;
}

.g-290 {
  grid-template-columns: repeat(auto-fit, minmax(290px, 1fr));
}

/* A bento tile twice as wide — only once there are genuinely two columns to
   span. Below that the `span 2` would force a second track that the
   auto-fit minmax never asked for, and every sibling would go half-width. */
@media (min-width: 720px) {
  .span-2 {
    grid-column: span 2;
  }
}

/* ══ eyebrow + section heads ═══════════════════════════════════════════ */

.eyebrow {
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.eyebrow-coral {
  color: var(--coral-text);
}

.eyebrow-violet {
  color: var(--violet-text);
}

.h2 {
  font-size: clamp(30px, 4.2vw, 52px);
  margin: 14px 0 0;
}

.lede {
  font-size: 17.5px;
  color: var(--ink-3);
  margin: 16px auto 0;
  max-width: 620px;
  line-height: 1.6;
}

/* ══ gradient icon tiles ═══════════════════════════════════════════════ */

.tile {
  border-radius: 17px;
  display: grid;
  place-items: center;
  flex: none;
}

.tile svg {
  width: 52%;
  height: 52%;
  stroke: #fff;
}

.tile-46 {
  width: 46px;
  height: 46px;
  border-radius: 15px;
}

.tile-52 {
  width: 52px;
  height: 52px;
}

.tile-56 {
  width: 56px;
  height: 56px;
  border-radius: 18px;
}

.tile-70 {
  width: 70px;
  height: 70px;
  border-radius: var(--r-md);
}

.tile-78 {
  width: 78px;
  height: 78px;
  border-radius: 24px;
}

.t-violet {
  background: linear-gradient(150deg, var(--violet-light), var(--violet));
  box-shadow: 0 12px 26px rgba(124, 92, 255, 0.3);
}

.t-coral {
  background: linear-gradient(150deg, #ff8a6b, #ff5a36);
  box-shadow: 0 12px 26px rgba(255, 90, 54, 0.3);
}

.t-sky {
  background: linear-gradient(150deg, var(--sky-light), var(--sky));
  box-shadow: 0 12px 26px rgba(56, 189, 248, 0.32);
}

.t-green {
  background: linear-gradient(150deg, var(--green-light), var(--green));
  box-shadow: 0 12px 26px rgba(34, 197, 94, 0.28);
}

.t-amber {
  background: linear-gradient(150deg, var(--amber-light), var(--amber));
  box-shadow: 0 12px 26px rgba(255, 197, 61, 0.32);
}

.t-plum {
  background: linear-gradient(150deg, #c4b5fd, #8b7cf6);
  box-shadow: 0 10px 22px rgba(139, 124, 246, 0.28);
}

.t-tangerine {
  background: linear-gradient(150deg, #fdba74, #fb8c3c);
  box-shadow: 0 10px 22px rgba(251, 140, 60, 0.28);
}

.t-slate {
  background: linear-gradient(150deg, #94a3b8, #475569);
  box-shadow: 0 10px 22px rgba(71, 85, 105, 0.24);
}

/* Flat tinted square — the numbered problem cards and the use-case list. */
.chip {
  width: 44px;
  height: 44px;
  border-radius: var(--r-sm);
  display: grid;
  place-items: center;
  font-weight: 800;
  font-size: 15px;
  flex: none;
}

.chip-40 {
  width: 40px;
  height: 40px;
  border-radius: 13px;
}

.c-coral {
  background: var(--coral-tint);
  color: var(--coral-text);
}

.c-violet {
  background: var(--violet-tint);
  color: var(--violet-text);
}

.c-amber {
  background: var(--amber-tint);
  color: var(--amber-text);
}

.c-sky {
  background: var(--sky-tint);
  color: var(--sky-text);
}

.c-green {
  background: var(--green-tint);
  color: var(--green-text);
}

.c-grey {
  background: #f0f1f3;
  color: var(--ink-2);
}

/* ══ hero ══════════════════════════════════════════════════════════════ */

.hero {
  position: relative;
  padding: 64px 20px 90px;
  text-align: center;
}

.hero-dots,
.hero-wash {
  position: absolute;
  pointer-events: none;
}

.hero-dots {
  inset: 0;
  background-image: radial-gradient(rgba(11, 11, 15, 0.07) 1px, transparent 1px);
  background-size: 24px 24px;
  -webkit-mask-image: radial-gradient(72% 58% at 50% 34%, #000, transparent 76%);
  mask-image: radial-gradient(72% 58% at 50% 34%, #000, transparent 76%);
}

.hero-wash {
  left: 50%;
  top: -90px;
  transform: translateX(-50%);
  width: 132vw;
  height: 760px;
  background:
    radial-gradient(circle at 30% 32%, rgba(124, 92, 255, 0.2), transparent 46%),
    radial-gradient(circle at 72% 28%, rgba(255, 107, 74, 0.18), transparent 46%),
    radial-gradient(circle at 52% 62%, rgba(56, 189, 248, 0.12), transparent 48%);
  -webkit-mask-image: radial-gradient(58% 52% at 50% 42%, #000, transparent 78%);
  mask-image: radial-gradient(58% 52% at 50% 42%, #000, transparent 78%);
}

.hero-inner {
  position: relative;
  z-index: 2;
  max-width: 860px;
  margin: 0 auto;
}

.hero h1 {
  /* The floor is 30px, not 36px. At 36px a 320px phone left about 264px for
     the rotating phrase, which is narrower than the phrase itself — so the
     headline ran off the right edge rather than fitting. The vw coefficient
     is unchanged, so nothing above ~555px moves. */
  font-size: clamp(30px, 5.4vw, 74px);
  line-height: 0.98;
  margin: 22px auto 0;
  max-width: 740px;
}

.hero-sub {
  font-size: clamp(16px, 1.4vw, 19px);
  line-height: 1.6;
  color: var(--ink-3);
  margin: 22px auto 0;
  max-width: 600px;
}

.hero-sub strong {
  color: var(--ink);
  font-weight: 600;
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  margin-top: 26px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-pill);
  padding: 7px 15px;
  font-size: 13.5px;
  font-weight: 500;
  color: var(--ink-2);
  box-shadow: var(--lift-1);
}

.badge-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--green);
  flex: none;
}

.cta-row {
  display: flex;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 32px;
}

.trust-row {
  display: flex;
  gap: 20px;
  justify-content: center;
  flex-wrap: wrap;
  margin-top: 22px;
  font-size: 14px;
  color: var(--ink-3);
  font-weight: 500;
}

/* ── floating cards ──────────────────────────────────────────────────
   Positioned from the centre so they track the hero's optical middle at
   any width, and clamped so they never leave the viewport. */
.floats {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 1;
}

.float {
  position: absolute;
  animation: floaty 9s ease-in-out infinite;
}

@keyframes floaty {
  0%,
  100% {
    transform: translateY(0) var(--tilt, );
  }
  50% {
    transform: translateY(-10px) var(--tilt, );
  }
}

@media (prefers-reduced-motion: reduce) {
  .float {
    animation: none;
  }
  html {
    scroll-behavior: auto;
  }
}

.float-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 16px;
  box-shadow: 0 12px 30px rgba(16, 24, 40, 0.1);
  padding: 11px 13px;
  display: flex;
  align-items: center;
  gap: 10px;
  text-align: left;
}

.float-card-icon {
  flex: none;
  width: 30px;
  height: 30px;
  border-radius: 10px;
  background: var(--canvas);
  display: grid;
  place-items: center;
}

.float-title {
  display: block;
  font-size: 13px;
  font-weight: 700;
  color: var(--ink);
}

.float-sub {
  display: block;
  font-size: 11px;
  color: var(--ink-3);
  font-weight: 500;
}

.float-tile {
  aspect-ratio: 1;
  border-radius: var(--r-md);
  display: grid;
  place-items: center;
}

.float-tile svg {
  width: 58%;
  height: 58%;
  stroke: #fff;
}

/* Positions. Each sits a fixed distance outside the 1080px content column. */
.f-l1 {
  left: max(8px, calc(50% - 34vw - 150px));
  top: 44px;
  width: clamp(104px, 11vw, 150px);
  --tilt: rotate(-5deg);
}

.f-l2 {
  left: max(8px, calc(50% - 34vw - 126px));
  top: clamp(230px, 26vw, 300px);
  width: clamp(84px, 9vw, 116px);
  animation-duration: 11s;
}

.f-l3 {
  left: max(8px, calc(50% - 34vw - 206px));
  top: 498px;
  width: clamp(160px, 18vw, 196px);
  animation-duration: 13s;
}

.f-r1 {
  right: max(8px, calc(50% - 34vw - 138px));
  top: 58px;
  width: clamp(88px, 9.5vw, 128px);
  animation-duration: 8s;
}

.f-r2 {
  right: max(8px, calc(50% - 34vw + 4px));
  top: clamp(170px, 19vw, 196px);
  width: clamp(62px, 7vw, 88px);
  animation-duration: 12s;
}

.f-r3 {
  right: max(8px, calc(50% - 34vw - 150px));
  top: clamp(250px, 28vw, 306px);
  width: clamp(104px, 11vw, 150px);
  --tilt: rotate(5deg);
  animation-duration: 10s;
}

.f-r4 {
  right: max(8px, calc(50% - 34vw - 196px));
  top: 512px;
  width: clamp(150px, 17vw, 186px);
  animation-duration: 14s;
}

/* The outermost pair needs room the page does not have below ~1120px, and
   nothing floats beside a phone-width hero at all. */
@media (max-width: 1120px) {
  .f-wide {
    display: none;
  }
}

@media (max-width: 760px) {
  .floats {
    display: none;
  }
}

/* ══ screenshot frames ═════════════════════════════════════════════════ */

.shot {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 32px;
  padding: 14px;
  box-shadow: var(--lift-3);
}

.shot img {
  border-radius: var(--r-md);
  width: 100%;
}

.shot-hero {
  max-width: 1080px;
  margin: 64px auto 0;
}

.shot-inset {
  border-radius: 20px;
  overflow: hidden;
  background: var(--well);
}

.shot-inset img {
  width: 100%;
  display: block;
}

/* ══ stats ═════════════════════════════════════════════════════════════ */

.stat {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: 24px;
  text-align: left;
}

.stat-n {
  font-size: 34px;
  font-weight: 800;
  letter-spacing: -0.05em;
  line-height: 1.1;
}

.stat-l {
  font-size: 14px;
  color: var(--ink-3);
  margin-top: 2px;
  font-weight: 500;
}

/* ══ how it works ══════════════════════════════════════════════════════ */

.step {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  padding: 26px;
  display: flex;
  flex-direction: column;
  text-align: left;
}

.step-art {
  aspect-ratio: 4 / 3;
  border-radius: 20px;
  background: var(--well);
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}

/* `contain`, not `cover`.
   These three frames hold whatever shot best explains the step, and those
   are not all the same shape — a phone form is portrait, a dashboard strip
   is wide. `cover` filled the frame by cropping, which on a portrait shot
   meant showing its top third and calling it an illustration. Contained and
   centred on the well, each one is whole and undistorted; the frame stays a
   consistent 4:3 so the three cards still line up. */
.step-art img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  object-position: center;
  padding: 14px;
  border-radius: 14px;
}

.step-head {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 22px;
}

.step-n {
  width: 26px;
  height: 26px;
  border-radius: var(--r-xs);
  background: var(--ink);
  color: #fff;
  display: grid;
  place-items: center;
  font-size: 13px;
  font-weight: 700;
  flex: none;
}

.step h3 {
  font-size: 21px;
  font-weight: 700;
}

.step p,
.feature p {
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--ink-3);
  margin: 10px 0 0;
}

/* WhatsApp bubbles in step 2. */
.bubbles {
  display: grid;
  gap: 8px;
  width: 100%;
  padding: 18px;
}

.bubble {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 14px 14px 14px 4px;
  padding: 11px 13px;
  font-size: 13px;
  line-height: 1.45;
  box-shadow: 0 6px 16px rgba(16, 24, 40, 0.06);
  text-align: left;
}

/* ══ features ══════════════════════════════════════════════════════════ */

.feature {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  padding: 28px;
  text-align: left;
}

.feature h3 {
  font-size: 19px;
  margin: 16px 0 0;
  font-weight: 700;
}

.feature-wide {
  display: grid;
  gap: 28px;
  align-items: center;
  padding: 32px;
}

/* Prose first, then the screenshot with the larger share — an equal split
   left the shot around 310px, where the product UI inside it stops being
   readable and turns into texture. */
@media (min-width: 700px) {
  .feature-wide {
    grid-template-columns: 1fr 1.35fr;
  }
}

.feature-wide h3 {
  font-size: 23px;
  margin: 18px 0 0;
}

.feature-wide strong {
  color: var(--ink);
  font-weight: 600;
}

.card-dark .feature-wide-art {
  border: 1px dashed rgba(255, 255, 255, 0.18);
}

/* ══ use cases ═════════════════════════════════════════════════════════ */

.use {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-md);
  padding: 22px 24px;
  display: flex;
  gap: 14px;
  align-items: flex-start;
  text-align: left;
}

.use-t {
  display: block;
  font-weight: 700;
  font-size: 16.5px;
}

.use-s {
  display: block;
  font-size: 14.5px;
  color: var(--ink-3);
  margin-top: 3px;
}

/* ══ pricing ═══════════════════════════════════════════════════════════
   Ported from the shadcn "Pricing" block. That component is React + Next +
   framer-motion + radix + canvas-confetti + number-flow; this site is static
   HTML served by nginx with no build step, so the *design* is reproduced
   here rather than the dependency tree — same perspective row, same switch,
   same rolling digits, same confetti, same Popular star.

   One reconciliation: the source is built for exactly three plans and keys
   its tilt off indices 0/1/2. Antri Online sells four, and the plan set is
   real product data wired to feature gating — so instead of dropping a tier
   to fit the layout, the tilt keys off each card's side of the popular one.
   Cards left of it hinge on their right edge, cards right of it on their
   left, and the effect generalises to any count. */

/* ── billing switch ─────────────────────────────────────────────────── */

.billing-row {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin-top: 30px;
}

.switch {
  position: relative;
  display: inline-flex;
  flex: none;
  cursor: pointer;
}

/* The real control, kept in the layout (not display:none) so it stays
   focusable and announced, but painted by the track beneath it. */
.switch input {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: pointer;
}

.switch-track {
  display: inline-flex;
  align-items: center;
  width: 44px;
  height: 24px;
  padding: 2px;
  border-radius: var(--r-pill);
  background: #d3d6dc;
  transition: background-color 0.2s;
}

.switch input:checked + .switch-track {
  background: var(--coral-btn);
}

.switch input:focus-visible + .switch-track {
  outline: 2px solid var(--coral-text);
  outline-offset: 2px;
}

.switch-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(16, 24, 40, 0.3);
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}

.switch input:checked + .switch-track .switch-thumb {
  transform: translateX(20px);
}

.billing-label {
  font-weight: 600;
  font-size: 15px;
}

.billing-save {
  color: var(--coral-text);
}

/* ── the perspective row ────────────────────────────────────────────── */

.plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 18px;
  margin-top: 40px;
  /* Stretch, not start: equal-height cards put every CTA on the same line,
     which is what makes a pricing row scannable. `margin-top:auto` on the
     rule above each button then absorbs the difference in feature counts. */
  align-items: stretch;
  text-align: center;
}

.plans > * {
  min-width: 0;
}

.plan {
  position: relative;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 18px;
  padding: 26px 22px;
  display: flex;
  flex-direction: column;
  text-align: center;
  z-index: 0;
  transition:
    transform 0.5s cubic-bezier(0.16, 1, 0.3, 1),
    box-shadow 0.3s,
    border-color 0.3s;
}

.plan-body {
  display: flex;
  flex-direction: column;
  flex: 1;
}

/* The popular card is the one the row is arranged around: it sits forward,
   full scale, above its neighbours. */
.plan.is-popular {
  z-index: 10;
  border: 2px solid var(--coral-btn);
  padding: 25px 21px;
  box-shadow: 0 18px 44px rgba(11, 11, 15, 0.1);
}

/* The recession only makes sense once the cards are actually in a row.
   Stacked on a phone it would just make them randomly smaller. */
@media (min-width: 940px) {
  .js .plans {
    /* A real perspective origin, so the hinge reads as depth rather than
       as a card that has been squashed. */
    perspective: 1400px;
    perspective-origin: 50% 40%;
  }

  .js .plan[data-tilt="left"] {
    transform-origin: right center;
    transform: translateX(11px) scale(0.95) rotateY(8deg);
  }

  .js .plan[data-tilt="right"] {
    transform-origin: left center;
    transform: translateX(-11px) scale(0.95) rotateY(-8deg);
  }

  .js .plan.is-popular {
    transform: translateY(-18px);
  }

  /* Hovering flattens a card back to face the reader — the depth is
     decoration, and it should get out of the way of someone reading. */
  @media (hover: hover) {
    .js .plan[data-tilt="left"]:hover,
    .js .plan[data-tilt="right"]:hover {
      transform: translateX(0) scale(1) rotateY(0deg);
      z-index: 20;
      box-shadow: var(--lift-3);
    }

    .js .plan.is-popular:hover {
      transform: translateY(-24px);
      box-shadow: 0 26px 56px rgba(11, 11, 15, 0.16);
    }
  }
}

/* ── the Popular flag ───────────────────────────────────────────────── */

.plan-flag {
  position: absolute;
  top: -2px;
  right: -2px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  background: var(--coral-btn);
  color: #fff;
  font-size: 12.5px;
  font-weight: 700;
  padding: 5px 11px;
  border-radius: 0 16px 0 14px;
}

.plan-flag svg {
  width: 14px;
  height: 14px;
  flex: none;
}

/* ── plan contents ──────────────────────────────────────────────────── */

.plan-name {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--ink-3);
}

.plan-price {
  margin-top: 20px;
  display: flex;
  align-items: baseline;
  justify-content: center;
  gap: 6px;
  flex-wrap: wrap;
}

.plan-amount {
  display: inline-flex;
  align-items: baseline;
  letter-spacing: -0.04em;
  font-weight: 800;
}

.plan-cur {
  font-size: 19px;
  color: var(--ink-3);
  font-weight: 700;
  margin-right: 2px;
}

.plan-n {
  font-size: 46px;
  line-height: 1;
  /* Rolling digits need a fixed advance width or the whole row jitters as
     the numbers change. */
  font-variant-numeric: tabular-nums;
  display: inline-flex;
  overflow: hidden;
}

.plan-unit {
  font-size: 26px;
}

.plan-period {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink-3);
  letter-spacing: 0.01em;
}

.plan-billed {
  font-size: 12.5px;
  color: var(--ink-3);
  margin-top: 6px;
}

.plan-list {
  margin: 20px 0 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 9px;
  text-align: left;
  font-size: 14.5px;
  color: var(--ink-2);
}

.plan-list li {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}

.plan-list svg {
  width: 15px;
  height: 15px;
  flex: none;
  margin-top: 3px;
  color: var(--coral-text);
}

.plan-list strong {
  font-weight: 700;
  color: var(--ink);
}

.plan-rule {
  width: 100%;
  border: 0;
  border-top: 1px solid var(--hairline-2);
  margin: 18px 0 0;
  /* Pushes the CTA to the card's floor so every button in the row lines up,
     however many features sit above it. */
  margin-top: auto;
  padding-top: 0;
}

.plan-cta {
  display: block;
  margin-top: 18px;
  padding: 12px 16px;
  border-radius: 12px;
  border: 1px solid var(--hairline-2);
  background: var(--surface);
  color: var(--ink);
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.02em;
  transition:
    background-color 0.25s,
    color 0.25s,
    box-shadow 0.25s,
    border-color 0.25s;
}

.plan-cta:hover {
  background-color: var(--coral-btn);
  background-image: linear-gradient(to bottom, var(--coral-btn-top), var(--coral-btn-bottom));
  border-color: transparent;
  color: #fff;
  /* The source's ring-2 + ring-offset-1, as a two-stop shadow. */
  box-shadow:
    0 0 0 2px var(--surface),
    0 0 0 4px var(--coral-btn);
}

.plan-cta.is-primary {
  background-color: var(--coral-btn);
  background-image: linear-gradient(to bottom, var(--coral-btn-top), var(--coral-btn-bottom));
  border-color: transparent;
  color: #fff;
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16);
}

.plan-desc {
  font-size: 12.5px;
  color: var(--ink-3);
  margin-top: 16px;
  line-height: 1.5;
}

/* Rolling digit columns. Height is locked to one cell so the strip shows
   exactly one number at a time and the card never changes height mid-roll. */
.digit {
  display: inline-block;
  overflow: hidden;
  height: 1em;
  line-height: 1;
}

.digit-strip {
  display: flex;
  flex-direction: column;
  transition: transform 0.55s cubic-bezier(0.16, 1, 0.3, 1);
}

.digit-strip > span {
  height: 1em;
  line-height: 1;
}

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

/* ── confetti ───────────────────────────────────────────────────────── */

/* Fixed and click-through, so it never intercepts the click that spawned it
   or adds to the page's scroll height. */
#confetti {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
  z-index: 90;
}

.addons {
  margin-top: 16px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 28px;
  padding: 28px;
  text-align: left;
}

.addons h4 {
  font-size: 13px;
  font-weight: 700;
  color: var(--ink-3);
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.addon-t {
  font-weight: 700;
  font-size: 16px;
}

.addon-s {
  font-size: 14.5px;
  color: var(--ink-3);
  margin-top: 2px;
}

.addons-foot {
  margin-top: 20px;
  padding-top: 18px;
  border-top: 1px solid rgba(11, 11, 15, 0.07);
  font-size: 14.5px;
  color: var(--ink-2);
}

/* ══ FAQ ═══════════════════════════════════════════════════════════════ */

.faq {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  padding: 8px 30px;
}

.faq details {
  border-bottom: 1px solid rgba(11, 11, 15, 0.07);
  padding: 20px 0;
}

.faq details:last-child {
  border-bottom: 0;
}

.faq summary {
  display: flex;
  justify-content: space-between;
  gap: 20px;
  cursor: pointer;
  font-size: 18px;
  font-weight: 700;
  list-style: none;
}

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

.faq .plus {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  background: var(--coral-tint);
  color: var(--coral-text);
  transition: transform 0.2s;
}

.faq details[open] .plus {
  transform: rotate(45deg);
}

.faq p {
  color: var(--ink-3);
  font-size: 16px;
  line-height: 1.65;
  margin: 12px 0 0;
  max-width: 700px;
}

/* ══ closing CTA ═══════════════════════════════════════════════════════ */

.close-cta {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-xl);
  padding: 76px 40px;
  text-align: center;
  box-shadow: var(--lift-3);
}

.close-cta h2 {
  font-size: clamp(30px, 4.4vw, 54px);
  max-width: 640px;
  margin: 26px auto 0;
}

/* ══ footer ════════════════════════════════════════════════════════════ */

.footer {
  padding: 20px 20px 0;
}

.footer-inner {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-xl);
  padding: 56px 44px 0;
  overflow: hidden;
}

.footer-cols {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  gap: 32px;
}

.footer-col {
  display: grid;
  gap: 9px;
  align-content: start;
  font-size: 15px;
}

.footer-col-h {
  font-weight: 700;
  color: var(--ink);
}

.footer-col a {
  color: var(--ink-3);
}

.footer-col a:hover {
  color: var(--ink);
}

.footer-blurb {
  font-size: 15px;
  color: var(--ink-3);
  margin: 14px 0 0;
  max-width: 260px;
  line-height: 1.55;
}

.footer-legal {
  font-size: 13.5px;
  color: var(--ink-3);
  margin-top: 36px;
}

/* Oversized wordmark, faded out at the bottom. Decorative: it repeats the
   brand name already in the footer link, so it is hidden from the a11y tree
   rather than read out a second time. */
.wordmark {
  margin: 12px -20px -14px;
  font-size: clamp(64px, 15vw, 210px);
  font-weight: 900;
  letter-spacing: -0.06em;
  line-height: 0.82;
  color: var(--coral);
  white-space: nowrap;
  -webkit-mask-image: linear-gradient(180deg, #000 30%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 30%, transparent 100%);
  user-select: none;
}

/* ══ responsive ════════════════════════════════════════════════════════ */

/* Once the bar wraps to two rows, a 999px radius stops reading as a pill and
   starts reading as a lozenge with a bite out of it. Below this width it
   becomes an ordinary rounded card, and the nav moves under the buttons. */
@media (max-width: 900px) {
  .header-bar {
    padding: 12px 14px;
    gap: 12px;
    border-radius: 26px;
    width: 100%;
    max-width: 520px;
  }
  .header-nav {
    order: 3;
    width: 100%;
    justify-content: center;
    gap: 18px;
    padding-top: 2px;
    border-top: 1px solid var(--hairline);
  }
  .brand {
    flex: 1;
  }
}

@media (max-width: 640px) {
  section {
    padding: 48px 16px;
  }
  .page {
    padding: 12px;
  }
  .hero {
    padding: 40px 16px 56px;
  }
  .card,
  .feature,
  .step,
  .plan,
  .addons {
    padding: 22px;
  }
  .feature-wide {
    padding: 22px;
    gap: 20px;
  }
  .card-dark {
    padding: 22px;
  }
  .faq {
    padding: 4px 20px;
  }
  .close-cta {
    padding: 48px 22px;
  }
  .footer-inner {
    padding: 36px 24px 0;
  }
  /* Two rows on a phone, not three.
     `.brand { flex: 1 }` from the 900px block makes the wordmark claim the
     whole first row, which pushes both buttons onto a row of their own and
     the nav onto a third — a 149px header on an 844px screen, against 69px
     on desktop. Here the brand takes only its own width so the buttons sit
     beside it, and the signup button drops the word "Gratis" to make that
     fit. Both actions stay reachable; hiding "Masuk" would have cost every
     returning owner their way back in. */
  .brand {
    flex: none;
    font-size: 17px;
  }
  .header-bar {
    gap: 8px 10px;
    padding: 10px 12px;
    justify-content: space-between;
  }
  .header-cta {
    gap: 6px;
  }
  .cta-word {
    display: none;
  }
  .header-nav {
    font-size: 14px;
    gap: 14px;
  }
  /* Three stacked lines with a 20px gutter read as three separate claims.
     Tightened, they read as one row of fine print, which is what they are. */
  .trust-row {
    gap: 6px 14px;
    font-size: 13px;
    margin-top: 18px;
  }
  .shot {
    padding: 8px;
    border-radius: 24px;
  }
}


/* ══ motion ════════════════════════════════════════════════════════════
   Three kinds, and nothing else:

     1. an entrance for the hero, on load
     2. a reveal for everything below the fold, on scroll
     3. hover feedback on the things you can click

   All of it is opt-in through a class that JavaScript adds. If the script
   never runs, the page renders in its final state rather than staying
   invisible — a reveal animation that hides content is a content bug the
   moment anything goes wrong. The `.js` gate on <html> is what arms it.

   Everything is transform + opacity so it stays on the compositor; nothing
   animates a property that forces layout. `prefers-reduced-motion` turns the
   whole thing off at the bottom of this block. */

/* ── 2. scroll reveal ─────────────────────────────────────────────── */

.js .reveal {
  opacity: 0;
  transform: translateY(18px);
}

.js .reveal.is-in {
  opacity: 1;
  transform: none;
  transition:
    opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1),
    transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
  /* Children of a revealed grid come in one after another. --i is set by
     the script from each child's index. */
  transition-delay: calc(var(--i, 0) * 60ms);
}

/* ── 1. hero entrance ─────────────────────────────────────────────── */

.js .hero-inner > *,
.js .shot-hero {
  animation: rise 0.7s cubic-bezier(0.22, 1, 0.36, 1) backwards;
}

.js .hero-inner > *:nth-child(1) { animation-delay: 0.05s; }
.js .hero-inner > *:nth-child(2) { animation-delay: 0.13s; }
.js .hero-inner > *:nth-child(3) { animation-delay: 0.21s; }
.js .hero-inner > *:nth-child(4) { animation-delay: 0.29s; }
.js .hero-inner > *:nth-child(5) { animation-delay: 0.37s; }
.js .hero-inner > *:nth-child(6) { animation-delay: 0.45s; }
.js .shot-hero { animation-delay: 0.5s; }

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(22px);
  }
}

/* The floating cards fade in after the copy has landed, so they read as
   decoration arriving around the message rather than competing with it. */
.js .floats {
  animation: fade 1.1s ease-out 0.55s backwards;
}

@keyframes fade {
  from {
    opacity: 0;
  }
}

/* The hero wash drifts very slowly. Long enough that it is never "an
   animation" you notice — only that the page is not quite static. */
.js .hero-wash {
  animation: drift 26s ease-in-out infinite alternate;
}

@keyframes drift {
  from {
    transform: translateX(-50%) translateY(0) scale(1);
  }
  to {
    transform: translateX(-50%) translateY(-26px) scale(1.07);
  }
}

/* ── 3. hover feedback ────────────────────────────────────────────── */

.card,
.feature,
.step,
.use,
.stat,
.plan {
  transition:
    transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
    box-shadow 0.22s cubic-bezier(0.22, 1, 0.36, 1),
    border-color 0.22s;
}

@media (hover: hover) {
  .card:hover,
  .feature:hover,
  .step:hover,
  .use:hover,
  .stat:hover {
    transform: translateY(-3px);
    border-color: rgba(11, 11, 15, 0.11);
    box-shadow: var(--lift-2);
  }

  /* The pricing cards lift a little further — they are the decision. */
  .plan:hover {
    transform: translateY(-5px);
    box-shadow: var(--lift-3);
  }

  .plan-featured:hover {
    box-shadow: 0 30px 60px rgba(11, 11, 15, 0.28);
  }

  /* Gradient tiles tilt toward the reader with their parent card. */
  .feature:hover .tile,
  .card:hover .chip,
  .use:hover .chip {
    transform: scale(1.06) rotate(-3deg);
  }
}

.tile,
.chip {
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}

/* Header links get an underline that grows from the left. */
.header-nav a,
.footer-col a {
  position: relative;
}

.header-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -4px;
  height: 2px;
  border-radius: 2px;
  background: var(--coral-btn);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}

.header-nav a:hover::after,
.header-nav a:focus-visible::after {
  transform: scaleX(1);
}

/* FAQ rows tint faintly on hover so the whole row reads as the hit target,
   not just the words. */
.faq summary {
  transition: color 0.18s;
}

.faq summary:hover {
  color: var(--coral-text);
}

.faq details[open] summary {
  color: var(--ink);
}

.faq .plus {
  transition:
    transform 0.25s cubic-bezier(0.22, 1, 0.36, 1),
    background-color 0.2s;
}

.faq summary:hover .plus {
  background: color-mix(in oklab, var(--coral-tint) 70%, var(--coral) 12%);
}

/* The answer slides open rather than snapping. `grid-template-rows` is the
   one way to transition to an unknown content height without measuring it. */
.faq details > p {
  display: grid;
  grid-template-rows: 1fr;
}

@supports (interpolate-size: allow-keywords) {
  .faq details::details-content {
    transition:
      height 0.3s cubic-bezier(0.22, 1, 0.36, 1),
      content-visibility 0.3s allow-discrete;
    height: 0;
    overflow: hidden;
  }
  .faq details[open]::details-content {
    height: auto;
  }
}

/* The billing toggle's price digits swap with a quick flip. */
.plan-n {
  display: inline-block;
  transition: transform 0.2s cubic-bezier(0.22, 1, 0.36, 1);
}

.plan-n.bump {
  animation: bump 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}

@keyframes bump {
  0% {
    transform: translateY(0);
    opacity: 1;
  }
  40% {
    transform: translateY(-7px);
    opacity: 0;
  }
  41% {
    transform: translateY(7px);
  }
  100% {
    transform: translateY(0);
    opacity: 1;
  }
}

/* The live dot on the hero badge breathes. */
.js .badge-dot {
  animation: pulse 2.4s ease-in-out infinite;
}

@keyframes pulse {
  0%,
  100% {
    box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.5);
  }
  70% {
    box-shadow: 0 0 0 6px rgba(34, 197, 94, 0);
  }
}

/* ── the opt-out ──────────────────────────────────────────────────── */

@media (prefers-reduced-motion: reduce) {
  .js .reveal,
  .js .reveal.is-in {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .js .hero-inner > *,
  .js .shot-hero,
  .js .floats,
  .js .hero-wash,
  .js .badge-dot,
  .float,
  .plan-n.bump {
    animation: none;
  }

  .js .hero-wash {
    transform: translateX(-50%);
  }

  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
  }
}


/* ══ hero mark ═════════════════════════════════════════════════════════
   The animated logo, on a white plate.

   White specifically, not `--surface`: the artwork was drawn against white
   and its own strokes are light in places, so letting it sit on the page's
   tinted canvas eats parts of the mark. The plate is the same rounded-square
   silhouette the gradient tile had, so the hero's composition is unchanged.

   A fixed square box with a min-height reserves the space before the
   animation loads — otherwise the whole hero jumps down the moment it does. */

.hero-mark {
  width: clamp(78px, 9vw, 104px);
  aspect-ratio: 1;
  margin: 0 auto;
  border-radius: 24px;
  background: #fff;
  border: 1px solid var(--hairline);
  box-shadow: 0 18px 40px rgba(16, 24, 40, 0.12);
  display: grid;
  place-items: center;
  overflow: hidden;
  padding: 8px;
}

.hero-mark > div {
  width: 100%;
  height: 100%;
}

.hero-mark svg {
  display: block;
  width: 100% !important;
  height: 100% !important;
}

/* ══ hero: rotating word ═══════════════════════════════════════════════
   One word in the headline cycles through the trades this is sold to. The
   track is aria-hidden and a single stable word carries the accessible
   name — a live-updating word inside an <h1> makes a screen reader
   re-announce the heading every few seconds. */

/* The rotating word and the comma that follows it must break as one unit —
   otherwise a long word pushes the comma onto the next line by itself. */
.nowrap {
  white-space: nowrap;
}

.rotator {
  position: relative;
  display: inline-grid;
  vertical-align: bottom;
  /* Animated by script to the incoming word's measured width, so the rest
     of the sentence never jumps as the words change length. */
  transition: width 0.45s cubic-bezier(0.22, 1, 0.36, 1);
  /* Safety net for that inline width. The script measures the phrase with
     `nowrap` and writes the result straight onto the element, which on a
     narrow screen can be wider than the screen itself — the phrase then
     spills off the edge instead of wrapping, because a locked box has no
     reason to break. This cap means the lock can never exceed the line it
     sits on, whatever phrase someone adds to `data-words` later. */
  max-width: 100%;
}

.rotator-view {
  position: relative;
  display: block;
  overflow: hidden;
  /* Room for the descenders in "kedaimu" / "barbershopmu" — `overflow:
     hidden` on a tight line-height would shave them off. */
  padding-bottom: 0.14em;
  margin-bottom: -0.14em;
}

.rotator-word {
  display: block;
  white-space: nowrap;
  color: var(--coral-text);
}

/* The other half of the cap above: with the box limited to the line width, a
   phrase that still does not fit has to be allowed to break, or its glyphs
   spill out of a box that itself fits. Phones only — above this width every
   phrase fits on one line and `nowrap` keeps the swap clean. */
@media (max-width: 560px) {
  .rotator-word {
    white-space: normal;
  }
}

/* Only the current word occupies flow; the rest stack behind it. */
.js .rotator-word:not(.is-current) {
  position: absolute;
  inset: 0;
}

.js .rotator-word {
  transition:
    transform 0.45s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.3s ease;
}

.js .rotator-word.is-out {
  transform: translateY(-102%);
  opacity: 0;
}

.js .rotator-word.is-in {
  transform: translateY(102%);
  opacity: 0;
  transition: none;
}

/* ══ free tier ═════════════════════════════════════════════════════════ */

.free {
  margin-top: 22px;
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: 24px;
  padding: 32px;
  display: grid;
  grid-template-columns: 1fr;
  gap: 26px;
  text-align: left;
  box-shadow: var(--lift-1);
}

@media (min-width: 820px) {
  .free {
    grid-template-columns: 1fr 1.1fr;
    gap: 44px;
    align-items: center;
  }
}

.free-head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.free-name {
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.06em;
  color: var(--ink-3);
}

.free-badge {
  font-size: 12px;
  font-weight: 700;
  padding: 4px 10px;
  border-radius: var(--r-pill);
  background: var(--green-tint);
  color: var(--green-text);
}

.free-price {
  margin-top: 14px;
  display: flex;
  align-items: baseline;
  gap: 6px;
  letter-spacing: -0.04em;
}

.free-n {
  font-size: 54px;
  font-weight: 800;
  line-height: 1;
}

.free-desc {
  margin-top: 12px;
  font-size: 15.5px;
  line-height: 1.6;
  color: var(--ink-3);
  max-width: 44ch;
}

.free-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 9px 20px;
  font-size: 14.5px;
  color: var(--ink-2);
}

.free-list li {
  display: flex;
  align-items: flex-start;
  gap: 9px;
}

.free-list svg {
  width: 15px;
  height: 15px;
  flex: none;
  margin-top: 3px;
  color: var(--green-text);
}

.free-cta {
  margin-top: 22px;
  width: 100%;
}

@media (min-width: 560px) {
  .free-cta {
    width: auto;
  }
}

/* ══ footer wordmark ═══════════════════════════════════════════════════
   Letters arrive one at a time as the footer scrolls up: "A" alone at first,
   then the rest, ending on "AntriOnline". Driven by scroll position rather
   than a timer, so it tracks the reader instead of racing them. */

.wordmark span {
  display: inline-block;
}

.js .wordmark span {
  opacity: 0;
  transform: translateY(0.14em);
  transition:
    opacity 0.34s ease,
    transform 0.34s cubic-bezier(0.22, 1, 0.36, 1);
}

.js .wordmark span.is-lit {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .js .wordmark span,
  .js .rotator-word {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .rotator {
    transition: none;
  }
}

/* ══ sign-in gate (masuk.html) ═════════════════════════════════════════ */

.gate {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: 32px 20px;
}

.gate-card {
  background: var(--surface);
  border: 1px solid var(--hairline);
  border-radius: var(--r-lg);
  padding: 40px;
  width: 100%;
  max-width: 440px;
  box-shadow: var(--lift-3);
  text-align: center;
}

.gate-card h1 {
  font-size: 30px;
  margin: 20px 0 0;
}

.gate-card p {
  color: var(--ink-3);
  margin: 12px 0 0;
  line-height: 1.6;
}

.field {
  text-align: left;
  margin-top: 24px;
}

.field label {
  display: block;
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 7px;
}

.input-group {
  display: flex;
  align-items: stretch;
  border: 1px solid var(--hairline-2);
  border-radius: var(--r-sm);
  background: var(--surface);
  overflow: hidden;
  transition: border-color 0.15s;
}

.input-group:focus-within {
  border-color: var(--coral);
}

.input-group input {
  flex: 1;
  min-width: 0;
  border: 0;
  outline: 0;
  padding: 14px 14px;
  font-family: inherit;
  font-size: 16px;
  color: var(--ink);
  background: transparent;
}

.input-group input::placeholder {
  color: var(--ink-faint);
}

.input-suffix {
  display: grid;
  place-items: center;
  padding: 0 14px;
  background: var(--canvas);
  border-left: 1px solid var(--hairline);
  font-size: 15px;
  color: var(--ink-3);
  font-weight: 500;
  white-space: nowrap;
}

.gate-note {
  font-size: 14px;
  color: var(--ink-3);
  margin-top: 20px;
}

.gate-err {
  margin-top: 14px;
  background: var(--coral-tint);
  color: #9b2f14;
  border-radius: 12px;
  padding: 11px 14px;
  font-size: 14.5px;
  text-align: left;
}

.gate-or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 22px 0;
}

.gate-or::before,
.gate-or::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--hairline-2);
}

.gate-or span {
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: 600;
}

.gbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

/* ══ veiled price ══════════════════════════════════════════════════════
   During early access the numbers are blurred rather than removed.

   Deleting them would leave four identical "Gratis" cards with nothing to
   tell them apart, and would make the eventual switch to paid a surprise.
   Blurred, the shape of a price is still there — the reader can see these
   tiers will cost something, just not yet and not how much.

   The blur is decoration only: `aria-hidden` keeps the unreadable figure out
   of the accessibility tree and "Gratis" is the real text, so a screen reader
   hears the truth rather than a number nobody is being charged. */

.plan-price.is-veiled {
  position: relative;
  justify-content: center;
  align-items: center;
  min-height: 58px;
}

.plan-veil {
  display: inline-flex;
  align-items: baseline;
  gap: 4px;
  filter: blur(8px);
  opacity: 0.3;
  user-select: none;
  pointer-events: none;
}

.plan-free {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 34px;
  font-weight: 800;
  letter-spacing: -0.04em;
  color: var(--green-text);
}

/* On the near-black featured card the light green step is the readable one. */
.plan-featured .plan-free {
  color: #6ee7a8;
}

.plan-featured .plan-veil {
  opacity: 0.42;
}

/* ══ kinetic grid ══════════════════════════════════════════════════════
   Canvas layer for the hero's interactive mesh. Sits above the static dot
   texture and the colour wash, below the copy.

   `pointer-events: none` on the canvas but not the host: the host needs
   clicks to spawn ripples, while the canvas itself must never intercept a
   click meant for the CTA sitting on top of it. */

#kinetic-grid {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  /* Fades out before it reaches the headline, so the mesh never competes
     with the words for attention. */
  -webkit-mask-image: radial-gradient(78% 62% at 50% 40%, transparent 26%, #000 72%);
  mask-image: radial-gradient(78% 62% at 50% 40%, transparent 26%, #000 72%);
}

#kinetic-grid canvas {
  display: block;
  pointer-events: none;
}

/* Everything in the hero that is CONTENT sits above the mesh.
   `.hero-inner` already declared z-index 2; the screenshot frame and the
   stat row did not, so they stacked at auto and the grid — z-index 1 — was
   painting its lines straight across them. A background has to stay behind
   all of its foreground, not just the part that happened to be positioned. */
.hero > .shot-hero,
.hero > .wrap {
  position: relative;
  z-index: 2;
}

/* ══ price add-ons ═════════════════════════════════════════════════════
   The optional extras under the plan row: WhatsApp message packs, a call
   screen, a dedicated number, an on-premise licence.

   Their prices wear the same `.plan-veil` blur as the plans above rather than
   being deleted. They are real figures and belong in the markup for the day
   pricing goes live, but nothing on this page is legibly priced during early
   access, so a reader who sees a crisp "Rp 50rb" here after a blurred plan
   price would read it as the one thing they are actually being charged for. */

.addons {
  max-width: 760px;
  margin: 30px auto 0;
  padding: 26px 28px;
  text-align: left;
  border: 1px solid var(--hairline);
  border-radius: 20px;
  background: var(--well);
}

.addons-h {
  font-weight: 700;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin-bottom: 6px;
}

.addon-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.addon {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 11px 0;
  border-bottom: 1px solid var(--hairline);
}

.addon:last-child { border-bottom: 0; }

.addon-t {
  font-size: 15px;
  color: var(--ink-2);
}

/* The veiled figure keeps a fixed footprint so the four rows stay aligned
   with each other instead of ragging to whatever the blur happens to span. */
.addon-p {
  flex: none;
  display: inline-flex;
  align-items: baseline;
  min-width: 168px;
  justify-content: flex-end;
}

.addon-p .plan-amount { font-size: 17px; }
.addon-p .plan-n      { font-size: 17px; }
.addon-p .plan-period { font-size: 12px; }

.partner {
  margin: 18px 0 0;
  font-size: 14px;
  line-height: 1.65;
  color: var(--ink-3);
}

.partner a {
  color: var(--coral-text);
  font-weight: 600;
  text-decoration: none;
}

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

/* The pricing section's badge is centred in a centred column; the hero's sits
   in a left-aligned stack, so the centring cannot live on `.badge` itself. */
.badge-center { margin: 22px auto 0; }

@media (max-width: 520px) {
  .addons { padding: 20px 18px; }
  .addon  { flex-direction: column; align-items: flex-start; gap: 2px; }
  .addon-p { min-width: 0; justify-content: flex-start; }
}

/* ══ mobile menu ═══════════════════════════════════════════════════════
   Above 700px nothing here applies: `.menu-wrap` is `display: contents`, so
   the nav and the buttons remain direct flex children of `.header-bar` and
   the desktop bar is byte-for-byte the layout it was before. Below 700px the
   wrapper turns into a dropdown panel and the bar keeps only the wordmark
   and the toggle.

   Why a panel at all: on a phone the four links plus two buttons could not
   fit beside the wordmark, so they wrapped onto two extra rows and the header
   grew to 149px on an 844px screen. Folded away, the bar is one row again and
   the hero starts where it should. */

.menu-wrap {
  display: contents;
}

.menu-btn {
  display: none;
  width: 42px;
  height: 42px;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--hairline);
  border-radius: 14px;
  background: var(--surface);
  color: var(--ink);
  padding: 0;
}

.menu-ico {
  display: block;
  width: 18px;
  height: 12px;
  position: relative;
}

.menu-ico span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: currentColor;
  transition:
    transform 0.26s cubic-bezier(0.22, 1, 0.36, 1),
    opacity 0.16s ease;
}

.menu-ico span:nth-child(1) { top: 0; }
.menu-ico span:nth-child(2) { top: 5px; }
.menu-ico span:nth-child(3) { top: 10px; }

/* Bars fold into a cross on open, so the control reads as "close" without a
   second icon or a label swap. */
.is-open .menu-ico span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.is-open .menu-ico span:nth-child(2) { opacity: 0; }
.is-open .menu-ico span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

@media (max-width: 700px) {
  .menu-btn {
    display: inline-flex;
  }

  .header-bar {
    justify-content: space-between;
    flex-wrap: nowrap;
    gap: 10px;
  }

  .menu-wrap {
    display: none;
    position: absolute;
    top: calc(100% + 8px);
    left: 0;
    right: 0;
    flex-direction: column;
    padding: 10px;
    background: var(--surface);
    border: 1px solid var(--hairline);
    border-radius: 24px;
    box-shadow: var(--lift-3);
  }

  .header.is-open .menu-wrap {
    display: flex;
  }

  /* The 900px block gives the nav `order: 3` and a top border for the
     wrapped-row layout; inside the panel it is a plain stacked list. */
  .menu-wrap .header-nav {
    order: 0;
    flex-direction: column;
    width: 100%;
    gap: 0;
    border-top: 0;
    padding-top: 0;
    font-size: 16px;
  }

  .menu-wrap .header-nav a {
    display: block;
    padding: 13px 14px;
    border-radius: 14px;
    color: var(--ink);
  }

  .menu-wrap .header-nav a:active {
    background: var(--well);
  }

  .menu-wrap .header-cta {
    flex-direction: column;
    width: 100%;
    gap: 8px;
    margin-top: 8px;
    padding-top: 12px;
    border-top: 1px solid var(--hairline);
  }

  .menu-wrap .header-cta .btn {
    width: 100%;
    justify-content: center;
    padding-top: 13px;
    padding-bottom: 13px;
  }

  /* Full label is affordable again: the buttons own a whole row in here. */
  .cta-word {
    display: inline;
  }
}

/* The panel is positioned against the bar, so the bar is the containing
   block. `.header` itself is sticky and already establishes one, but the
   panel must line up with the pill, not the full-width header. */
.header-bar {
  position: relative;
}

@media (prefers-reduced-motion: reduce) {
  .menu-ico span {
    transition: none;
  }
}

/* The hero badge carries an all-caps label. Caps set at the badge's default
   tracking run together and read slower than the sentence-case label this
   pill was built for, so the hero's gets the eyebrow treatment instead. It is
   scoped to `.hero` on purpose: the pricing badge is still a sentence, and
   letter-spacing a sentence is the wrong end of the same rule. */
.hero .badge {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--ink);
}

/* The numbered chips are the page's hardest contrast case: 15px at weight 800
   on a pale tint of their own hue. Violet, amber, and sky clear AA by 0.01 to
   0.14, and coral landed at 4.4959 — under the 4.5 line by four thousandths,
   which is a fail however invisible it looks. Darkened just for this chip
   rather than by moving `--coral-text`, which also paints the rotating half
   of the headline and the partner link; those are large or on white and are
   already comfortable, so there is no reason to shift the brand hue for them. */
.chip.c-coral {
  color: #c53517;
}
