/* ---------- Pixel font ---------- */
@font-face {
  font-family: "ByteBounce";
  src: url("../font/ByteBounce.ttf") format("truetype");
  font-weight: 500;
  font-display: swap;
  /* ByteBounce's glyphs sit small inside its own em-box -- measured at
     ~53% of Arial's cap-height at the same font-size, which is what made
     every font-size value on the site look small. size-adjust scales the
     rendered glyphs back up to roughly match a normal font, so none of the
     font-size values elsewhere in this file need to change. */
  size-adjust: 140%;
}

/* ---------- Tokens ---------- */
:root {
  /* Professional 3-color scheme: trust blue (primary), sky-tinted
     neutrals, and a single warm accent for CTAs -- replaces the
     all-orange palette. Background shifted to a soft sky blue, which
     doubles as the hero's "matches the sky" backdrop. */
  --color-primary: #2563EB;
  --color-primary-dark: #1D4ED8;
  --color-secondary: #60A5FA;
  --color-accent: #F97316;
  --color-accent-dark: #EA580C;
  --color-background: #EFF6FF;
  --color-foreground: #0F172A;
  --color-card: #FFFFFF;
  --color-muted: #E0EDFB;
  --color-muted-foreground: #475569;
  --color-border: #BFDBFE;
  --color-destructive: #DC2626;
  --color-on-primary: #FFFFFF;
  --color-shadow: #D1D5DB;

  /* ByteBounce (purchased, self-hosted from /font) replaces Fredoka/Nunito
     everywhere -- unlike Press Start 2P it's normal-width and still reads
     fine at body size, so one pixel face covers both roles and none of the
     sizes below need the compensation a true bitmap font would need. It
     ships one weight (500); a bold/700 on top of it is the browser's
     synthetic bold. It also has no ₹ glyph, so the rupee sign in the
     pricing cards falls back to the next font in the stack. */
  --font-display: "ByteBounce", system-ui, sans-serif;
  --font-body: "ByteBounce", system-ui, sans-serif;

  --radius-lg: 24px;
  --radius-md: 16px;
  --shadow-clay: 8px 8px 0 var(--color-shadow);
  --shadow-clay-sm: 5px 5px 0 var(--color-shadow);

  --space-xs: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem);
  --space-sm: clamp(0.75rem, 0.6rem + 0.75vw, 1.125rem);
  --space-md: clamp(1.25rem, 1rem + 1.25vw, 2rem);
  --space-lg: clamp(2rem, 1.5rem + 2.5vw, 3.5rem);
  --space-xl: clamp(3rem, 2rem + 5vw, 6rem);

  --duration-fast: 150ms;
  --duration-normal: 250ms;
  --ease-clay: cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* Deliberately no prefers-color-scheme:dark override -- the brand is a
   single light, airy palette regardless of system theme. */

/* ---------- Reset ---------- */
* {
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  margin: 0;
  background: var(--color-background);
  color: var(--color-foreground);
  font-family: var(--font-body);
  font-size: clamp(1rem, 0.95rem + 0.2vw, 1.125rem);
  line-height: 1.6;
  overflow-x: hidden;
}

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

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

h1,
h2,
h3 {
  font-family: var(--font-display);
  line-height: 1.15;
  margin: 0 0 var(--space-xs);
}

p {
  margin: 0 0 var(--space-sm);
}

ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

button {
  font: inherit;
}

