/* ────────────────────────────────────────────────────────────────────
   Vijay Velu — premium dark portfolio
   Design tokens: black ground, glass cards, electric blue → purple
   signature gradient, cyan as a secondary/terminal accent.
   ──────────────────────────────────────────────────────────────────── */

:root {
  --ground:      #050505;
  --surface:     #0A0B0D;
  --ink:         #F5F6F8;
  --ink-2:       #9CA3AF;
  /* Lightened from the portfolio's #62666F, which measured 3.5:1 on the black
     ground — below the 4.5:1 WCAG AA minimum for the small type it is used on
     (footer links, table headers, eyebrows). #7C8290 measures 5.3:1. */
  --ink-3:       #7C8290;

  --glass-fill:    rgba(255,255,255,.045);
  --glass-fill-hi: rgba(255,255,255,.075);
  --glass-border:  rgba(255,255,255,.09);
  --glass-border-hi: rgba(255,255,255,.16);

  --space:   #0B1220;
  --blue:    #2563EB;
  --blue-lt: #3B82F6;
  --purple:  #7C3AED;
  --violet-lt: #8B5CF6;
  --cyan:    #22D3EE;
  --star:    #F8FAFC;

  --grad: linear-gradient(120deg, var(--blue), var(--purple));
  /* Text gradient uses the lighter stops — #2563EB on black is 3.96:1, fine for
     large display type but too dim for anything smaller. */
  --grad-text: linear-gradient(120deg, var(--blue-lt), var(--violet-lt));
  --grad-soft: linear-gradient(120deg, rgba(59,130,246,.5), rgba(139,92,246,.5));

  --r-lg: 24px;
  --r-md: 18px;
  --r-sm: 12px;

  --display: 'Space Grotesk', 'Helvetica Neue', Arial, sans-serif;
  --body:    'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
  --mono:    'JetBrains Mono', ui-monospace, monospace;

  /* ── Motion language ──────────────────────────────────────────────
     One shared set of curves. --spring overshoots slightly and is the
     default for anything a pointer touches; --ease is the calmer curve
     for reveals and ambient movement. Durations stay in the 150–350ms
     band the brief calls for. */
  --ease:    cubic-bezier(.16,.8,.24,1);
  --spring:  cubic-bezier(.34,1.56,.64,1);
  --t-fast:  .18s;
  --t-med:   .26s;
  --t-slow:  .34s;
}

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

html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; }

body {
  background: var(--ground);
  color: var(--ink);
  font-family: var(--body);
  font-size: 16px;
  line-height: 1.65;
  min-height: 100vh;
  overflow-x: clip;
}

a { color: inherit; text-decoration: none; }
ul { list-style: none; }
img, svg { display: block; max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: none; cursor: pointer; }

::selection { background: var(--purple); color: #fff; }

.wrap {
  max-width: 1240px;
  margin: 0 auto;
  padding: 0 2rem;
}

.eyebrow {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: inline-flex;
  align-items: center;
  gap: .5rem;
}

.eyebrow::before {
  content: "";
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--grad);
}

/* ════════════════════════════════════════════════════════════════════
   ATMOSPHERE
   One continuous scene, ported from vijayvelu.space: a single canvas
   flies through a starfield on first paint per tab session, then settles
   into a living knowledge graph that stays for the rest of the visit —
   drawn and animated entirely by main.js. No separate hero canvas: this
   is now the one background layer for every page.
   ════════════════════════════════════════════════════════════════════ */
.bg-graph {
  position: fixed;
  inset: 0;
  z-index: -1;
  width: 100%;
  height: 100%;
  pointer-events: none;
  opacity: .55;
  transform: translateZ(0);
  will-change: transform;
  -webkit-mask-image: radial-gradient(ellipse 92% 80% at 50% 45%, #000 55%, transparent 100%);
  mask-image: radial-gradient(ellipse 92% 80% at 50% 45%, #000 55%, transparent 100%);
  transition: opacity .9s cubic-bezier(.4,0,.2,1);
}

@media (max-width: 720px) { .bg-graph { opacity: .4; } }

/* Cursor-tracked glow. Sits behind the graph, answering the intro
   timeline: dimmed under warp, lifts as the words resolve, eases back
   once the page is at rest. */
.ambient {
  position: fixed;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background:
    radial-gradient(620px circle at var(--mx, 50%) var(--my, 22%), rgba(124,58,237,.09), transparent 58%);
  opacity: .8;
  transition: opacity 1.3s cubic-bezier(.4,0,.2,1);
}
.js.intro-lock .ambient { opacity: .45; }
.js.materialize .ambient { opacity: 1; }
.js.settled .ambient { opacity: .82; }

/* ── Glass card primitive ────────────────────────────────────────── */
.glass {
  background: var(--glass-fill);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  backdrop-filter: blur(20px);
  -webkit-backdrop-filter: blur(20px);
  position: relative;
}

/* Gradient border + edge highlight, drawn as a 1px masked ring so the
   card's own background stays untouched. */
.glass::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1px;
  background: linear-gradient(160deg, rgba(255,255,255,.16), rgba(124,58,237,.09) 32%, transparent 58%);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  pointer-events: none;
  transition: background var(--t-slow) ease;
}

/* Internal glow — a soft bloom pooled at the top edge, so a card reads as
   lit from above rather than as a flat translucent rectangle. */
.glass {
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.05),
    inset 0 24px 48px -32px rgba(124,58,237,.28);
}

/* ── Light sweep ──────────────────────────────────────────────────────
   A polished-aluminium highlight travelling across a panel. Driven by
   main.js, which adds .sheen-run to one visible element at a time on a
   long random interval — so it stays the rare accent the brief asks for
   rather than a permanent shimmer on every card.

   Only applied to components whose ::after is free (.contact-card and
   .cta-band already use theirs for their radial bloom). */
.sheen { position: relative; overflow: hidden; }

.sheen::after {
  content: "";
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 45%;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-160%, 0, 0) skewX(-18deg);
  background: linear-gradient(90deg,
    transparent,
    rgba(255,255,255,.05) 38%,
    rgba(255,255,255,.13) 50%,
    rgba(255,255,255,.05) 62%,
    transparent);
}

.sheen-run::after {
  animation: sheen 1100ms cubic-bezier(.4,0,.2,1) forwards;
}

@keyframes sheen {
  0%   { opacity: 0; transform: translate3d(-160%, 0, 0) skewX(-18deg); }
  12%  { opacity: 1; }
  88%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(320%, 0, 0) skewX(-18deg); }
}

/* Buttons catch the light on hover as well — the same material responding
   to attention rather than a separate effect. */
.btn.sheen:hover::after { animation: sheen 900ms cubic-bezier(.4,0,.2,1) forwards; }

/* ── Nav ─────────────────────────────────────────────────────────── */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.1rem 2rem;
  background: rgba(5,5,5,.6);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  border-bottom: 1px solid transparent;
  transition: border-color .3s ease, background .3s ease;
}