@media (prefers-reduced-motion: reduce) {

  *,
  *::before,
  *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

:focus-visible {
  outline: 3px solid var(--color-accent);
  outline-offset: 3px;
  border-radius: 6px;
}

.container {
  max-width: 1180px;
  margin: 0 auto;
  padding: 0 var(--space-md);
}

.grain-overlay {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 999;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- Buttons ---------- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5em;
  font-family: var(--font-display);
  font-weight: 600;
  border: 3px solid var(--color-shadow);
  border-radius: 999px;
  padding: 0.85em 1.6em;
  cursor: pointer;
  box-shadow: var(--shadow-clay-sm);
  transition: transform var(--duration-fast) var(--ease-clay), box-shadow var(--duration-fast) var(--ease-clay);
  min-height: 44px;
}

.btn:hover {
  transform: translate(-2px, -2px);
  box-shadow: 7px 7px 0 var(--color-shadow);
}

.btn:active {
  transform: translate(1px, 1px);
  box-shadow: 3px 3px 0 var(--color-shadow);
}

.btn-primary {
  background: var(--color-primary);
  color: var(--color-on-primary);
}

.btn-outline {
  background: var(--color-card);
  color: var(--color-foreground);
}

.btn-ghost {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}

.btn-ghost:hover {
  box-shadow: none;
  color: var(--color-accent);
}

.cta-arrow {
  display: inline-block;
  animation: cta-arrow-bounce 1.5s ease-in-out infinite;
}

.cta-bounce:hover .cta-arrow {
  animation-duration: 0.6s;
  transform: translateY(3px);
}

@keyframes cta-arrow-bounce {

  0%,
  100% {
    transform: translateY(0);
  }

  50% {
    transform: translateY(5px);
  }
}

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

.btn-lg {
  /* Horizontal padding trimmed from 2em: ByteBounce's size-adjust makes its
     glyphs noticeably wider than Fredoka's at the same font-size, and the
     original 2em pushed the two hero buttons just past the row's width,
     wrapping "Explore Our Services" onto its own line. */
  padding: 1em 1.5em;
  font-size: 1.05rem;
}

.btn-sm {
  padding: 0.6em 1.2em;
  font-size: 0.9rem;
}

.btn-block {
  width: 100%;
}

/* ---------- Header ---------- */
.site-header {
  position: sticky;
  top: 0;
  z-index: 100;
  /* Fully opaque, not translucent+blurred -- the blur let section
     headings partially show through as they scrolled past, reading as
     the text being glitchily cut off rather than cleanly passing
     behind a solid bar. */
  background: var(--color-background);
  border-bottom: 2px solid var(--color-border);
}

.header-inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-block: var(--space-xs);
}

.logo {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.4rem;
}

.logo {
  --klg-dot-color: var(--color-primary);
}

.main-nav {
  display: flex;
  gap: var(--space-md);
  font-weight: 700;
}

.main-nav a {
  padding: 0.5em 0;
  position: relative;
}

.main-nav a::after {
  content: "";
  position: absolute;
  left: 0;
  right: 100%;
  bottom: 0;
  height: 3px;
  background: var(--color-primary);
  transition: right var(--duration-normal) var(--ease-clay);
}

.main-nav a:hover::after {
  right: 0;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.nav-toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
}

.nav-toggle span {
  width: 24px;
  height: 3px;
  background: var(--color-foreground);
  border-radius: 2px;
  transition: transform var(--duration-normal), opacity var(--duration-normal);
}

.nav-toggle[aria-expanded="true"] span:nth-child(1) {
  transform: translateY(8px) rotate(45deg);
}

.nav-toggle[aria-expanded="true"] span:nth-child(2) {
  opacity: 0;
}

.nav-toggle[aria-expanded="true"] span:nth-child(3) {
  transform: translateY(-8px) rotate(-45deg);
}

/* ---------- Hero ---------- */
.hero {
  position: relative;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  /* Symmetric -- the scroll cue is position:fixed now, so it no longer
     needs extra bottom clearance reserved here to avoid overlap. */
  padding-block: var(--space-xl);
  overflow: clip;
  /* A real sky, distinct from the rest of the page's flatter light
     background: deeper blue up high (mimicking real atmosphere near
     "space"), paling toward the horizon near the fold. */
  background: linear-gradient(180deg, #cfe6ff 0%, #e3f1ff 45%, var(--color-background) 100%);
}

.hero-inner {
  width: 100%;
}

/* A tiled, softly-visible dot field behind everything in the hero,
   continuously scrolling downward -- combined with the downward-drifting
   clouds, this is what sells "the rocket is climbing" rather than the
   scene being static. Seamless: the animation moves it by exactly one
   tile, so the loop point is invisible. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image: radial-gradient(circle, var(--color-border) 1.5px, transparent 1.6px);
  background-size: 70px 70px;
  opacity: 0.4;
  animation: starfield-drift 9s linear infinite;
}

@keyframes starfield-drift {
  from {
    background-position: 0 0;
  }

  to {
    background-position: 0 70px;
  }
}

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

.hero-clouds-layer {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Cursor-parallax target (see js/main.js) -- a smoothing transition
     rather than per-frame JS lerp, since mousemove is the only driver. */
  transition: transform 0.4s ease-out;
}

.hero-cloud {
  position: absolute;
  z-index: 0;
  fill: #ffffff;
  opacity: 0.85;
  /* Soft blue-gray underside shadow (not the old orange-tinted one)
     reads as sunlight-from-above on a real cumulus cloud. */
  filter: drop-shadow(0 10px 14px rgba(30, 64, 120, 0.16));
  animation: cloud-drift linear infinite, cloud-sway ease-in-out infinite;
}

.hero-cloud-1 {
  width: 220px;
  left: 4%;
  top: -220px;
  animation-duration: 20s, 6s;
  animation-delay: 0s, 0s;
}

.hero-cloud-2 {
  width: 150px;
  left: 58%;
  top: -220px;
  opacity: 0.65;
  animation-duration: 27s, 7s;
  animation-delay: -9s, -2s;
}

.hero-cloud-3 {
  width: 110px;
  left: 32%;
  top: -220px;
  opacity: 0.55;
  animation-duration: 16s, 5s;
  animation-delay: -5s, -1s;
}

.hero-cloud-4 {
  width: 180px;
  left: 82%;
  top: -220px;
  opacity: 0.6;
  animation-duration: 23s, 6.5s;
  animation-delay: -16s, -3s;
}

.hero-cloud-5 {
  width: 90px;
  left: 14%;
  top: -220px;
  opacity: 0.45;
  animation-duration: 13s, 4.5s;
  animation-delay: -3s, -0.5s;
}

@keyframes cloud-drift {
  from {
    transform: translateY(0);
  }

  to {
    transform: translateY(calc(100vh + 300px));
  }
}

@keyframes cloud-sway {

  0%,
  100% {
    margin-left: 0;
  }

  50% {
    margin-left: 12px;
  }
}

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

.hero-inner {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  width: 100%;
}

.hero-copy {
  /* Centered within its own half -- not hugging the edge, not
     stretching full-width -- both axes (vertical comes from the grid's
     align-items:center above). No top nudge needed here: at this width
     the rocket lives in its own column to the right, so there's no
     shared vertical space with the copy to protect (the stacked,
     single-column layout below adds its own smaller nudge, where the
     rocket parks above the copy in the same column). */
  justify-self: center;
  align-self: center;
  max-width: 34rem;
}

.eyebrow {
  font-family: var(--font-display);
  color: var(--color-accent);
  font-weight: 600;
  letter-spacing: 0.02em;
  margin-bottom: var(--space-xs);
}

.eyebrow.center,
.section-title.center,
.section-sub.center {
  text-align: center;
  margin-left: auto;
  margin-right: auto;
}

/* More generous, even rhythm between hero elements -- filling the
   vertical space intentionally instead of leaving it bunched at the
   top with a gap underneath the trust row. */
.hero-copy .eyebrow {
  margin-bottom: var(--space-sm);
}

.hero h1 {
  font-size: clamp(2.5rem, 2rem + 3vw, 4.5rem);
  font-weight: 700;
  margin-bottom: var(--space-md);
}

.hero h1 .highlight {
  color: var(--color-primary);
  position: relative;
  display: inline-block;
}

.hero-sub {
  max-width: 46ch;
  font-size: 1.15rem;
  color: var(--color-muted-foreground);
  margin-bottom: var(--space-sm);
}

.hero-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  /* Was margin-block (space-lg on top AND bottom, ~112px combined at
     the high end) -- between that, the hero's own space-xl top/bottom
     padding, and every other element's own margin, the hero's total
     content height ran past 100dvh on common laptop-with-chrome
     viewport heights (~700-820px), pushing the trust row below the
     fold. One-sided and smaller: still a clear break from the copy
     above, without the redundant matching gap below it. */
  margin-top: var(--space-md);
  margin-bottom: var(--space-md);
}

.hero-trust {
  display: flex;
  align-items: center;
  gap: 0.75em;
  font-size: 0.95rem;
}

.hero-trust p {
  margin: 0;
  color: var(--color-muted-foreground);
}

.rocket-canvas {
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  /* Below both the nav (100) and .clay-card (41 -> wait, see below)
     so cards keep sliding correctly under the sticky nav, and the
     rocket keeps flying behind cards during a crossing. Raising the
     rocket above nav here previously forced cards above nav too (to
     stay above the rocket), which broke normal sticky-nav scrolling.
     The "rocket hidden behind nav in the hero" case is fixed by
     keeping the hero's resting position clear of the nav band instead
     (see EDGE_MARGIN / hero yFrac in rocket.js), not by z-index. */
  z-index: 40;
}