.nav.is-scrolled {
  border-bottom-color: var(--glass-border);
  background: rgba(5,5,5,.82);
}

/* Scroll-aware: retreats on the way down, returns the moment you scroll up. */
.nav {
  transform: translate3d(0, 0, 0);
  transition: border-color .3s ease, background .3s ease,
              transform var(--t-slow) var(--ease);
  will-change: transform;
}

.nav.is-hidden { transform: translate3d(0, -110%, 0); }

/* Reading progress — a hairline along the nav's lower edge. Scaled on the
   X axis from JS, so the only per-frame work is a composited transform. */
.nav-progress {
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--blue), var(--purple), var(--cyan));
  will-change: transform;
}

/* Active section indicator — one pill that slides between links rather
   than a border toggling on each. Positioned by JS. */
.nav-links { position: relative; }

.nav-indicator {
  position: absolute;
  bottom: -6px;
  height: 2px;
  border-radius: 2px;
  background: var(--grad);
  opacity: 0;
  transform: translate3d(var(--ind-x, 0), 0, 0);
  width: var(--ind-w, 0);
  transition: transform var(--t-slow) var(--ease),
              width var(--t-slow) var(--ease),
              opacity var(--t-med) ease;
  pointer-events: none;
  will-change: transform, width;
}

.nav-indicator.on { opacity: 1; }

.nav-links a.is-active { color: var(--ink); }

.nav-mark {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: -.01em;
  display: flex;
  align-items: center;
  gap: .55rem;
}

.nav-mark-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 0 14px rgba(139,92,246,.7);
}

.nav-links {
  display: flex;
  gap: 2.1rem;
}

.nav-links a {
  font-size: .88rem;
  color: var(--ink-2);
  transition: color .2s ease;
  position: relative;
}

.nav-links a:hover, .nav-links a:focus-visible { color: var(--ink); }

.nav-cta {
  display: inline-flex;
  align-items: center;
  gap: .5rem;
  font-size: .84rem;
  font-weight: 500;
  padding: .55rem 1.2rem;
  border-radius: 999px;
  background: var(--glass-fill-hi);
  border: 1px solid var(--glass-border-hi);
  transition: background .2s ease, border-color .2s ease, transform .2s ease;
}

.nav-cta:hover { border-color: rgba(139,92,246,.5); transform: translateY(-1px); }

.nav-toggle {
  display: none;
  width: 40px;
  height: 40px;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
  border: 1px solid var(--glass-border);
}

.nav-burger, .nav-burger::before, .nav-burger::after {
  width: 16px;
  height: 1.5px;
  background: var(--ink);
  position: relative;
  transition: transform .25s ease, opacity .2s ease, top .25s ease;
}

.nav-burger::before, .nav-burger::after { content: ""; position: absolute; left: 0; }
.nav-burger::before { top: -5px; }
.nav-burger::after { top: 5px; }

.nav-toggle[aria-expanded="true"] .nav-burger { background: transparent; }
.nav-toggle[aria-expanded="true"] .nav-burger::before { top: 0; transform: rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-burger::after { top: 0; transform: rotate(-45deg); }

/* ── Buttons ─────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .85rem 1.6rem;
  border-radius: 999px;
  font-size: .92rem;
  font-weight: 500;
  transition: transform .25s var(--ease), box-shadow .25s ease, border-color .25s ease, background .25s ease;
}

.btn-primary {
  background: var(--grad);
  color: #fff;
  box-shadow: 0 8px 30px -10px rgba(139,92,246,.55);
}

.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 14px 40px -10px rgba(139,92,246,.7); }

.btn-ghost {
  background: var(--glass-fill);
  border: 1px solid var(--glass-border-hi);
  color: var(--ink);
}

.btn-ghost:hover { background: var(--glass-fill-hi); border-color: rgba(255,255,255,.28); transform: translateY(-2px); }

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
}

/* ── Hero ────────────────────────────────────────────────────────── */
.hero {
  position: relative;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 8rem 2rem 4rem;
  overflow: hidden;
}

.hero-content { position: relative; z-index: 2; max-width: 920px; }

.hero .eyebrow { margin-bottom: 1.75rem; }

.hero h1 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(2.6rem, 6.4vw, 5.2rem);
  line-height: 1.04;
  letter-spacing: -.03em;
  text-wrap: balance;
}

.hero h1 .grad {
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero-sub {
  margin: 1.75rem auto 0;
  max-width: 46ch;
  font-size: clamp(1rem, 1.4vw, 1.15rem);
  color: var(--ink-2);
  line-height: 1.7;
  text-wrap: balance;
}

.hero .btn-row { justify-content: center; margin-top: 2.5rem; }

.hero-scroll {
  position: absolute;
  bottom: 2.25rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .6rem;
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--ink-3);
  z-index: 2;
}

.hero-scroll-line {
  width: 1px;
  height: 34px;
  background: linear-gradient(var(--purple), transparent);
  animation: scrolldrop 2s ease-in-out infinite;
}

@keyframes scrolldrop {
  0% { transform: scaleY(0); transform-origin: top; opacity: 0; }
  40% { transform: scaleY(1); transform-origin: top; opacity: 1; }
  60% { transform: scaleY(1); transform-origin: bottom; opacity: 1; }
  100% { transform: scaleY(0); transform-origin: bottom; opacity: 0; }
}

/* ── Trusted-by strip ──────────────────────────────────────────────
   A single-line social-proof bar between the hero and the trust bar —
   the first client's name is visible before any scrolling happens. */
.trustedby {
  position: relative;
  z-index: 1;
  border-bottom: 1px solid var(--glass-border);
  padding: 1rem 0;
}

.trustedby-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .9rem;
  flex-wrap: wrap;
  text-align: center;
  padding: .4rem;
  border-radius: var(--r-sm);
  transition: background var(--t-med) ease;
}

.trustedby-link:hover { background: var(--glass-fill); }

.trustedby-label {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.trustedby-client {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-family: var(--display);
  font-weight: 600;
  font-size: .96rem;
  color: var(--ink);
}

.trustedby-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--cyan);
  box-shadow: 0 0 10px rgba(34,211,238,.7);
  flex: none;
}

.trustedby-tag {
  font-family: var(--mono);
  font-weight: 400;
  font-size: .72rem;
  color: var(--ink-3);
}

.trustedby-cta {
  font-size: .82rem;
  font-weight: 500;
  color: var(--cyan);
  transition: gap var(--t-med) ease;
}

@media (max-width: 480px) {
  .trustedby-client { font-size: .88rem; }
  .trustedby-tag { display: none; }
}

/* ── Section scaffold ────────────────────────────────────────────── */
.section { padding: 8rem 0; position: relative; }
.section-tight { padding: 5rem 0; }

.section-head {
  max-width: 640px;
  margin-bottom: 4rem;
}