@media (max-width: 860px) {

  /* Must match rocket.js's own NARROW_QUERY breakpoint (860px) and
     .hero-inner's single-column stacking breakpoint below -- below
     that width the rocket is a small parked/hero badge, not doing the
     behind-cards flight (canTravel is false there), so there's no
     conflict raising it above both cards and the nav here. */
  .rocket-canvas {
    z-index: 103;
  }
}

.rocket-hotspot {
  position: fixed;
  top: 0;
  left: 0;
  width: 0;
  height: 0;
  border-radius: 50%;
  pointer-events: none;
  z-index: 42;
  touch-action: none;
}

.rocket-hotspot.is-active {
  pointer-events: auto;
  cursor: grab;
}

.rocket-hotspot.is-dragging {
  cursor: grabbing;
}

.scroll-cue {
  /* Fixed to the viewport, not the (min-height:100dvh, can grow taller
     with content) hero section -- guarantees it always sits at the
     real bottom of the screen on every viewport size. */
  position: fixed;
  bottom: var(--space-md);
  left: 50%;
  transform: translateX(-50%);
  width: 28px;
  height: 44px;
  border: 3px solid var(--color-shadow);
  border-radius: 20px;
  z-index: 41;
  opacity: 1;
  transition: opacity var(--duration-normal) ease;
}

.scroll-cue.is-hidden {
  opacity: 0;
  pointer-events: none;
}

.scroll-cue span {
  position: absolute;
  top: 8px;
  left: 50%;
  width: 5px;
  height: 5px;
  background: var(--color-primary);
  border-radius: 50%;
  transform: translateX(-50%);
  animation: scroll-cue 1.6s ease-in-out infinite;
}

@keyframes scroll-cue {
  0% {
    transform: translate(-50%, 0);
    opacity: 1;
  }

  70% {
    opacity: 0.4;
  }

  100% {
    transform: translate(-50%, 16px);
    opacity: 0;
  }
}

/* ---------- Sections ---------- */
section {
  padding-block: var(--space-xl);
}

/* Alternating section backgrounds -- two light tones (not literal
   dark/light, the site is deliberately single-palette-light), just
   enough contrast to separate one section from the next as you
   scroll. Each transition is a short gradient into the section's own
   tone rather than a flat color starting right at the boundary, so
   there's no hard seam where one section's solid color cuts straight
   into the next's -- it eases in over the top ~18% and holds steady
   for the rest. Hero keeps its own sky gradient, untouched; Services
   stays flat since it already matches the tone hero ends on. */
.services {
  background: var(--color-background);
}

.pricing,
.about,
.contact {
  background: linear-gradient(180deg, var(--color-background) 0%, var(--color-muted) 18%, var(--color-muted) 100%);
}

.clients,
.faq {
  background: linear-gradient(180deg, var(--color-muted) 0%, var(--color-background) 18%, var(--color-background) 100%);
}

.section-title {
  font-size: clamp(1.75rem, 1.4rem + 1.6vw, 2.75rem);
  max-width: 30ch;
}

.section-sub {
  color: var(--color-muted-foreground);
  max-width: 55ch;
  font-size: 1.1rem;
}

/* ---------- Clay card ---------- */
.clay-card {
  background: var(--color-card);
  border: 3px solid var(--color-shadow);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-clay);
  padding: var(--space-md);
  transition: transform var(--duration-normal) var(--ease-clay), box-shadow var(--duration-normal) var(--ease-clay);
  /* Sits above .rocket-canvas (40) so the rocket can fly behind cards
     during a section crossing, but still below the nav (100) so cards
     keep sliding correctly underneath it during normal scroll. */
  position: relative;
  z-index: 41;
}

.card-grid {
  display: grid;
  gap: var(--space-md);
  margin-top: var(--space-lg);
}

/* ---------- Services carousel ----------
   Back to the simple version: a native horizontal scroll-snap row
   (no scroll-hijacking, no sticky pinning) with arrow buttons, at
   every width. The one addition is the "active" card -- whichever
   card sits nearest the carousel's center lifts up slightly (JS in
   main.js tracks this on scroll), giving a clear sense of focus
   without any extra scroll machinery. */