.section-head h2 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(2rem, 4vw, 3rem);
  letter-spacing: -.025em;
  line-height: 1.1;
  margin-top: 1rem;
}

.section-head p {
  margin-top: 1.1rem;
  color: var(--ink-2);
  font-size: 1rem;
  line-height: 1.75;
  max-width: 52ch;
}

/* ── About ───────────────────────────────────────────────────────── */
.about-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 4rem;
  align-items: start;
}

.about-lede {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.3rem, 2.4vw, 1.7rem);
  line-height: 1.45;
  letter-spacing: -.01em;
  color: var(--ink);
}

.about-lede .grad {
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.about-body {
  margin-top: 1.5rem;
  color: var(--ink-2);
  font-size: .95rem;
  line-height: 1.85;
}

.about-body p + p { margin-top: 1rem; }

.stat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--glass-border);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-lg);
  overflow: hidden;
  margin-top: 2.5rem;
}

.stat {
  background: var(--surface);
  padding: 1.75rem 1.5rem;
}

.stat-val {
  font-family: var(--display);
  font-weight: 600;
  font-size: 2.1rem;
  letter-spacing: -.02em;
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.stat-label {
  margin-top: .4rem;
  font-size: .8rem;
  color: var(--ink-2);
}

.focus-card { padding: 2rem; }

.focus-card h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.15rem;
  margin-bottom: 1.25rem;
}

.focus-list { display: flex; flex-direction: column; gap: .9rem; }

.focus-item {
  display: flex;
  align-items: baseline;
  gap: .8rem;
  font-size: .9rem;
  color: var(--ink-2);
}

.focus-item strong {
  color: var(--ink);
  font-weight: 500;
  min-width: 9rem;
}

/* ── Skills ──────────────────────────────────────────────────────── */
.skills-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1.1rem;
}

.skill-card {
  padding: 1.6rem 1.4rem;
  transition: transform .3s var(--ease), border-color .3s ease, background .3s ease;
}

.skill-card:hover {
  transform: translateY(-4px);
  border-color: rgba(139,92,246,.4);
  background: var(--glass-fill-hi);
}

.skill-icon {
  width: 34px;
  height: 34px;
  border-radius: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--grad-soft);
  margin-bottom: 1rem;
}

.skill-icon svg { width: 18px; height: 18px; stroke: #fff; }

.skill-card h4 {
  font-family: var(--display);
  font-weight: 600;
  font-size: .98rem;
  margin-bottom: .5rem;
}

.skill-card p {
  font-size: .8rem;
  color: var(--ink-2);
  line-height: 1.6;
}

/* ── Projects ────────────────────────────────────────────────────── */
.projects-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1.5rem;
}

.project-card {
  padding: 2rem;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
  transition: transform .35s var(--ease), border-color .35s ease;
}

.project-card:hover { transform: translateY(-5px); border-color: rgba(139,92,246,.4); }

.project-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
}

.project-card h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.4rem;
  letter-spacing: -.015em;
}

.project-state {
  font-family: var(--mono);
  font-size: .62rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  padding: .3rem .7rem;
  border-radius: 999px;
  border: 1px solid var(--glass-border-hi);
  color: var(--ink-2);
  white-space: nowrap;
}

.project-state.is-live {
  color: var(--cyan);
  border-color: rgba(34,211,238,.4);
  background: rgba(34,211,238,.06);
}

.project-card p { color: var(--ink-2); font-size: .92rem; line-height: 1.75; flex: 1; }

/* Pushed to the base of the card so stacks line up across a row
   whether or not the card has a links footer. */
.project-stack {
  display: flex;
  flex-wrap: wrap;
  gap: .45rem;
  margin-top: auto;
}

.project-stack span {
  font-family: var(--mono);
  font-size: .68rem;
  color: var(--ink-2);
  border: 1px solid var(--glass-border);
  padding: .25rem .6rem;
  border-radius: 999px;
}

.project-links {
  display: flex;
  gap: 1.4rem;
  padding-top: 1rem;
  border-top: 1px solid var(--glass-border);
  font-size: .85rem;
}

.project-links a {
  color: var(--ink-2);
  transition: color .2s ease;
}

.project-links a:hover { color: var(--cyan); }

/* ── Experience timeline ─────────────────────────────────────────── */
.timeline { position: relative; padding-left: 2.5rem; }

.timeline::before {
  content: "";
  position: absolute;
  left: 6px;
  top: .4rem;
  bottom: .4rem;
  width: 1px;
  background: linear-gradient(var(--glass-border-hi), transparent 95%);
}

.tl-item {
  position: relative;
  padding-bottom: 3rem;
}

.tl-item:last-child { padding-bottom: 0; }

.tl-item::before {
  content: "";
  position: absolute;
  left: -2.5rem;
  top: .35rem;
  width: 13px;
  height: 13px;
  border-radius: 50%;
  background: var(--surface);
  border: 2px solid var(--ink-3);
}

.tl-item.is-current::before {
  border-color: var(--purple);
  box-shadow: 0 0 0 4px rgba(139,92,246,.15), 0 0 16px rgba(139,92,246,.5);
}

.tl-when {
  font-family: var(--mono);
  font-size: .74rem;
  letter-spacing: .04em;
  color: var(--ink-3);
  margin-bottom: .5rem;
}

.tl-item.is-current .tl-when { color: var(--purple); }

.tl-item h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.3rem;
  letter-spacing: -.01em;
}

.tl-org { color: var(--ink-2); font-size: .88rem; margin-top: .25rem; margin-bottom: .9rem; }

.tl-item p { color: var(--ink-2); font-size: .9rem; line-height: 1.8; max-width: 62ch; }

.tl-tags { display: flex; flex-wrap: wrap; gap: .45rem; margin-top: 1rem; }

.tl-tags span {
  font-family: var(--mono);
  font-size: .68rem;
  color: var(--ink-2);
  border: 1px solid var(--glass-border);
  padding: .22rem .6rem;
  border-radius: 999px;
}

/* ── Certifications ──────────────────────────────────────────────── */
.certs-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.1rem;
}

.cert-card {
  padding: 1.6rem 1.5rem;
  display: flex;
  flex-direction: column;
  gap: .3rem;
  transition: transform .3s var(--ease), border-color .3s ease;
}

.cert-card:hover { transform: translateY(-3px); border-color: rgba(34,211,238,.4); }

.cert-badge {
  width: 30px;
  height: 30px;
  border-radius: 8px;
  background: var(--grad-soft);
  margin-bottom: .9rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex: none;
}

.cert-badge svg { width: 15px; height: 15px; stroke: #fff; }

/* Earned reads as a check; in progress reads as a clock. */
.cert-card.is-progress .cert-badge {
  background: rgba(34,211,238,.14);
  border: 1px solid rgba(34,211,238,.35);
}

.cert-card.is-progress .cert-badge svg { stroke: var(--cyan); }

.cert-name { font-weight: 500; font-size: .92rem; line-height: 1.4; }
.cert-by { font-family: var(--mono); font-size: .68rem; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); margin-top: .3rem; }
.cert-card.is-progress .cert-by { color: var(--cyan); }

/* ── Tech stack marquee ──────────────────────────────────────────── */
.stack-marquee {
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
  mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
}

.stack-track {
  display: flex;
  gap: 1rem;
  width: max-content;
  animation: marquee 34s linear infinite;
}

.stack-marquee:hover .stack-track { animation-play-state: paused; }

@keyframes marquee {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

.stack-chip {
  display: flex;
  align-items: center;
  gap: .55rem;
  padding: .75rem 1.3rem;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: .85rem;
  color: var(--ink-2);
  white-space: nowrap;
}

.stack-chip i {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--grad);
}

/* ── Contact ─────────────────────────────────────────────────────── */
.contact-card {
  padding: 4.5rem 3rem;
  text-align: center;
  position: relative;
  overflow: hidden;
}

.contact-card::after {
  content: "";
  position: absolute;
  inset: -40%;
  background: radial-gradient(circle at 50% 30%, rgba(139,92,246,.16), transparent 60%);
  pointer-events: none;
}

.contact-card > * { position: relative; }

.contact-card h2 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(2rem, 4.6vw, 3.4rem);
  letter-spacing: -.025em;
  line-height: 1.08;
  max-width: 18ch;
  margin: 0 auto 1.25rem;
  text-wrap: balance;
}

.contact-card p {
  color: var(--ink-2);
  max-width: 44ch;
  margin: 0 auto 2.25rem;
  font-size: 1rem;
  line-height: 1.75;
}

/* The enquiry form's own instance of .contact-card (left-aligned, one per
   site — previously an inline style override). Extra bottom padding
   reserves clearance from the fixed Call/WhatsApp corner buttons: a
   visitor filling the form naturally stops scrolling once "Send enquiry"
   and the status text are fully visible, which is well before they'd
   reach the page's true end — so the .foot bottom-clearance fix doesn't
   cover this position. This makes sure the card's own last line ("Built
   by...") has breathing room below it before the FAB's corner zone,
   wherever the user happens to stop scrolling.

   Compound selector (.contact-card.contact-card-form) deliberately, not
   just .contact-card-form: the generic mobile `.contact-card { padding:
   3rem 1.5rem }` override further down the file is equal specificity to
   a single-class selector and would win on source order alone. Two
   classes beats one regardless of where each rule sits in the file. */
.contact-card.contact-card-form {
  text-align: left;
  padding: 3rem 3rem calc(3rem + 92px);
}

@media (max-width: 720px) {
  .contact-card.contact-card-form { padding: 3rem 1.5rem calc(3rem + 92px); }
}

@media (max-width: 560px) {
  .contact-card.contact-card-form { padding-bottom: calc(3rem + 62px); }
}

.contact-links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: .8rem;
  margin-top: 2.5rem;
}

.contact-links a {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .7rem 1.4rem;
  border-radius: 999px;
  background: var(--glass-fill);
  border: 1px solid var(--glass-border-hi);
  font-size: .88rem;
  transition: background .2s ease, border-color .2s ease, transform .2s ease;
}

.contact-links a:hover { background: var(--glass-fill-hi); border-color: rgba(139,92,246,.5); transform: translateY(-2px); }

.contact-links .ico { width: 16px; height: 16px; flex: none; }

.built-with {
  display: inline-flex;
  align-items: center;
  gap: .6rem;
  margin-top: 2.5rem !important;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .02em;
  color: var(--ink-3) !important;
  max-width: none !important;
}

.built-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--grad);
  box-shadow: 0 0 10px rgba(139,92,246,.8);
  animation: breathe 2.6s ease-in-out infinite;
}

@keyframes breathe {
  0%, 100% { opacity: 1; }
  50% { opacity: .35; }
}

/* ── Footer ──────────────────────────────────────────────────────── */
.foot {
  border-top: 1px solid var(--glass-border);
  padding: 2.5rem 0;
  /* Extra bottom clearance so the fixed Call/WhatsApp corner buttons
     never sit on top of the footer's own content (.foot-links is
     right-aligned, under the WhatsApp button's corner) at true
     page-bottom scroll. */
  padding-bottom: calc(2.5rem + 92px + env(safe-area-inset-bottom));
}

.foot-inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.25rem;
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--ink-3);
}

.foot-links { display: flex; gap: 1.5rem; }
.foot-links a {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
}
.foot-links a:hover { color: var(--cyan); }

/* Small inline icon used beside any contact/app link — footer, CTA bands,
   form fallback buttons. Sized to sit on the mono/body baseline without
   pushing line-height. */
.ico-sm {
  width: 14px;
  height: 14px;
  flex-shrink: 0;
  display: block;
}

.btn .ico-sm { width: 16px; height: 16px; }

/* ════════════════════════════════════════════════════════════════════
   SALES / CASE-STUDY COMPONENTS
   Everything below is specific to veelabs.online and has no counterpart
   on vijayvelu.space. Keep it in this block so the shared theme above
   stays diffable against the portfolio.
   ════════════════════════════════════════════════════════════════════ */

/* ── Trust bar — proof strip directly under the hero ─────────────── */
.trustbar {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--glass-border);
  border-top: 1px solid var(--glass-border);
  border-bottom: 1px solid var(--glass-border);
}

.trustbar-cell {
  background: var(--ground);
  padding: 2rem 1.5rem;
  text-align: center;
}

.trustbar-val {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.9rem;
  letter-spacing: -.02em;
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.trustbar-label {
  margin-top: .35rem;
  font-size: .78rem;
  color: var(--ink-2);
  line-height: 1.45;
}

/* ── Service cards — what I sell ─────────────────────────────────── */
.service-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem;
}

.service-card {
  padding: 2rem 1.8rem;
  display: flex;
  flex-direction: column;
  gap: .9rem;
  transition: transform .35s var(--ease), border-color .35s ease;
}

.service-card:hover { transform: translateY(-4px); border-color: rgba(139,92,246,.4); }

.service-card h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.15rem;
  letter-spacing: -.01em;
}

.service-card p { color: var(--ink-2); font-size: .89rem; line-height: 1.75; flex: 1; }

.service-list {
  display: flex;
  flex-direction: column;
  gap: .5rem;
  margin-top: auto;
  padding-top: 1rem;
  border-top: 1px solid var(--glass-border);
}

.service-list span {
  font-size: .82rem;
  color: var(--ink-2);
  padding-left: 1.35rem;
  position: relative;
}

.service-list span::before {
  content: "";
  position: absolute;
  left: 0;
  top: .52em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--grad);
}

/* ── Featured case study — the hero of the sales pitch ───────────── */
.feature-case {
  display: grid;
  grid-template-columns: 1fr 1.15fr;
  gap: 3rem;
  align-items: center;
  padding: 2.75rem;
}