.services-carousel {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

.services-track {
  display: flex;
  gap: var(--space-md);
  overflow-x: auto;
  /* Flex items default to min-width:auto, which lets this item's huge
     intrinsic content width (all slots laid out side by side) force
     .services-carousel wider than the viewport instead of shrinking
     to fit -- min-width:0 lets it shrink and scroll internally instead. */
  min-width: 0;
  scroll-snap-type: x mandatory;
  scroll-padding-left: var(--space-md);
  /* Without this, a trackpad swipe aimed at scrolling the carousel
     horizontally also carries a small vertical component that leaks
     into the page's own scroll once the track can't absorb more --
     and since the rocket's position is driven entirely by page
     scrollY, that leak reads as "scrolling the carousel moves the
     rocket". This stops the scroll from chaining to the page. */
  overscroll-behavior: contain;
  /* Room for the clay shadow and the active-card lift so they don't
     get clipped by the scroll container -- top needs to cover the
     active lift's full -16px, not just the shadow. */
  padding: 22px 6px 30px;
  margin: -22px -6px -30px;
  scrollbar-width: none;
}

.services-track::-webkit-scrollbar {
  display: none;
}

/* A wrapper around each card, not the card itself -- Vanilla-Tilt sets
   its own `transform` inline on .services-card for the hover tilt,
   and this keeps that separate from the slot's own layout duties. */
.services-slot {
  /* display:grid so the card (its only child) fills both axes via
     grid's own default stretch -- simpler and safer than a
     percentage-height child, which can spiral into an oversized box
     when the parent's own height itself comes from a flex stretch. */
  display: grid;
  flex: 0 0 260px;
  scroll-snap-align: start;
}

.carousel-arrow {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  border: 3px solid var(--color-shadow);
  background: var(--color-card);
  color: var(--color-foreground);
  cursor: pointer;
  box-shadow: var(--shadow-clay-sm);
  transition: transform var(--duration-fast) var(--ease-clay), box-shadow var(--duration-fast) var(--ease-clay);
}

.carousel-arrow svg {
  width: 20px;
  height: 20px;
}

.carousel-arrow:hover {
  transform: translate(-2px, -2px);
  box-shadow: 6px 6px 0 var(--color-shadow);
}

.carousel-arrow:active {
  transform: translate(1px, 1px);
  box-shadow: 3px 3px 0 var(--color-shadow);
}

@media (max-width: 640px) {

  /* On mobile the arrow buttons (44px each) plus gaps ate into the
     row's width while .services-slot was still sized off the full
     78vw viewport width, so the card demanded more space than the
     track had and overflowed past the visible edge. Touch swipe
     already works via scroll-snap, so drop the arrows here and give
     the track the full row width to size the card against. */
  .carousel-arrow {
    display: none;
  }

  .services-slot {
    flex-basis: 78vw;
  }
}

.card-icon {
  width: 56px;
  height: 56px;
  border-radius: var(--radius-md);
  display: flex;
  align-items: center;
  justify-content: center;
  border: 3px solid var(--color-shadow);
  margin-bottom: var(--space-sm);
}

.card-icon svg {
  width: 28px;
  height: 28px;
}

.icon-bg-1 {
  background: var(--color-secondary);
}

.icon-bg-2 {
  background: var(--color-primary);
  color: #fff;
}

.icon-bg-3 {
  background: var(--color-accent-dark);
  color: #fff;
}

.icon-bg-4 {
  background: var(--color-muted);
}

.clay-card h3 {
  font-size: 1.25rem;
}

.clay-card p:last-child {
  margin-bottom: 0;
  color: var(--color-muted-foreground);
}

/* ---------- Pricing ---------- */
/* Pricing reuses the exact same carousel component as Services
   (.services-carousel / .services-track / .services-card / the
   active-slot lift) -- only this one extra bit for the price pill. */
.pricing-row-price {
  display: inline-block;
  margin-top: var(--space-sm);
  padding: 0.5em 1.1em;
  border-radius: 999px;
  background: var(--color-muted);
  color: var(--color-primary-dark);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 0.95rem;
}

.pricing-note {
  margin-top: var(--space-md);
  color: var(--color-muted-foreground);
  font-size: 0.95rem;
  text-align: center;
}

/* Generic centered CTA row, reused wherever a section wants one
   button below its content (Clients today; Pricing's own markup use
   of this class is currently commented out). */
.section-cta {
  display: flex;
  justify-content: center;
  margin-top: var(--space-md);
}

/* ---------- Clients ---------- */
.clients-marquee {
  overflow: hidden;
  margin-top: var(--space-lg);
  margin-bottom: var(--space-lg);
  /* Badges carry a box-shadow that extends past their own box (right +
     down); overflow:hidden was clipping it flush at the container edge
     with no reserved room, cutting the bottom off every badge. */
  padding: 4px 4px 10px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 10%, #000 90%, transparent);
}

.clients-track {
  display: flex;
  gap: var(--space-md);
  width: max-content;
  /* Duration doubled alongside the badge list (3 names x4 repeats,
     up from x2) so the pace per badge stays the same -- otherwise the
     same 22s now has twice the distance to cover, which reads as a
     rushed, stuttery scroll rather than a smooth drift. will-change
     promotes the track to its own compositor layer up front, instead
     of the browser deciding to promote/demote it mid-animation
     (a common real cause of a visible hitch partway through). */
  will-change: transform;
  animation: marquee 44s linear infinite;
}

@keyframes marquee {
  from {
    transform: translate3d(0, 0, 0);
  }

  to {
    transform: translate3d(-50%, 0, 0);
  }
}

.client-badge {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.1rem;
  padding: 0.9em 1.8em;
  background: var(--color-card);
  border: 3px solid var(--color-shadow);
  border-radius: 999px;
  box-shadow: var(--shadow-clay-sm);
  white-space: nowrap;
}

/* The whole strip slides in from the side and settles, rather than
   the marquee just appearing already mid-scroll. */
.js .clients-marquee[data-reveal] {
  transform: translateX(-60px);
}

.clients-marquee[data-reveal].is-visible {
  transform: translateX(0);
  transition: opacity 0.6s ease-out, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

.clients-highlights {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-md);
  max-width: 900px;
  margin: var(--space-lg) auto 0;
  text-align: center;
}

.clients-highlight h3 {
  font-size: 1.1rem;
}

.clients-highlight p {
  margin: 0;
  color: var(--color-muted-foreground);
  font-size: 0.95rem;
}

@media (max-width: 700px) {
  .clients-highlights {
    grid-template-columns: 1fr;
  }
}

/* ---------- About ---------- */
.about-copy {
  max-width: 640px;
  margin: var(--space-lg) auto 0;
}

.about-copy p {
  color: var(--color-muted-foreground);
  font-size: 1.05rem;
  text-align: center;
}

.about-tagline {
  margin-top: var(--space-md);
  font-family: var(--font-display);
  font-size: 1.3rem;
  color: var(--color-foreground);
}

.about-tagline strong {
  color: var(--color-primary);
}

/* ---------- FAQ ---------- */
.faq-list {
  display: grid;
  gap: var(--space-sm);
  max-width: 46rem;
  margin: var(--space-lg) auto 0;
}

.faq-item summary {
  cursor: pointer;
  font-family: var(--font-display);
  font-weight: 600;
  list-style: none;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
}

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

.faq-item summary::after {
  content: "+";
  flex-shrink: 0;
  font-size: 1.4rem;
  color: var(--color-primary);
  transition: transform var(--duration-normal) var(--ease-clay);
}

.faq-item[open] summary::after {
  transform: rotate(45deg);
}

.faq-item p {
  margin: var(--space-sm) 0 0;
  color: var(--color-muted-foreground);
}

/* ---------- Contact ---------- */
.contact-inner {
  display: grid;
  grid-template-columns: 0.9fr 1.1fr;
  gap: var(--space-lg);
  align-items: start;
}

.contact-details {
  margin-top: var(--space-md);
  display: grid;
  gap: var(--space-sm);
}

.contact-details li {
  display: flex;
  align-items: center;
  gap: 0.75em;
  font-weight: 700;
}

.contact-details svg {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
  color: var(--color-accent);
}

/* Info slides in from the left, the form pops up from below with a
   bounce -- converging from two directions instead of both just fading up. */
.js .contact-info[data-reveal] {
  transform: translateX(-40px);
}

.contact-info[data-reveal].is-visible {
  transform: translateX(0);
  transition: opacity 0.6s ease-out, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
}

.js .contact-form[data-reveal] {
  transform: translateY(50px) scale(0.92);
}

.contact-form[data-reveal].is-visible {
  transform: translateY(0) scale(1);
  transition: opacity 0.55s ease-out, transform 0.6s cubic-bezier(0.22, 1.6, 0.36, 1);
}

.contact-form {
  display: grid;
  gap: var(--space-sm);
}

.form-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-sm);
  /* Without this, grid's default stretch makes both fields match the
     row's height -- so a field with an extra .field-error line (e.g.
     "Full name") stretches its shorter sibling ("Business or company
     name") too, and that leftover space inflates the sibling's input
     box instead of just leaving a gap. */
  align-items: start;
}