.feature-case-body h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(1.6rem, 3vw, 2.2rem);
  letter-spacing: -.02em;
  line-height: 1.15;
  margin: .9rem 0 1rem;
}

.feature-case-body > p { color: var(--ink-2); font-size: .95rem; line-height: 1.8; }

.feature-metrics {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  background: var(--glass-border);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-md);
  overflow: hidden;
  margin: 1.75rem 0;
}

.feature-metric { background: var(--surface); padding: 1.1rem .9rem; text-align: center; }

.feature-metric b {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.45rem;
  color: var(--ink);
  letter-spacing: -.02em;
}

.feature-metric span {
  display: block;
  margin-top: .2rem;
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ── Device frames — screenshots presented as real screens ───────── */
.device {
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--glass-border-hi);
  background: var(--surface);
  box-shadow: 0 24px 60px -20px rgba(0,0,0,.8);
}

.device-bar {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding: .6rem .85rem;
  background: rgba(255,255,255,.04);
  border-bottom: 1px solid var(--glass-border);
}

.device-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--ink-3); opacity: .5; flex: none; }

.device-url {
  margin-left: .6rem;
  font-family: var(--mono);
  font-size: .68rem;
  color: var(--ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.device img { width: 100%; height: auto; display: block; }

.device-phone {
  border-radius: 26px;
  border: 6px solid #1a1c20;
  max-width: 260px;
  box-shadow: 0 24px 60px -18px rgba(0,0,0,.85);
}

.device-phone img { border-radius: 20px; }

/* ── Process — numbered build steps ──────────────────────────────── */
.process-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.25rem;
  counter-reset: step;
}

.process-step {
  padding: 1.9rem 1.6rem;
  position: relative;
  transition: transform .3s var(--ease), border-color .3s ease;
}

.process-step:hover { transform: translateY(-3px); border-color: rgba(139,92,246,.4); }

.process-step::before {
  counter-increment: step;
  content: "0" counter(step);
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .12em;
  background: var(--grad-text);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.process-step h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.02rem;
  margin: .7rem 0 .55rem;
}

.process-step p { color: var(--ink-2); font-size: .84rem; line-height: 1.7; }

.process-when {
  display: inline-block;
  margin-top: .9rem;
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  border: 1px solid var(--glass-border);
  border-radius: 999px;
  padding: .2rem .6rem;
}

/* ── Trust signals ───────────────────────────────────────────────── */
.trust-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1.1rem;
}

.trust-card {
  padding: 1.7rem 1.5rem;
  transition: transform .3s var(--ease), border-color .3s ease;
}

.trust-card:hover { transform: translateY(-3px); border-color: rgba(34,211,238,.4); }

.trust-card h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: .98rem;
  margin-bottom: .5rem;
}

.trust-card p { color: var(--ink-2); font-size: .83rem; line-height: 1.7; }

/* ── CTA band ────────────────────────────────────────────────────── */
.cta-band {
  padding: 3.5rem 3rem;
  text-align: center;
  position: relative;
  overflow: hidden;
}

.cta-band::after {
  content: "";
  position: absolute;
  inset: -40%;
  background: radial-gradient(circle at 50% 30%, rgba(59,130,246,.16), transparent 60%);
  pointer-events: none;
}

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

.cta-band h2 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(1.7rem, 3.6vw, 2.6rem);
  letter-spacing: -.025em;
  line-height: 1.12;
  max-width: 20ch;
  margin: .9rem auto 1rem;
  text-wrap: balance;
}

.cta-band p { color: var(--ink-2); max-width: 48ch; margin: 0 auto 2rem; line-height: 1.75; }

.cta-band .btn-row { justify-content: center; }

/* ── Contact form ────────────────────────────────────────────────── */
.form-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  text-align: left;
}

.field { display: flex; flex-direction: column; gap: .45rem; }
.field-wide { grid-column: 1 / -1; }

.field label {
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.field input,
.field select,
.field textarea {
  font-family: var(--body);
  font-size: .92rem;
  color: var(--ink);
  background: rgba(255,255,255,.035);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-sm);
  padding: .8rem 1rem;
  width: 100%;
  transition: border-color .2s ease, background .2s ease;
}

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

.field select { appearance: none; cursor: pointer; }
.field select option { background: #14161a; color: var(--ink); }

.field input::placeholder,
.field textarea::placeholder { color: var(--ink-3); }

.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: rgba(139,92,246,.6);
  background: rgba(255,255,255,.06);
}

.field-error {
  font-size: .74rem;
  color: #F87171;
  min-height: 1em;
}

/* Honeypot — off-screen, never shown to a human. */
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.form-note {
  font-family: var(--mono);
  font-size: .7rem;
  color: var(--ink-3);
  line-height: 1.6;
  margin-top: 1rem;
}

/* ── Case-study page ─────────────────────────────────────────────── */
.case-hero { padding: 9rem 0 3rem; }

.case-hero h1 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(2.2rem, 5vw, 3.6rem);
  letter-spacing: -.03em;
  line-height: 1.08;
  margin: 1rem 0 1.25rem;
  max-width: 20ch;
  text-wrap: balance;
}

.case-lede {
  color: var(--ink-2);
  font-size: 1.05rem;
  line-height: 1.8;
  max-width: 58ch;
}

.case-meta {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 1px;
  background: var(--glass-border);
  border: 1px solid var(--glass-border);
  border-radius: var(--r-md);
  overflow: hidden;
  margin-top: 2.5rem;
}

.case-meta div { background: var(--surface); padding: 1.2rem 1.3rem; }

.case-meta dt {
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: .35rem;
}

.case-meta dd { font-size: .9rem; color: var(--ink); }

.case-body { max-width: 68ch; }

.case-body h2 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(1.5rem, 3vw, 2rem);
  letter-spacing: -.02em;
  margin-bottom: 1.1rem;
}

.case-body h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.1rem;
  margin: 2rem 0 .75rem;
}

.case-body p { color: var(--ink-2); line-height: 1.85; margin-bottom: 1rem; }

.case-body ul { margin: 0 0 1.25rem; }

.case-body li {
  color: var(--ink-2);
  line-height: 1.8;
  padding-left: 1.5rem;
  position: relative;
  margin-bottom: .5rem;
}

.case-body li::before {
  content: "";
  position: absolute;
  left: 0;
  top: .62em;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--grad);
}

.case-body strong { color: var(--ink); font-weight: 500; }

/* ── Before / after ──────────────────────────────────────────────── */
.ba-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.25rem;
}

.ba-card { padding: 1.9rem 1.7rem; }

.ba-card.is-before { border-color: rgba(248,113,113,.28); }
.ba-card.is-after { border-color: rgba(34,211,238,.32); }

.ba-tag {
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  padding: .25rem .7rem;
  border-radius: 999px;
  border: 1px solid currentColor;
  display: inline-block;
  margin-bottom: 1.1rem;
}