.field {
  display: grid;
  gap: 0.4em;
}

.field label {
  font-weight: 700;
  font-size: 0.95rem;
}

.field-optional {
  font-weight: 400;
  color: var(--color-muted-foreground);
}

.field input,
.field select,
.field textarea {
  font: inherit;
  padding: 0.75em 1em;
  border: 3px solid var(--color-shadow);
  border-radius: var(--radius-md);
  background: var(--color-background);
  color: var(--color-foreground);
  min-height: 44px;
  transition: box-shadow var(--duration-fast);
}

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

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  box-shadow: 4px 4px 0 var(--color-accent);
}

.field input[aria-invalid="true"],
.field textarea[aria-invalid="true"] {
  border-color: var(--color-destructive);
}

.field-error {
  color: var(--color-destructive);
  font-size: 0.85rem;
  font-weight: 700;
  min-height: 1.2em;
  margin: 0;
}

.form-status {
  text-align: center;
  font-weight: 700;
  min-height: 1.2em;
  margin: 0;
}

.form-status.success {
  color: var(--color-accent-dark);
}

.form-status.error {
  color: var(--color-destructive);
}

/* ---------- Footer ---------- */
.site-footer {
  position: relative;
  border-top: 2px solid var(--color-border);
  padding-block: var(--space-lg);
  background: linear-gradient(180deg, var(--color-muted) 0%, var(--color-background) 100%);
}