.ba-card.is-before .ba-tag { color: #F87171; }
.ba-card.is-after .ba-tag { color: var(--cyan); }

.ba-card ul { margin: 0; }

.ba-card li {
  color: var(--ink-2);
  font-size: .88rem;
  line-height: 1.7;
  padding-left: 1.5rem;
  position: relative;
  margin-bottom: .7rem;
}

.ba-card li:last-child { margin-bottom: 0; }

.ba-card li::before {
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--mono);
  font-size: .9em;
}

.ba-card.is-before li::before { content: "\00d7"; color: #F87171; }
.ba-card.is-after li::before { content: "\2713"; color: var(--cyan); }

/* ── Lighthouse score rings ──────────────────────────────────────── */
.score-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 1.1rem;
}

.score { padding: 1.6rem 1rem; text-align: center; }

.score-ring {
  --val: 0;
  --ring: var(--cyan);
  width: 84px;
  height: 84px;
  margin: 0 auto .9rem;
  border-radius: 50%;
  display: grid;
  place-items: center;
  background:
    radial-gradient(closest-side, var(--surface) 79%, transparent 80% 100%),
    conic-gradient(var(--ring) calc(var(--val) * 1%), rgba(255,255,255,.09) 0);
}

.score-ring b {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.5rem;
  color: var(--ink);
}

.score-name { font-size: .85rem; color: var(--ink); font-weight: 500; }

.score-sub {
  font-family: var(--mono);
  font-size: .64rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-top: .25rem;
}

/* ── Vitals table ────────────────────────────────────────────────── */
.vitals {
  width: 100%;
  border-collapse: collapse;
  font-size: .89rem;
}

.vitals th,
.vitals td {
  text-align: left;
  padding: .85rem 1rem;
  border-bottom: 1px solid var(--glass-border);
}

.vitals th {
  font-family: var(--mono);
  font-size: .66rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
  font-weight: 400;
}

.vitals td { color: var(--ink-2); }
.vitals td:first-child { color: var(--ink); }
.vitals tr:last-child td { border-bottom: none; }

.vitals .good { color: var(--cyan); font-family: var(--mono); font-size: .84rem; }
.vitals .ok { color: #FBBF24; font-family: var(--mono); font-size: .84rem; }

.table-scroll { overflow-x: auto; }

.measured-note {
  font-family: var(--mono);
  font-size: .7rem;
  color: var(--ink-3);
  line-height: 1.7;
  margin-top: 1rem;
}

/* ── Case-study index cards ──────────────────────────────────────── */
.case-card {
  padding: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transition: transform .35s var(--ease), border-color .35s ease;
}

.case-card:hover { transform: translateY(-5px); border-color: rgba(139,92,246,.4); }

.case-card-shot {
  border-bottom: 1px solid var(--glass-border);
  background: var(--surface);
}

.case-card-shot img { width: 100%; height: auto; display: block; }

.case-card-body { padding: 1.8rem; display: flex; flex-direction: column; gap: .8rem; flex: 1; }

.case-card-body h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.3rem;
  letter-spacing: -.015em;
}

.case-card-body p { color: var(--ink-2); font-size: .89rem; line-height: 1.7; flex: 1; }

/* Placeholder tile inviting the next client into the empty slot. */
.case-card-empty {
  border-style: dashed;
  display: grid;
  place-items: center;
  text-align: center;
  padding: 3rem 2rem;
  min-height: 320px;
}

.case-card-empty h3 {
  font-family: var(--display);
  font-weight: 600;
  font-size: 1.2rem;
  margin-bottom: .6rem;
}

.case-card-empty p { color: var(--ink-2); font-size: .88rem; max-width: 30ch; margin: 0 auto 1.5rem; }

/* ── Testimonial ─────────────────────────────────────────────────── */
.quote-card { padding: 2.5rem; }

.quote-card blockquote {
  font-family: var(--display);
  font-weight: 500;
  font-size: clamp(1.1rem, 2vw, 1.35rem);
  line-height: 1.55;
  letter-spacing: -.01em;
  color: var(--ink);
}

.quote-who {
  margin-top: 1.5rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--glass-border);
  font-size: .85rem;
  color: var(--ink-2);
}

.quote-who b { color: var(--ink); font-weight: 500; }

/* ── Breadcrumb ──────────────────────────────────────────────────── */
.crumb {
  font-family: var(--mono);
  font-size: .72rem;
  letter-spacing: .06em;
  color: var(--ink-3);
}

.crumb a { color: var(--ink-3); }
.crumb a:hover { color: var(--cyan); }

/* ── Interactive live preview ─────────────────────────────────────────
   A real, scrollable iframe of the client's production site inside a
   device frame, with a viewport switcher. The iframe is not in the markup
   at rest — main.js injects it only when the panel scrolls into view, so
   a third-party document never touches initial page load. Until then the
   frame holds the static screenshot, which also serves as the permanent
   fallback if the client's headers refuse framing. */
.preview { padding: 1.5rem; }

.preview-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.preview-title {
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-3);
}

.seg {
  display: inline-flex;
  padding: 3px;
  border-radius: 999px;
  background: rgba(255,255,255,.04);
  border: 1px solid var(--glass-border);
  position: relative;
}

.seg button {
  position: relative;
  z-index: 1;
  padding: .42rem 1rem;
  border-radius: 999px;
  font-family: var(--mono);
  font-size: .68rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-3);
  transition: color var(--t-med) ease;
}

.seg button[aria-pressed="true"] { color: var(--star); }

/* The moving pill behind the active segment. */
.seg-pill {
  position: absolute;
  top: 3px;
  bottom: 3px;
  border-radius: 999px;
  background: var(--grad);
  transform: translate3d(var(--seg-x, 0), 0, 0);
  width: var(--seg-w, 0);
  transition: transform var(--t-slow) var(--spring), width var(--t-slow) var(--spring);
  will-change: transform, width;
}

.preview-stage {
  display: grid;
  place-items: center;
  padding: 1.5rem 0 .5rem;
  transition: min-height var(--t-slow) var(--ease);
}

.preview-frame {
  position: relative;
  width: 100%;
  max-width: 1100px;
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--glass-border-hi);
  background: var(--surface);
  box-shadow: 0 30px 70px -24px rgba(0,0,0,.85);
  transition: max-width var(--t-slow) var(--ease), border-radius var(--t-slow) var(--ease);
}

/* Phone viewport — the same frame, constrained and rounded. */
.preview[data-view="mobile"] .preview-frame {
  max-width: 390px;
  border-radius: 30px;
  border-width: 6px;
  border-color: #17191d;
}

.preview-viewport {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 10;
  transition: aspect-ratio var(--t-slow) var(--ease);
  background: var(--surface);
}

.preview[data-view="mobile"] .preview-viewport { aspect-ratio: 390 / 720; }

.preview-viewport iframe,
.preview-viewport img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  display: block;
  object-fit: cover;
  object-position: top center;
}

.preview-viewport iframe { background: #050505; }

/* Shown until the iframe reports loaded; also the permanent state if the
   client's site refuses to be framed. */
.preview-fallback {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  z-index: 2;
  transition: opacity var(--t-slow) ease;
}

.preview-fallback.is-gone { opacity: 0; pointer-events: none; }

.preview-skeleton {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: linear-gradient(100deg,
    rgba(255,255,255,.02) 20%,
    rgba(255,255,255,.06) 40%,
    rgba(255,255,255,.02) 60%);
  background-size: 220% 100%;
  animation: skeleton 1.4s linear infinite;
  transition: opacity var(--t-slow) ease;
}

.preview-skeleton.is-gone { opacity: 0; pointer-events: none; }

@keyframes skeleton {
  from { background-position: 120% 0; }
  to   { background-position: -120% 0; }
}

.preview-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: .75rem;
  margin-top: 1.25rem;
  padding-top: 1rem;
  border-top: 1px solid var(--glass-border);
  font-family: var(--mono);
  font-size: .68rem;
  color: var(--ink-3);
}

.preview-foot a { color: var(--cyan); }

/* ── Card depth response ──────────────────────────────────────────────
   main.js writes --rx/--ry from the pointer's position over the card. The
   rotation is deliberately tiny (max ~4deg) — enough to read as a
   physical surface catching light, not a novelty tilt. */
.depth {
  transform:
    perspective(900px)
    rotateX(var(--rx, 0deg))
    rotateY(var(--ry, 0deg))
    translate3d(0, var(--lift, 0), 0);
  transition: transform var(--t-slow) var(--ease), border-color var(--t-slow) ease;
  transform-style: preserve-3d;
}

.depth.is-live { transition: transform 90ms linear, border-color var(--t-slow) ease; }

/* ── Reveal ──────────────────────────────────────────────────────── */
.js .rv {
  opacity: 0;
  transform: translate3d(0, 26px, 0) scale(.99);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
  will-change: transform, opacity;
}

.js .rv.on {
  opacity: 1;
  transform: none;
  will-change: auto;
}

/* ── Responsive ──────────────────────────────────────────────────── */
@media (max-width: 1080px) {
  .skills-grid { grid-template-columns: repeat(3, 1fr); }
  .certs-grid { grid-template-columns: repeat(2, 1fr); }
  .feature-case { grid-template-columns: 1fr; gap: 2.25rem; padding: 2rem; }
}

@media (max-width: 900px) {
  .about-grid { grid-template-columns: 1fr; gap: 2.5rem; }
  .projects-grid { grid-template-columns: 1fr; }
  .skills-grid { grid-template-columns: repeat(2, 1fr); }
  .service-grid,
  .process-grid,
  .trust-grid { grid-template-columns: repeat(2, 1fr); }
  .trustbar { grid-template-columns: repeat(2, 1fr); }
  .case-meta { grid-template-columns: repeat(2, 1fr); }
  .ba-grid { grid-template-columns: 1fr; }
}

@media (max-width: 720px) {
  .wrap { padding: 0 1.25rem; }
  .nav { padding: .95rem 1.25rem; }
  .nav-links, .nav-cta { display: none; }
  .nav-toggle { display: flex; }

  /* Burger disclosure — main.js toggles .is-open on #navLinks. Without this
     rule the button is inert and there is no nav at all on small screens. */
  .nav-links.is-open {
    display: flex;
    flex-direction: column;
    gap: 0;
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    padding: .25rem 1.25rem 1rem;
    background: rgba(5,5,5,.96);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-bottom: 1px solid var(--glass-border);
  }

  .nav-links.is-open li { border-top: 1px solid var(--glass-border); }
  .nav-links.is-open a { display: block; padding: .9rem 0; font-size: .95rem; }
  .section { padding: 5rem 0; }
  /* Bottom padding reserves clearance for the fixed contact-fab. .hero is
     min-height:100vh with justify-content:center, so on short viewports
     the centered button row lands almost exactly where the fixed FAB
     sits — this isn't a "scroll to the end" collision, it's the very
     first screen a visitor sees. Verified against real bounding boxes at
     340–430px: without this, the FAB overlaps ".hero .btn-row"'s last
     button at every common phone width except the narrowest. */
  .hero { padding: 7rem 1.25rem calc(3rem + 84px); }
  /* The h1's hardcoded line breaks are tuned for desktop and wrap raggedly
     on a phone. Drop them and let text-wrap:balance do the work. */
  .hero h1 br { display: none; }
  .stat-grid { grid-template-columns: 1fr 1fr; }
  .skills-grid { grid-template-columns: 1fr 1fr; }
  .certs-grid { grid-template-columns: 1fr; }
  .contact-card { padding: 3rem 1.5rem; }
  .service-grid,
  .process-grid,
  .trust-grid { grid-template-columns: 1fr; }
  .form-grid { grid-template-columns: 1fr; }
  .cta-band { padding: 2.75rem 1.5rem; }
  .case-hero { padding: 7rem 0 2rem; }
  .feature-metrics { grid-template-columns: 1fr; }
}

@media (max-width: 480px) {
  .skills-grid { grid-template-columns: 1fr; }
  .trustbar { grid-template-columns: 1fr; }
  .case-meta { grid-template-columns: 1fr; }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js .rv { opacity: 1; transform: none; transition: none; }
  .stack-track { animation: none; }
  .hero-scroll-line { animation: none; }
  .preview-skeleton { animation: none; }
  .sheen::after { display: none; }
  .depth { transform: none !important; }
  .nav.is-hidden { transform: none; }

  .js.intro-lock .mat,
  .mat { opacity: 1 !important; filter: none !important; transform: none !important; }
  .js.materialize .mat { transition: none; will-change: auto; }
  .ambient, .bg-graph { transition: none; }
  .js.intro-lock .ambient { opacity: .8; }

  * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
}

/* ── Custom cursor: precise dot + lagging ring ────────────────────────
   Ported from vijayvelu.space. Only enabled on fine pointers; the native
   cursor is restored for touch, reduced-motion, and any device without
   hover — see the guard in main.js. */
.cursor-glow {
  position: fixed;
  width: 420px;
  height: 420px;
  border-radius: 50%;
  pointer-events: none;
  z-index: 1;
  background: radial-gradient(circle, rgba(124,58,237,.10), transparent 70%);
  transform: translate(-50%, -50%);
  left: var(--cx, -100px);
  top: var(--cy, -100px);
  transition: opacity .3s ease;
}

.cur-dot, .cur-ring {
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: 300;
  border-radius: 50%;
  opacity: 0;
  will-change: transform, opacity;
  transition: opacity .12s linear;
}

.cur-dot {
  width: 6px;
  height: 6px;
  margin: -3px 0 0 -3px;
  background: var(--ink);
}