@media (hover: hover) and (pointer: fine) {
  .site-footer {
    /* The classic pure-CSS parallax: the gradient is fixed to the
       viewport instead of scrolling with the footer's own box, so it
       visibly shifts underneath the content as the footer scrolls
       into view. Skipped on touch devices -- background-attachment:
       fixed is unreliable and janky on mobile Safari/Chrome, so those
       get a normal scrolling background instead. */
    background-attachment: fixed;
  }
}

.footer-inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  text-align: center;
}

.footer-tagline {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.2rem;
  font-weight: 600;
}

/* Shared between the footer and the hero, so the two bookend each
   other with the same service list instead of only appearing once. */
.services-tagline {
  margin: 0;
  color: var(--color-muted-foreground);
}

.footer-copy {
  margin: var(--space-xs) 0 0;
  color: var(--color-muted-foreground);
  font-size: 0.85rem;
}

/* ---------- Scroll reveal (JS toggles .is-visible) ----------
   Gated behind .js (set by an inline head script) so content stays
   fully visible if JavaScript never runs. */
.js [data-reveal] {
  opacity: 0;
  transform: translateY(24px);
}

[data-reveal].is-visible {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 0.7s var(--ease-clay), transform 0.7s var(--ease-clay);
}

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal] {
    opacity: 1;
    transform: none;
  }
}

/* Hero gets its own bespoke staggered GSAP entrance instead of the
   generic reveal (see js/main.js heroIntro). Same no-JS fallback. */
.js .hero-anim {
  opacity: 0;
  transform: translateY(22px);
}

@media (prefers-reduced-motion: reduce) {
  .js .hero-anim {
    opacity: 1;
    transform: none;
  }
}

/* ---------- Responsive ---------- */
@media (max-width: 1024px) {
  .contact-inner {
    grid-template-columns: 1fr;
  }
}

@media (max-width: 860px) {
  html {
    scroll-padding-top: 10px;
  }

  .main-nav {
    display: none;
  }

  .nav-toggle {
    display: flex;
  }

  /* Fixed-to-viewport positioning (needed so it's never mispositioned
     by a hero taller than one screen) means it can overlap hero copy
     on narrow screens where content commonly exceeds 100dvh. Mobile
     users don't need a "scroll down" affordance anyway. */
  .scroll-cue {
    display: none;
  }

  body.nav-open .main-nav {
    display: flex;
    flex-direction: column;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: var(--color-background);
    border-bottom: 2px solid var(--color-border);
    padding: var(--space-md);
    gap: var(--space-sm);
  }

  .hero-inner {
    grid-template-columns: 1fr;
    text-align: center;
  }

  .hero-copy {
    /* No padding-inline here anymore -- .hero-inner now carries
       .container, which already provides matching edge spacing at
       every width, so this would just double up on top of it. */
    /* Only needed here: below this breakpoint the rocket shares this
       same stacked column, parked just above the copy, so a modest
       nudge keeps a clean gap without re-adding the larger desktop
       margin this used to carry unconditionally. */
    margin-top: clamp(16px, 5vw, 40px);
  }

  .hero-sub {
    margin-inline: auto;
  }

  .hero-actions {
    justify-content: center;
  }

  .hero-trust {
    flex-direction: column;
    gap: 0.35em;
  }
}