.cur-ring {
  width: 34px;
  height: 34px;
  margin: -17px 0 0 -17px;
  border: 1.5px solid rgba(255,255,255,.35);
  transition: opacity .12s linear, border-color .15s ease, background .15s ease;
}

.has-cursor .cur-dot,
.has-cursor .cur-ring { opacity: 1; }

.has-cursor.cur-active .cur-dot { opacity: 0; }
.has-cursor.cur-active .cur-ring {
  border-color: rgba(124,58,237,.9);
  background: rgba(124,58,237,.12);
}

.has-cursor, .has-cursor a, .has-cursor button { cursor: none; }

@media (max-width: 900px) {
  .cursor-glow { display: none; }
}

/* ── Intro materialise: hyperspace → settled, ported from vijayvelu.space
   Elements marked .mat start blurred/lowered while .intro-lock is on the
   root, then resolve on a staggered timeline (--mat-d per element) once
   main.js flips the root to .materialize. A failsafe reveals everything
   at 2s regardless, in case the scene script never runs. Only
   opacity/filter/transform move, so nothing reflows once painted. */
.js.intro-lock .mat {
  opacity: 0;
  filter: blur(8px);
  transform: translateY(10px) scale(.988);
}

.mat {
  opacity: 1;
  filter: blur(0);
  transform: translateY(0) scale(1);
}

.js.materialize .mat {
  transition:
    opacity   .55s cubic-bezier(.50, .00, .20, 1.00) var(--mat-d, 0ms),
    filter   .62s cubic-bezier(.33, .00, .18, 1.00) var(--mat-d, 0ms),
    transform .68s cubic-bezier(.16, 1.04, .30, 1.00) var(--mat-d, 0ms);
  will-change: opacity, filter, transform;
}

.js.settled .mat { will-change: auto; }

.js.intro-lock .mat { animation: matFailsafe 0s linear 2s forwards; }

@keyframes matFailsafe {
  to { opacity: 1; filter: blur(0); transform: none; }
}

/* Coarse pointers get no depth-tilt — a pointer affordance with nothing
   to respond to on a touchscreen. */
@media (hover: none), (pointer: coarse) {
  .depth { transform: none; }
}

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

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--ink);
  color: var(--ground);
  padding: .6rem 1.1rem;
  font-family: var(--mono);
  font-size: .75rem;
  z-index: 200;
  border-radius: var(--r-sm);
}

.skip:focus { left: 1rem; top: 1rem; }

/* Visible to screen readers only — table captions, redundant labels. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ── Persistent contact: Call (bottom-left) + WhatsApp (bottom-right) ──
   Two independent fixed buttons, each anchored to its own corner, on
   every viewport — not a clustered pair. Same treatment either way:
   labelled pill, brand-appropriate colour, safe-area-aware.

   Reveal timing is JS-gated, not a fixed page-load delay — see the
   "reveal the contact FAB" block in main.js. Reason: the homepage hero
   is min-height:100vh with its button row centered inside it, and on
   short mobile viewports that content is already taller than the
   viewport (verified with real bounding-box checks at 360–430px widths:
   a same-height-viewport hero button row overlapped a fixed corner
   button at every common phone width tested except the narrowest).
   Padding-based fixes can't reliably solve this once content height
   exceeds the viewport, because min-height stops being the binding
   constraint. Both buttons stay hidden and non-interactive until the
   hero has scrolled out of view; pages without that tall hero
   (case-hero pages, 404) reveal shortly after load instead.

   Real numbers: WhatsApp Business "Vee Labs" +91 73051 85348, same
   number used for the tel: link. */
.contact-fab-call,
.contact-fab-wa {
  position: fixed;
  bottom: calc(1rem + env(safe-area-inset-bottom));
  z-index: 150;
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  min-width: 48px;
  min-height: 48px;
  padding: .7rem 1.15rem;
  border-radius: 999px;
  font-family: var(--body);
  font-size: .86rem;
  font-weight: 600;
  white-space: nowrap;
  color: #fff;
  box-shadow: 0 10px 28px -8px rgba(0,0,0,.55);
  opacity: 0;
  transform: translate3d(0, 14px, 0) scale(.95);
  pointer-events: none;
  transition: opacity .4s var(--ease), transform .4s var(--spring), box-shadow var(--t-fast) ease, filter var(--t-fast) ease;
}

.contact-fab-call { left: calc(1rem + env(safe-area-inset-left)); background: var(--grad); }

.contact-fab-wa {
  right: calc(1rem + env(safe-area-inset-right));
  /* WhatsApp's own brand green, not the site palette — the one deliberate
     exception, so it reads as WhatsApp at a glance. #0B7A3F rather than
     the familiar lighter #25D366: the light shade is 1.98:1 against white
     (fails WCAG's 4.5:1 for this text size), #0B7A3F measures 5.43:1. */
  background: #0B7A3F;
}

body.fab-visible .contact-fab-call,
body.fab-visible .contact-fab-wa {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}

.contact-fab-call svg,
.contact-fab-wa svg { width: 20px; height: 20px; flex: none; display: block; }

.contact-fab-call:hover,
.contact-fab-wa:hover {
  transform: translateY(-3px);
  filter: brightness(1.1);
  box-shadow: 0 16px 34px -8px rgba(0,0,0,.6);
}

.contact-fab-call:active,
.contact-fab-wa:active {
  transform: translateY(-1px) scale(.97);
  filter: brightness(.96);
}

/* Defensive hook for later: a modal or cookie-banner system only needs to
   toggle one of these classes on <body> to keep either button from
   stacking on top of it. Neither exists on this site yet. */
body.modal-open .contact-fab-call,
body.modal-open .contact-fab-wa,
body.has-bottom-banner .contact-fab-call,
body.has-bottom-banner .contact-fab-wa { display: none; }

@media (prefers-reduced-motion: reduce) {
  /* The reveal GATE (JS timing) still applies — it's a functional fix for
     a real overlap, not a decorative flourish — only the slide/scale
     motion is removed, replaced by a plain, near-instant fade. */
  .contact-fab-call,
  .contact-fab-wa { transition: opacity .15s linear; transform: none; }
  .contact-fab-call:hover, .contact-fab-call:active,
  .contact-fab-wa:hover, .contact-fab-wa:active { transform: none; }
}

@media (max-width: 560px) {
  .contact-fab-call,
  .contact-fab-wa { padding: .65rem 1rem; font-size: .82rem; }
  /* Slightly shorter footprint at this size — matches the reduced
     padding/font-size above. */
  footer.foot { padding-bottom: calc(2.5rem + 62px + env(safe-area-inset-bottom)); }
}

/* Below this width a labelled pill in each corner starts crowding the
   page's own edge content — collapse to icon-only. The label stays for
   screen readers via aria-label on the link itself. */
@media (max-width: 360px) {
  .contact-fab-call span,
  .contact-fab-wa span { display: none; }
  .contact-fab-call,
  .contact-fab-wa { padding: .7rem; border-radius: 50%; }
}