@media (max-width: 640px) {
  .form-row {
    grid-template-columns: 1fr;
  }
}

/* ---------- Hero typewriter ---------- */
.type-line {
  position: relative;
  display: inline-block;
  white-space: nowrap;
}

.type-ghost {
  visibility: hidden;
}

.type-typed {
  position: absolute;
  left: 0;
  top: 0;
}

.type-cursor {
  font-weight: 400;
  margin-left: 0.05em;
  animation: type-blink 1s steps(1) infinite;
}

@keyframes type-blink {
  50% {
    opacity: 0;
  }
}

/* ---------- Tilt cards: icon + title float above the card (translateZ) ---------- */
.tilt {
  transform-style: preserve-3d;
}

.tilt .card-icon {
  transform: translateZ(0);
  box-shadow: 0 10px 14px rgba(15, 23, 42, 0.25);
}

.tilt h3 {
  transform: translateZ(0);
}
/* LogoLoop-style hover: strip pauses, hovered badge scales up. */
.clients-marquee:hover .clients-track { animation-play-state: paused; }
.client-badge { transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1); }
.client-badge:hover { transform: scale(1.12); }
@media (prefers-reduced-motion: reduce) { .clients-track { animation: none; } }

/* Focus carousel (Services + Pricing): the snapped card sits centered at full
   size, the rest shrink and dim. Uses scroll-state container queries
   (Chrome 133+); other browsers keep the plain scroll-snap row above.
   `scale` (not transform) so it doesn't fight Vanilla-Tilt's inline transform. */
@supports (container-type: scroll-state) {
  .services-track {
    --slot: 260px;
    scroll-behavior: smooth;
  }
  .services-slot {
    flex-basis: var(--slot);
    container-type: scroll-state;
  }
  .services-card {
    transition: scale 300ms ease-in-out, opacity 300ms ease-in-out;
  }
  @container not scroll-state(snapped: inline) {
    .services-card { scale: 0.85; opacity: 0.55; }
  }
  @media (max-width: 640px) {
    .services-track { --slot: 78vw; }
  }
  @media (prefers-reduced-motion: reduce) {
    .services-track { scroll-behavior: auto; }
    .services-card { transition: none; }
  }
}

/* Mobile: arrows back. The track becomes a size container (flex:1) and slots
   are sized off its own width (cqw), so arrows can't push cards past the edge. */
@media (max-width: 640px) {
  .services-carousel { gap: 6px; }
  .carousel-arrow { display: flex; width: 34px; height: 34px; }
  .carousel-arrow svg { width: 16px; height: 16px; }
  .services-track { flex: 1; container-type: inline-size; --slot: 100cqw; }
  .services-slot { flex-basis: 100cqw; }
}

/* 3D pop: icon + title always float off the card. The cards rest at a slight
   angle (Vanilla-Tilt startX/startY on desktop, CSS below on touch) so the
   depth is visible without hovering. */
.tilt .card-icon { transform: translateZ(50px); box-shadow: 0 10px 14px rgba(15, 23, 42, 0.25); }
.tilt h3 {
  transform: translateZ(40px);
}
@media (hover: none) {
  .tilt { transform: perspective(800px) rotateX(3deg) rotateY(-4deg); }
}

/* Circular gallery mode (set by JS): slots are absolutely placed and moved with
   transforms. No preserve-3d on the slot: it puts the tilting card in the slot's 3D scene and
   hit-testing then flips, so the tilt resets itself. The card keeps its own 3D. */
.services-track.is-circular {
  position: relative;
  display: block;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  padding: 0;
  margin: 0;
  scroll-snap-type: none;
  touch-action: pan-y;
  /* overflow:hidden still makes this a scroll container; `contain` (set above) would swallow wheel scrolling over it */
  overscroll-behavior: auto;
  cursor: grab;
  outline-offset: 4px;
}
.services-track.is-dragging { cursor: grabbing; user-select: none; }
.services-track.is-circular .services-slot {
  position: absolute;
  top: 24px;
  left: 50%;
  flex: none;
  z-index: 41;
  will-change: transform;
}
.services-track.is-circular .services-card { scale: 1; opacity: 1; }

/* Only the centered card reacts to the pointer. */
.services-track.is-circular .services-slot:not(.is-center) { pointer-events: none; }

.word { display: inline-block; }
