/* =========================================================
   ANTHONY RASOLOFOARIMANANA — PRODUCT OWNER PORTFOLIO
   Design concept: the portfolio as a shipped product.
   Every section carries a semver release tag; the experience
   section reads like a real changelog; the top bar tracks
   scroll progress the way a CI pipeline tracks a build.
   ========================================================= */

/* ---------- Tokens ---------- */
:root{
  --bg: #F7F6F2;
  --bg-alt: #EFEDE5;
  --card-bg: #FFFFFF;
  --ink: #1B1F23;
  --ink-dim: #5B5F63;
  --line: #DDD8CC;

  --blue: #2451B8;
  --blue-dim: #DCE4F7;
  --green: #1F8A5F;
  --green-dim: #DCEFE3;

  --font-display: 'Space Grotesk', 'Inter', sans-serif;
  --font-body: 'Inter', sans-serif;
  --font-mono: 'IBM Plex Mono', monospace;

  --radius-s: 6px;
  --radius-m: 12px;
  --radius-l: 20px;

  --shadow-s: 0 1px 2px rgba(20,20,15,0.06);
  --shadow-m: 0 8px 24px rgba(20,20,15,0.08);
  --shadow-l: 0 20px 48px rgba(20,20,15,0.12);

  --ease: cubic-bezier(.22,.61,.36,1);
  color-scheme: light;
}

html[data-theme="dark"]{
  --bg: #14171A;
  --bg-alt: #1A1E22;
  --card-bg: #1E2226;
  --ink: #ECEBE6;
  --ink-dim: #9BA0A6;
  --line: #2B2F33;

  --blue: #7DA3F5;
  --blue-dim: #1E2A42;
  --green: #63D19E;
  --green-dim: #1A3328;

  color-scheme: dark;
}

@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{ animation-duration: 0.001ms !important; animation-iteration-count: 1 !important; transition-duration: 0.001ms !important; scroll-behavior: auto !important; }
}

/* ---------- Reset ---------- */
*,*::before,*::after{ box-sizing: border-box; }
html{ scroll-behavior: smooth; }
body{
  margin:0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  transition: background .4s var(--ease), color .4s var(--ease);
}
img{ max-width:100%; display:block; }
a{ color: inherit; text-decoration: none; }
ul,ol{ margin:0; padding:0; list-style:none; }
h1,h2,h3,p{ margin:0; }
button{ font-family: inherit; cursor:pointer; border:none; background:none; color:inherit; }
input,textarea{ font-family: inherit; }

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

/* custom scrollbar */
::-webkit-scrollbar{ width:10px; height:10px; }
::-webkit-scrollbar-track{ background: var(--bg); }
::-webkit-scrollbar-thumb{ background: var(--line); border-radius: 10px; border: 2px solid var(--bg); }
::-webkit-scrollbar-thumb:hover{ background: var(--ink-dim); }

.section-inner{
  max-width: 1120px;
  margin: 0 auto;
  padding: 0 32px;
  position: relative;
  z-index: 2;
}

/* ---------- Build bar (signature element) ---------- */
.buildbar{
  position: fixed;
  top:0; left:0; right:0;
  height: 34px;
  background: var(--card-bg);
  border-bottom: 1px solid var(--line);
  z-index: 200;
  display:flex;
  align-items:center;
}
.buildbar__fill{
  position:absolute;
  top:0; left:0; height:100%;
  width:0%;
  background: linear-gradient(90deg, var(--blue-dim), var(--blue));
  opacity:.55;
  transition: width .12s linear;
}
.buildbar__label{
  position:relative;
  margin-left:auto;
  margin-right:16px;
  display:flex;
  align-items:center;
  gap:8px;
  font-family: var(--font-mono);
  font-size:11px;
  letter-spacing:.03em;
  color: var(--ink-dim);
}
.buildbar__dot{
  width:7px; height:7px; border-radius:50%;
  background: var(--green);
  box-shadow: 0 0 0 3px var(--green-dim);
}

/* ---------- Nav ---------- */
.nav{
  position: fixed;
  top: 34px; left:0; right:0;
  z-index: 190;
  background: color-mix(in srgb, var(--bg) 88%, transparent);
  backdrop-filter: blur(10px) saturate(140%);
  border-bottom: 1px solid transparent;
  transition: border-color .3s var(--ease), background .3s var(--ease);
}
.nav.is-scrolled{ border-bottom-color: var(--line); }
.nav__inner{
  max-width: 1120px;
  margin:0 auto;
  padding: 14px 32px;
  display:flex;
  align-items:center;
  gap: 24px;
}
.nav__brand{ display:flex; align-items:center; gap:10px; margin-right:auto; }
.nav__avatar{
  width: 34px;
  height: 34px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
  border: 2px solid var(--blue);
  box-shadow: var(--shadow-s);
  animation: avatarDrift 5s ease-in-out infinite;
}
.nav__avatar img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

@keyframes avatarDrift{
  0%   { transform: translate(0, 0); }
  20%  { transform: translate(-2px, -2px); }
  40%  { transform: translate(2px, -1.5px); }
  60%  { transform: translate(2px, 2px); }
  80%  { transform: translate(-2px, 1.5px); }
  100% { transform: translate(0, 0); }
}

@keyframes spinLang{
  from{ transform: rotate(0deg); }
  to{ transform: rotate(360deg); }
}
.nav__name{ font-family: var(--font-display); font-weight:600; font-size: 15px; }
.nav__dim{ color: var(--ink-dim); font-weight: 500; }

.nav__links{ display:flex; gap: 4px; }
.nav__link{
  position: relative;
  font-size: 13.5px;
  padding: 8px 10px;
  color: var(--ink-dim);
  border-radius: var(--radius-s);
  transition: color .35s var(--ease), transform .35s var(--ease);
  display:flex; align-items:center; gap:6px;
}
.nav__link::before{
  content: attr(data-tag);
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--line);
  position: relative;
  transition: color .35s var(--ease);
}
.nav__link::after{
  content: '';
  position: absolute;
  inset: 2px;
  z-index: -1;
  border-radius: var(--radius-s);
  background: var(--bg-alt);
  opacity: 0;
  transform: scale(.82);
  transition: opacity .4s var(--ease), transform .4s var(--ease);
}
.nav__link:hover{ color: var(--ink); transform: translateY(-1px); }
.nav__link:hover::before{ color: var(--blue); }
.nav__link:hover::after{ opacity: 1; transform: scale(1); }
.nav__link.is-active{ color: var(--blue); }
.nav__link.is-active::before{ color: var(--blue); }
.nav__link.is-active::after{ opacity: 1; transform: scale(1); }

.nav__actions{
    display:flex;
    align-items:center;
    gap:10px;
}

.lang-switch{
    display:flex;
    align-items:center;
    gap:10px;
    padding:8px 14px;
    border-radius:999px;
    border:1px solid var(--line);
    background:var(--card-bg);
    color:var(--ink);
    cursor:pointer;
    overflow:hidden;
    position:relative;
    transition:
        transform .35s var(--ease),
        border-color .35s,
        box-shadow .35s,
        background .35s;
}

.lang-switch:hover{
    transform:translateY(-3px);
    border-color:var(--blue);
    box-shadow:
        0 12px 30px rgba(0,0,0,.08),
        0 0 0 4px rgba(59,130,246,.08);
}

.lang-switch__icon{
    position:relative;
    width:24px;
    height:24px;
}

.lang-switch__flag{
    position:absolute;
    inset:0;
    display:grid;
    place-items:center;
    font-size:20px;
    opacity:0;
    transform:scale(.5) rotate(-12deg);
    transition:
        opacity .35s ease,
        transform .35s cubic-bezier(.22,.68,0,1.2),
        filter .35s;
    filter:drop-shadow(0 2px 4px rgba(0,0,0,.15));
}

.lang-switch__flag.active{
    opacity:1;
    transform:scale(1) rotate(0);
}

.lang-switch:hover .lang-switch__flag.active{
    transform:scale(1.18) rotate(6deg);
    filter:
        drop-shadow(0 4px 10px rgba(0,0,0,.25))
        drop-shadow(0 0 12px rgba(59,130,246,.35));
}

.lang-switch__label{
    font-family:var(--font-mono);
    font-size:11px;
    font-weight:700;
    letter-spacing:.12em;
    transition:color .3s;
}

.lang-switch:hover .lang-switch__label{
    color:var(--blue);
}
.theme-toggle{
  width:34px; height:34px;
  border-radius: 50%;
  display:flex; align-items:center; justify-content:center;
  border: 1px solid var(--line);
  color: var(--ink);
  transition: background .2s, transform .3s var(--ease);
}
.theme-toggle:hover{ background: var(--bg-alt); transform: rotate(15deg); }
.icon{ width:17px; height:17px; }
.icon--moon{ display:none; }
html[data-theme="dark"] .icon--sun{ display:none; }
html[data-theme="dark"] .icon--moon{ display:block; }

.nav__burger{ display:none; flex-direction:column; gap:4px; width:34px; height:34px; align-items:center; justify-content:center; }
.nav__burger span{ width:18px; height:2px; background: var(--ink); border-radius: 2px; transition: transform .25s var(--ease), opacity .2s; }
.nav__burger.is-open span:nth-child(1){ transform: translateY(6px) rotate(45deg); }
.nav__burger.is-open span:nth-child(2){ opacity:0; }
.nav__burger.is-open span:nth-child(3){ transform: translateY(-6px) rotate(-45deg); }

html[data-language="fr"] .hero__summary,
html[data-language="fr"] .about__bio,
html[data-language="fr"] .about__bio p,
html[data-language="fr"] .mini-card,
html[data-language="fr"] .mini-card p,
html[data-language="fr"] .skill-card,
html[data-language="fr"] .changelog__entry,
html[data-language="fr"] .pipeline__stage,
html[data-language="fr"] .testimonial-card,
html[data-language="fr"] .contact__content{
  text-align: center;
}

html[data-language="fr"] .hero__summary,
html[data-language="fr"] .about__bio p,
html[data-language="fr"] .mini-card p,
html[data-language="fr"] .skill-card,
html[data-language="fr"] .changelog__entry,
html[data-language="fr"] .pipeline__stage,
html[data-language="fr"] .testimonial-card,
html[data-language="fr"] .contact__content{
  margin-left: auto;
  margin-right: auto;
}

html[data-language="fr"] .hero__summary,
html[data-language="fr"] .about__bio p,
html[data-language="fr"] .mini-card p,
html[data-language="fr"] .testimonial-card p,
html[data-language="fr"] .contact__content p{
  max-width: 720px;
}

html[data-language="fr"] .about__grid,
html[data-language="fr"] .skills__grid,
html[data-language="fr"] .changelog,
html[data-language="fr"] .pipeline,
html[data-language="fr"] .contact__grid{
  justify-items: center;
}

@media (max-width: 900px){
  html[data-language="fr"] .about__grid{ grid-template-columns: 1fr; }
  html[data-language="fr"] .skills__grid{ grid-template-columns: 1fr; }
  html[data-language="fr"] .changelog__entry{ grid-template-columns: 1fr; }
}

/* ---------- Hero ---------- */
.hero{
  position: relative;
  min-height: 100vh;
  display:flex;
  flex-direction: column;
  align-items:center;
  justify-content:center;
  padding-top: 120px;
  overflow: hidden;
}
.hero__grid{
  position:absolute; inset:0;
  background-image:
    linear-gradient(var(--line) 1px, transparent 1px),
    linear-gradient(90deg, var(--line) 1px, transparent 1px);
  background-size: 56px 56px;
  opacity: .35;
  mask-image: radial-gradient(ellipse 60% 55% at 50% 40%, black 40%, transparent 90%);
}
.hero__shapes{ position:absolute; inset:0; pointer-events:none; }

/* ---------- Floating decorative shapes (shared, reused across every section) ---------- */
.shape{
  position:absolute;
  border: 1.5px solid var(--blue);
  opacity:.35;
  animation: float 4s ease-in-out infinite;
  will-change: transform;
}
.shape--sq{ width:64px; height:64px; top:18%; left:10%; border-radius: 12px; animation-delay: 0s; }
.shape--circ{ width:90px; height:90px; border-radius:50%; bottom:16%; right:12%; border-color: var(--green); animation-delay: .6s; }
.shape--tri{ width:0; height:0; border-left: 34px solid transparent; border-right: 34px solid transparent; border-bottom: 58px solid transparent; outline: none; top:62%; left:20%; border-bottom-color: var(--blue); opacity:.15; animation-delay: 1.2s; }
.shape--dot{ width:14px; height:14px; border-radius:50%; border:none; background: var(--green); opacity:.5; animation-delay: .3s; }
.shape--ring{ width:130px; height:130px; border-radius:50%; border:1.5px solid var(--blue); opacity:.16; animation-delay: .9s; }
.shape--sq2{ width:46px; height:46px; border-radius: 10px; border-color: var(--green); opacity:.28; animation-delay: 1.5s; }
.shape--circ2{ width:58px; height:58px; border-radius:50%; opacity:.25; animation-delay: .2s; }

/* Faster, slightly larger float travel so the movement reads clearly */
@keyframes float{
  0%,100%{ transform: translateY(0) rotate(0deg);}
  50%{ transform: translateY(-22px) rotate(7deg);}
}

.hero__inner{
  position:relative;
  z-index:2;
  width:100%;
  max-width:1180px;
  /* .hero__stats and the scroll-cue "mouse" indicator both live here
     as direct children, outside .hero__columns, and rely on this
     inherited text-align:center for their own auto-margin centering
     (scroll-cue is inline-flex, so it only centers via an ancestor's
     text-align, not through the flex box model). .hero__columns below
     overrides this back to left for the two-column text/illustration
     layout specifically. */
  text-align:center;
}

/* Two-column desktop layout: text left, illustration + social icons
   right (icons placed in their own row below the illustration, via the
   named areas below). .hero__content, .hero__social and .hero__visual
   are direct children/grid items here — .hero__social sits between the
   other two in markup only so that on mobile (flex-column, named areas
   ignored) it stacks in that same visual order without needing any
   extra CSS: right after the text content, before the illustration —
   i.e. its original position, unmoved. Collapses to that single
   stacked column at the existing 900px tablet breakpoint below — see
   Hero Responsive. */
.hero__columns{
  display:grid;
  grid-template-columns: 1.05fr 0.95fr;
  grid-template-areas:
    "content visual"
    "content social";
  align-items:center;
  column-gap:60px;
  row-gap:6px;
  text-align:left;
}
.hero__content{ grid-area:content; }
.hero__social{ grid-area:social; }

.hero__visual{
  grid-area:visual;
  display:flex;
  justify-content:center;
}

.hero__illustration-wrap{
  position:relative;
  width:100%;
  max-width:420px;
}
.hero__illustration-img{
  display:block;
  width:100%;
  height:auto;
  border-radius:var(--radius-l);
  border:1px solid var(--line);
  box-shadow:var(--shadow-m);
}

/* Floating personality bubbles — positioned around the image via a
   percentage offset from their near edge, so each one sits partly over
   the illustration's own edge rather than fully detached from it.
   Hidden/offset by default (hero-illustration.js fades + settles them
   in, with a safe fallback to this fully-visible resting state if that
   script or GSAP fails to load) and given a small triangular tail via
   ::after, pointing back toward the image on whichever side each
   bubble sits. */
.hero__bubble{
  position:absolute;
  z-index:3;
  max-width:170px;
  padding:9px 14px;
  background:var(--card-bg);
  border:1px solid var(--line);
  border-radius:14px;
  box-shadow:var(--shadow-m);
  font-family:var(--font-mono);
  font-size:12.5px;
  font-weight:600;
  line-height:1.35;
  color:var(--ink);
  /* No `transform` here — the idle float + hover lift/scale
     (hero-illustration.js) drive this bubble's transform directly via
     GSAP, so only the non-transform hover state animates via CSS,
     matching the same split already used for .project-card's tilt. */
  transition: box-shadow .3s var(--ease), border-color .3s var(--ease);
}
.hero__bubble:hover{
  box-shadow: 0 14px 28px rgba(36, 81, 184, .22);
  border-color: var(--blue);
}
.hero__bubble::after{
  content:'';
  position:absolute;
  top:50%;
  width:0;
  height:0;
  border-top:7px solid transparent;
  border-bottom:7px solid transparent;
  transform:translateY(-50%);
}
.hero__bubble--1{ top:-4%; left:-12%; }
.hero__bubble--2{ top:44%; right:-12%; }
.hero__bubble--3{ bottom:4%; left:-10%; }

.hero__bubble--1::after,
.hero__bubble--3::after{
  right:-7px;
  border-left:9px solid var(--card-bg);
  filter:drop-shadow(1px 0 0 var(--line));
}
.hero__bubble--2::after{
  left:-7px;
  border-right:9px solid var(--card-bg);
  filter:drop-shadow(-1px 0 0 var(--line));
}

.tag-badge{
  display:inline-flex;
  align-items:center;
  gap:7px;
  font-family: var(--font-mono);
  font-size:12px;
  color:var(--ink-dim);
  border:1px solid var(--line);
  padding:5px 12px;
  border-radius:999px;
  margin-bottom:22px;
  background:var(--card-bg);
}

.tag-badge__dot{
  width:6px;
  height:6px;
  border-radius:50%;
  background:var(--green);
  animation:pulse 2s infinite;
}

@keyframes pulse{
  0%,100%{ opacity:1;}
  50%{ opacity:.3;}
}

.hero__title{
  font-family:var(--font-display);
  font-weight:700;
  font-size:clamp(1.8rem, 4.2vw, 3rem);
  letter-spacing:-0.02em;
  margin-bottom:14px;
}

/* Restyled as a small pill/chip — the same shape/border/background
   language as .tag-badge just above it — instead of plain inline
   text+icon. inline-flex (not flex) so it shrinks to fit its content
   like a proper chip rather than stretching the row's full width. */
.hero__location{
  display:inline-flex;
  align-items:center;
  gap:7px;
  color:var(--ink-dim);
  font-family:var(--font-mono);
  font-size:.82rem;
  border:1px solid var(--line);
  background:var(--card-bg);
  padding:6px 13px 6px 10px;
  border-radius:999px;
  margin:0 0 14px;
  transition:border-color .3s var(--ease), box-shadow .3s var(--ease), transform .25s var(--ease);
}
.hero__location:hover{
  border-color:var(--blue);
  box-shadow:var(--shadow-s);
  transform:translateY(-2px);
}

.hero__location-icon{
  display:flex;
  align-items:center;
  justify-content:center;
  flex:none;
  color:var(--blue);
}
.hero__location-icon svg{ width:15px; height:15px; display:block; }

/* The pin (above) stays put on the left. This second icon — the
   Malagasy flag — stays collapsed and invisible at rest and smoothly
   grows in on the right on hover: width/margin animate alongside
   opacity so it reads as sliding/growing into view rather than just
   fading in place, and the chip (inline-flex, sized to its own
   content) widens to fit it in step. Its own SVG carries fixed flag
   colors (not currentColor), so it's sized here rather than through
   the shared icon-svg rule above, which assumes a square 15x15 glyph.
   Pure CSS: this element isn't touched by any animation script, so a
   plain transition is enough. */
.hero__location-icon--reveal{
  width:0;
  height:14px;
  margin-left:0;
  opacity:0;
  overflow:hidden;
  transition:width .35s var(--ease), margin-left .35s var(--ease), opacity .3s var(--ease);
}
.hero__location-icon--reveal svg{ width:20px; height:14px; flex:none; display:block; }
.hero__location:hover .hero__location-icon--reveal{
  width:20px;
  margin-left:7px;
  opacity:1;
}

.hero__role{
  font-family:var(--font-mono);
  font-size:clamp(1.2rem, 2.2vw, 1.6rem);
  color:var(--ink-dim);
  margin-bottom:22px;
  min-height:1.6em;
  display:flex;
  justify-content:flex-start;
  align-items: center;
  gap: 0.7rem;
  text-align:left;
  flex-wrap: wrap;
}
.hero__role--label{
  color: var(--ink-dim);
  font-weight: 500;
}
.hero__role .accent{
  color: var(--blue);
  font-weight: 700;
  min-width: 8ch;
}

.typing{
  display:inline-block;
  min-width:1ch;
  white-space:nowrap;
  overflow:hidden;
  letter-spacing:0.02em;
}

.typing-cursor{
  color:var(--blue);
  animation:blink 1s step-start infinite;
}

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

.hero__summary{
  font-size:clamp(1rem, 1.6vw, 1.15rem);
  color:var(--ink-dim);
  max-width:560px;
  margin:0 0 32px;
}

.hero__cta{
  display:flex;
  gap:14px;
  justify-content:flex-start;
  flex-wrap:wrap;
  margin-bottom:26px;
}

.btn{
  font-family:var(--font-body);
  font-weight:600;
  font-size:14.5px;
  padding:13px 24px;
  border-radius:var(--radius-m);
  transition:transform .2s var(--ease), box-shadow .2s var(--ease), background .2s;
  display:inline-flex;
  align-items:center;
  gap:8px;
}

.btn--primary{
  background:var(--blue);
  color:#fff;
  box-shadow:var(--shadow-s);
}

.btn--primary:hover{
  transform:translateY(-2px);
  box-shadow:var(--shadow-m);
}

.btn--ghost{
  border:1px solid var(--line);
  background:var(--card-bg);
}

.btn--ghost:hover{
  transform:translateY(-2px);
  border-color:var(--blue);
  color:var(--blue);
}

.btn--wide{
  width:100%;
  justify-content:center;
}

/* Icons for the two Hero CTA buttons — a lighter touch than the
   Email/LinkedIn/GitHub buttons' expand-on-hover pill treatment: these
   are already full text+icon buttons at rest, so the improvisation
   here is a small directional nudge on hover instead (trailing arrow
   moves toward where the click takes you; the download icon nudges
   down, echoing the action itself), reusing .btn's own existing
   `--ease` timing rather than introducing a new one. */
.btn__icon{
  display:flex;
  flex:none;
  transition:transform .25s var(--ease);
}
.btn__icon svg{ width:16px; height:16px; }

.btn--primary:hover .btn__icon--trail{ transform:translateX(4px); }
.btn--ghost:hover .btn__icon--lead{ transform:translateY(3px); }

/* Email/LinkedIn/GitHub buttons. Always centered: on desktop this sits
   under the illustration, which is itself centered within its grid
   column (.hero__visual), and on mobile it's centered along with the
   rest of the stacked content. A single, consistent gap between all
   three — same value everywhere (this task's spacing/width fixes are
   not desktop-only; only the illustration-proximity change below is). */
.hero__social{
  display:flex;
  justify-content:center;
  gap:14px;
}

/* The real flex item in the row above — hero-social-reveal.js animates
   ITS `width` (44px at rest → the shared expanded width), so a growing
   button pushes its neighbors along the row with a normal, smoothly
   reflowing flex layout, instead of overlaying them in place.
   flex:0 0 auto (not a fixed flex-basis) so that a plain `width`
   actually drives its size — GSAP sets `width` every frame, and the
   browser lays the row out fresh each time. */
.hero__social-item{
  flex:0 0 auto;
  width:44px;
  height:44px;
}

/* Icon-only at rest (this is also the reduced-motion / no-JS/no-GSAP
   fallback state). Fills its .hero__social-item parent exactly
   (width/height:100%), so it grows in step as that parent's width is
   animated. flex-shrink:0 on the icon and label below keeps their
   combined natural width intact even while this box itself is clamped
   to 44px, so overflow:hidden clips them correctly and scrollWidth
   (which the script reads to know how far to expand) reports their
   true expanded size rather than a shrunk one. padding-left is ALSO
   animated, alongside width — from 12px at rest (which centers the
   20px icon in this 44px box) to a value computed per-button so its
   own icon+label content ends up centered within the shared expanded
   width, even for a button whose own content (e.g. "Email") is
   narrower than the widest one ("LinkedIn") that width is based on.
   Animating padding-left as a plain number (rather than toggling
   justify-content) keeps one continuously-interpolated property doing
   the centering, so there's no discrete jump at any point in the
   animation. These sizes (item/btn width+height, padding-left) are
   mirrored as constants in hero-social-reveal.js — keep both in sync
   if either changes. */
.hero__social-btn{
  display:flex;
  align-items:center;
  justify-content:flex-start;
  width:100%;
  height:100%;
  overflow:hidden;
  padding-left:12px;
  border-radius:13px;
  background:var(--blue-dim);
  color:var(--blue);
  white-space:nowrap;
}
.hero__social-btn svg{ width:20px; height:20px; flex:none; }

.hero__social-btn__label{
  flex:none;
  opacity:0;
  padding-left:8px;
  padding-right:14px;
  font-family:var(--font-mono);
  font-size:14px;
  font-weight:600;
  white-space:nowrap;
}

.hero__stats{
  display:flex;
  justify-content:center;
  gap:14px;
  flex-wrap:wrap;
  margin-top:8px;
  padding-top:26px;
  border-top:1px solid var(--line);
}

/* Small icon-badge chips instead of plain stacked numbers — same
   improvisation as the Hero CTA buttons (a subtle icon nudge on
   hover, timed with the site's shared --ease), but as a lift + border
   tint on the whole card, matching the site's other bordered-card
   hover treatment (mini-card, project-card) rather than repeating the
   circular .ic badge already used everywhere else on the page. */
.stat{
  display:flex;
  align-items:center;
  gap:10px;
  padding:10px 16px;
  border-radius:var(--radius-m);
  background:var(--card-bg);
  border:1px solid var(--line);
  transition:transform .25s var(--ease), box-shadow .25s var(--ease), border-color .25s var(--ease);
}
.stat:hover{
  transform:translateY(-3px);
  box-shadow:var(--shadow-m);
  border-color:var(--blue);
}

.stat__icon{
  display:flex;
  flex:none;
  color:var(--blue);
  transition:transform .3s var(--ease);
}
.stat__icon svg{ width:18px; height:18px; }
.stat:hover .stat__icon{ transform:scale(1.15); }

.stat__body{
  display:flex;
  flex-direction:column;
  gap:1px;
}

.stat__num{
  font-family:var(--font-mono);
  font-weight:600;
  font-size:1.3rem;
  line-height:1.1;
  color:var(--blue);
}

.stat__label{
  font-size:.72rem;
  color:var(--ink-dim);
  white-space:nowrap;
}

/* Wrapper only positions things now — the visible "mouse" shape moved
   to .scroll-cue__mouse below so a second, touch-only variant
   (.scroll-cue__phone) can sit alongside it without inheriting an
   oval border that wouldn't suit a phone shape. */
.scroll-cue{
  display:inline-flex;
  margin:42px auto 0;
}

.scroll-cue__mouse{
  display:inline-flex;
  width:26px;
  height:42px;
  border:1.5px solid var(--line);
  border-radius:20px;
  justify-content:center;
  padding-top:8px;
}

.scroll-cue__dot{
  width:6px;
  height:14px;
  background:var(--blue);
  border-radius:3px;
  animation:scrolldown 1.8s infinite;
}

@keyframes scrolldown{
  0%{ opacity:1; transform:translateY(0);}
  70%{ opacity:0; transform:translateY(20px);}
  100%{ opacity:0;}
}

/* Touch "scroll down" hint — built the same way as .scroll-cue__mouse
   just above it (an outline shape with something scrolling down
   inside), just phone-shaped instead of mouse-shaped, and reusing that
   same scrolldown keyframe rather than a different motion. A small
   dynamic-island pill near the top (::before) reads it clearly as a
   phone rather than a generic rounded rectangle. Shown only when BOTH
   the mobile-width breakpoint AND a coarse (touch) pointer match — see
   the max-width:900px block below — so a desktop browser merely
   resized narrow still shows the mouse variant. Hidden by default
   here. */
.scroll-cue__phone{
  position:relative;
  display:none;
  align-items:flex-start;
  justify-content:center;
  width:24px;
  height:40px;
  border:1.5px solid var(--line);
  border-radius:7px;
  padding-top:13px;
}
.scroll-cue__phone::before{
  content:'';
  position:absolute;
  top:5px;
  left:50%;
  transform:translateX(-50%);
  width:9px;
  height:3px;
  border-radius:2px;
  background:var(--line);
  z-index:1;
}

/* A small Lucide "pointer" (hand + extended finger) standing in for
   the touch itself, in place of the plain dot this used to be. Its
   own animation (not the shared .scroll-cue__dot "scrolldown" one —
   that 20px travel was tuned for the taller 42px mouse shape, and
   would carry this icon past the phone's shorter ~24px inner screen
   area before the loop restarts) keeps the same 1.8s duration and
   0%/70%/100% rhythm, just scaled to a travel distance this smaller
   outline can actually contain. align-items/justify-content set
   explicitly (rather than left at their default stretch/flex-start)
   so the icon reliably centers instead of depending on flex defaults. */
.scroll-cue__phone-finger{
  position:relative;
  z-index:2;
  display:flex;
  align-items:center;
  justify-content:center;
  color:var(--blue);
  animation:scrolldown-phone 1.8s infinite;
}
.scroll-cue__phone-finger svg{ width:11px; height:11px; display:block; }
@keyframes scrolldown-phone{
  0%{ opacity:1; transform:translateY(10px);}
  70%{ opacity:0; transform:translateY(0);}
  100%{ opacity:0;}
}
@media (prefers-reduced-motion:reduce){
  .scroll-cue__phone-finger{
    animation:none;
    opacity:1;
    transform:none;
  }
}

/* ---------- Hero Responsive ---------- */

/* Laptop range: the section previously inherited desktop's 100vh + fixed
   90px top padding, which on shorter laptop viewports produced uneven,
   sometimes cramped or oversized gaps between the title/role/summary/CTA
   stack. This range gets its own predictable, non-vh-based padding and
   tighter, tuned spacing instead of relying on viewport height. */
@media (min-width:901px) and (max-width:1440px){

  .hero{
    min-height: auto;
    padding: 150px 40px 96px;
  }

  .hero__inner{
    max-width: 960px;
  }

  /* column-gap/row-gap set separately here (not the `gap` shorthand) so
     this narrower-viewport column spacing doesn't also widen row-gap
     back past the tighter base value above, which is what pulls the
     social buttons close to the illustration. */
  .hero__columns{ column-gap: 40px; row-gap: 6px; }
  .hero__illustration-wrap{ max-width: 360px; }

  .hero__title{ margin-bottom: 16px; }
  .hero__role{ margin-bottom: 20px; }
  .hero__summary{ margin-bottom: 26px; }
  .hero__cta{ margin-bottom: 22px; }
  .hero__social{ margin-bottom: 6px; }

  .hero__stats{
    margin-top: 10px;
    padding-top: 22px;
    gap: 12px;
  }
}

@media (max-width:900px){

  .hero{
    min-height:auto;
    padding:150px 24px 70px;
  }

  .hero__inner{
    width:100%;
    max-width:100%;
  }

  /* Stack back into a single column: text first, illustration below —
     plain DOM order already puts .hero__content before .hero__visual,
     so switching from grid to a column flex is enough. Text re-centers
     to match how this section already looked on mobile before the
     two-column restyle. */
  .hero__columns{
    display:flex;
    flex-direction:column;
    gap:36px;
    text-align:center;
  }
  .hero__role{ justify-content:center; text-align:center; }
  .hero__summary{ margin:0 auto 32px; }
  .hero__cta{ justify-content:center; }

  .hero__illustration-wrap{ max-width:300px; }
  .hero__bubble{ font-size:11.5px; padding:7px 11px; max-width:140px; }
  .hero__bubble--1{ left:-21%; }

  /* Stacked, the illustration (with bubbles peeking past its edges)
     sits directly above this bar — the base 8px margin-top was tuned
     for when a plain text column was there instead, and reads as too
     tight now. */
  .hero__stats{
    margin-top:32px;
    gap:12px;
  }

  /* Swap the mouse-wheel hint for a touch-appropriate one — same
     wrapper/margin, different visual metaphor. Nested inside an
     additional (pointer:coarse) check (not just this width breakpoint)
     so a desktop browser window merely resized narrow keeps showing
     the mouse variant; only an actual touch input device gets the
     phone one. Desktop (wide viewport) is unaffected regardless, since
     none of this exists outside the max-width:900px block at all. */
  @media (pointer:coarse){
    .scroll-cue__mouse{ display:none; }
    .scroll-cue__phone{
      display:inline-flex;
    }
  }

}

@media (max-width:600px){

  .hero{
    min-height:auto;
    padding:170px 24px 80px;
  }

  .hero__inner{
    width:100%;
    max-width:100%;
  }

  .hero__illustration-wrap{ max-width:230px; }
  .hero__bubble{ font-size:10.5px; padding:6px 9px; max-width:112px; border-radius:11px; }
  .hero__bubble::after{ border-top-width:5px; border-bottom-width:5px; }
  .hero__bubble--1::after,
  .hero__bubble--3::after{ border-left-width:7px; }
  .hero__bubble--2::after{ border-right-width:7px; }

  .hero__title{
    font-size:2.1rem;
    line-height:1.05;
    word-break:normal;
  }

  .hero__role{
    font-size:1rem;
    line-height:1.6;
  }

  .hero__summary{
    max-width:100%;
    font-size:.98rem;
    margin-bottom:26px;
  }

  .hero__cta{
    flex-direction:column;
    align-items:stretch;
    width:100%;
  }

  .hero__cta .btn{
    width:100%;
    justify-content:center;
  }

  .hero__stats{
    gap:10px;
    justify-content:center;
  }

  .stat{
    padding:9px 13px;
  }
  .stat__label{
    font-size:.68rem;
  }

  .scroll-cue{
    bottom:20px;
  }

}
/* ---------- Sections ---------- */
.section{ padding:110px 0; position: relative; overflow: hidden; }
.section--alt{ background:var(--bg-alt); }
.release-tag{
  font-family:var(--font-mono);
  font-size:12.5px;
  color:var(--blue);
  margin-bottom:10px;
  letter-spacing:.02em;
}

.section-title{
  font-family: var(--font-display);
  font-size: clamp(1.7rem, 3.4vw, 2.5rem);
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: 48px;
  max-width: 620px;
}

/* Shared heading-icon row, reused identically across every section for
   visual consistency. The icon itself reuses the .ic badge component
   (same look as the skills/experience/process icons) at a slightly
   larger size; purely decorative, so it stays static (no hover). */
.section-title-row{
  display:flex;
  align-items:center;
  gap: 14px;
  margin-bottom: 48px;
}
.section-title-row .section-title{
  margin-bottom: 0;
}
.section-icon{
  width: 42px;
  height: 42px;
  border-radius: 13px;
  flex: none;
}
.section-icon svg{ width: 20px; height: 20px; }

.reveal{ opacity:1; transform:translateY(0); transition: opacity .7s var(--ease), transform .7s var(--ease); }
.reveal.is-visible{ opacity:1; transform:translateY(0); }

/* Per-section decorative shapes layer — sits behind .section-inner
   (which is z-index:2) and above the section's own background, so every
   section down the page gets its own bit of floating motion. */
.bg-shapes{
  position:absolute;
  inset:0;
  overflow:hidden;
  pointer-events:none;
  z-index:0;
}

/* ---------- About ---------- */
.about__grid{ display:grid; grid-template-columns: 1.1fr 1fr; gap: 56px; align-items:start; }

.about__bio{ position: relative; padding-left: 26px; }
/* Accent line — draws in via GSAP ScrollTrigger (about-redesign.js).
   Defaults to fully-drawn (scaleY(1)) so it degrades safely if that
   script/GSAP fails to load, matching the same safe-fallback pattern
   used for the Experience timeline fill. */
.about__accent{
  position: absolute;
  left: 0;
  top: 2px;
  bottom: 2px;
  width: 3px;
  border-radius: 2px;
  background: linear-gradient(180deg, var(--blue), var(--green));
  transform-origin: top;
  transform: scaleY(1);
}
.about__bio p{ color: var(--ink-dim); margin-bottom: 18px; font-size: 1.02rem; }
.about__lead{
  display: block;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.2rem;
  line-height: 1.4;
  color: var(--ink);
  margin-bottom: 10px;
}

.about__cards{ display:flex; flex-direction:column; gap: 16px; perspective: 900px; }
.mini-card{
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-m);
  padding: 20px 22px;
  transition: box-shadow .25s var(--ease), border-color .25s var(--ease);
}
.mini-card:hover{ box-shadow: var(--shadow-m); border-color: var(--blue); }
.mini-card__tag{ font-family: var(--font-mono); font-size: 11px; color: var(--blue); }
.mini-card p{ margin-top: 8px; color: var(--ink-dim); font-size: .95rem; }

/* Identity cards (Mindset / Philosophy / Vision) — icon badge + a
   distinct accent color per card, drawn from the site's existing tag
   palette (Experience section). Tilt/transform on hover is handled
   entirely by about-redesign.js via GSAP (so it can combine rotateX/
   rotateY with a lift, without fighting a CSS :hover transform). */
.identity-card{ will-change: transform; }
.identity-card__icon{
  width: 40px;
  height: 40px;
  border-radius: 12px;
  margin-bottom: 12px;
  transition: transform .3s var(--ease);
}
.identity-card:hover .identity-card__icon{
  transform: scale(1.15) rotate(8deg);
}

.identity-card--mindset .identity-card__icon{ background: rgba(245,158,11,.16); color: #f59e0b; }
.identity-card--philosophy .identity-card__icon{ background: rgba(168,85,247,.14); color: #a855f7; }
.identity-card--vision .identity-card__icon{ background: rgba(20,184,166,.16); color: #14b8a6; }

.identity-card--mindset:hover{ border-color: rgba(245,158,11,.4); }
.identity-card--philosophy:hover{ border-color: rgba(168,85,247,.4); }
.identity-card--vision:hover{ border-color: rgba(20,184,166,.4); }

/* ---------- Skills ---------- */
.skills__grid{
  display:grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap:24px;
  margin-top: 24px;
}

.skill-card{
  position:relative;
  background: var(--card-bg);
  border:1px solid rgba(88,104,150,.12);
  border-radius:28px;
  padding:28px 26px 24px;
  box-shadow:0 26px 60px rgba(15,20,40,.04);
  transition:transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease), background .35s var(--ease);
  opacity: 1;
  transform: translateY(0);
  will-change: transform;
}

.skill-card::before{
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, rgba(100,144,255,.04), transparent 45%);
  opacity: 0;
  pointer-events: none;
  transition: opacity .35s var(--ease);
}

.skill-card:hover{
  transform: translateY(-5px);
  box-shadow:0 28px 60px rgba(15,20,40,.06);
  border-color: rgba(100,144,255,.18);
}

html[data-theme="dark"] .skill-card:hover{
  background: rgba(255,255,255,.025);
  border-color: rgba(100,144,255,.2);
  box-shadow:0 28px 60px rgba(0,0,0,.32);
}

.skill-card:hover::before{
  opacity: 1;
}

.skill-card.is-visible{
  opacity: 1;
  transform: translateY(0);
  transition-delay: var(--delay, 0s);
}

.skill-card h3{
  font-family: var(--font-display);
  font-size: 1.1rem;
  color: var(--ink);
  margin: 0 0 18px;
}

.skill-card__head{
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 18px;
}
.skill-card__head h3{
  margin-bottom: 0;
}

.skill-card__header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap: 16px;
  margin-bottom: 24px;
}

.skill-card__badge{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  padding: 7px 12px;
  border-radius: 999px;
  background: rgba(100,144,255,.1);
  color: var(--blue);
  font-size: .78rem;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
}

.skill-list{
  display:grid;
  gap:18px;
  padding:0;
  margin:0;
  list-style:none;
}

.skill-list li{
  display:grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto auto;
  grid-template-areas:
    "label"
    "bar";
  gap:.55rem;
  padding:16px 0;
  border-bottom:1px solid rgba(88,104,150,.10);
}

.skill-list li:last-child{
  border-bottom:none;
}

.skill-list li > span{
  grid-area: label;
  display:flex;
  align-items:center;
  gap:8px;
  font-size:.95rem;
  color:var(--ink);
  line-height:1.4;
}

.skill-list li{
  position: relative;
}

.skill-list li i{
  grid-area: bar;
  display:block;
  height:14px;
  border-radius:999px;
  background:var(--line);
  position:relative;
  overflow:hidden;
  min-width: 120px;
  box-shadow: inset 0 0 0 1px rgba(255,255,255,.08);
}

.skill-list li i::before{
  display: none;
}

.skill-list li i::after{
  content: '';
  position:absolute;
  left:0;
  top:0;
  bottom:0;
  width:0;
  border-radius:999px;
  background: linear-gradient(90deg, var(--progress-start, #60a5fa), var(--progress-end, #2563eb));
  box-shadow: inset 0 1px 6px rgba(0,0,0,.08);
  transition: width 1.2s cubic-bezier(0.4, 0, 0.2, 1);
}

.skill-list li.is-animated i::after{
  width: var(--progress, 0%);
}

.skill-list li:hover > span{
  color: var(--blue);
}

.skill-list li:hover i::after{
  filter: brightness(1.05);
}

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

/* ---------- Changelog / Experience ---------- */
.changelog-wrap{ position:relative; }
.changelog{ padding-left: 26px; margin: 0; list-style:none; }

/* The line is ONLY this animated fill — there is no static pre-drawn
   track, so nothing shows ahead of scroll progress. Drawn on scroll by
   experience-timeline.js via a GSAP ScrollTrigger scrub. Defaults to
   fully-drawn (scaleY(1)) so it degrades safely to a plain always-on
   line if that script/GSAP ever fails to load, or if the user prefers
   reduced motion. */
.changelog__fill{
  position:absolute;
  top:0;
  left:0;
  width:2px;
  height:100%;
  border-radius:2px;
  background: var(--blue);
}

.changelog__entry{ position:relative; padding: 24px 24px 44px 28px; margin-bottom: 32px; opacity:0; transform: translateY(24px) scale(0.98); filter: blur(1.8px); will-change: opacity, transform, filter; transition: opacity .65s var(--ease), transform .65s var(--ease), filter .65s var(--ease), box-shadow .65s var(--ease), border-radius .65s var(--ease), background .65s var(--ease); }
.changelog__entry.is-visible{ opacity:1; transform: translateY(0) scale(1); filter: blur(0); border-radius: 32px; box-shadow: 0 20px 45px rgba(36,81,184,.08); }

/* Light mode only: soft-edged card background. Fades from the page's
   own background color (var(--bg)) at the box's outer edges into the
   card tone (var(--card-bg)) toward the center/content, instead of a
   hard-edged flat fill. The 55% inner stop keeps a solid, fully-legible
   core over where the text/tags actually sit — only the outer margin
   fades. Dark mode is untouched (no rule added for it). Radial ellipse
   sizing auto-adapts to each entry's own box, so left-side, right-side,
   and full-width mobile entries all get a proportionate fade. */
html:not([data-theme="dark"]) .changelog__entry{
  background: radial-gradient(ellipse at center, var(--card-bg) 0%, var(--card-bg) 55%, var(--bg) 100%);
}

/* Bullet: a real element (not ::before) so experience-timeline.js can
   toggle .is-lit on it exactly when the scroll-fill reaches its position.
   Centered exactly on the 2px line (left:-31px = -(26px .changelog
   padding + half the 12px dot) so the line's own center lands under the
   dot's center); being fully opaque, the dot visually "cuts" the line
   wherever it sits — no separate line-gap technique needed. */
.changelog__dot{
  position:absolute; left: -31px; top: 4px;
  width: 12px; height:12px; border-radius:50%;
  background: var(--card-bg); border: 2px solid var(--blue);
  z-index: 2;
  /* A small halo matching the page background, so the line visibly
     stops just short of the dot instead of running flush into it. */
  box-shadow: 0 0 0 4px var(--bg);
  transition: background .3s var(--ease), box-shadow .3s var(--ease), transform .3s var(--ease);
}
.changelog__dot.is-lit{
  background: var(--blue);
  box-shadow: 0 0 0 4px var(--bg), 0 0 0 9px rgba(100,144,255,.2);
  transform: scale(1.15);
}

/* Desktop: centered line, one entry per row, alternating sides —
   first on the left, second lower-right, third lower-left, and so on. */
@media (min-width: 901px){
  .changelog{ padding-left: 0; }
  .changelog__fill{ left: calc(50% - 1px); }
  .changelog__entry{
    width: calc(50% - 48px);
    padding: 24px;
    margin-bottom: 64px;
  }
  .changelog__entry:nth-child(odd){ margin-left: 0; margin-right: auto; }
  .changelog__entry:nth-child(even){ margin-left: auto; margin-right: 0; }
  .changelog__dot{ left: auto; }
  .changelog__entry:nth-child(odd) .changelog__dot{ right: -54px; }
  .changelog__entry:nth-child(even) .changelog__dot{ left: -54px; }
}

.changelog__meta{ display:flex; align-items:center; gap:12px; margin-bottom: 8px; flex-wrap: wrap; }
.version-pill{
  font-family: var(--font-mono);
  font-size: 10.5px;
  padding: 3px 9px;
  border-radius: 999px;
  background: var(--bg-alt);
  color: var(--ink-dim);
  border: 1px solid var(--line);
}
.version-pill--current{ background: var(--green-dim); color: var(--green); border-color: transparent; }
.changelog__meta time{ font-family: var(--font-mono); font-size: 12px; color: var(--ink-dim); }
.changelog__entry h3{ font-family: var(--font-display); font-size: 1.2rem; margin-bottom: 6px; }
.changelog__entry .at{ color: var(--ink-dim); font-weight: 400; font-size: .95rem; }
.changelog__entry > p{ color: var(--ink-dim); margin-bottom: 12px; }

.changelog__list{ display:flex; flex-direction:column; gap: 14px; }
.changelog__list li{
  display:flex;
  flex-direction: column;
  align-items:flex-start;
  gap: 6px;
  font-size: .95rem;
}
.changelog__list strong{ color: var(--blue); }

.tag{
  display:inline-flex;
  align-items:center;
  gap:5px;
  flex:none;
  padding: 3px 10px 3px 8px;
  border-radius: 999px;
  font-family: var(--font-mono);
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .02em;
  white-space: nowrap;
}
.tag svg{ width:12px; height:12px; }
.tag-text{ width:100%; color: var(--ink-dim); line-height:1.55; }

.tag--added{ background: var(--green-dim); color: var(--green); }
.tag--improved{ background: var(--blue-dim); color: var(--blue); }
.tag--designed{ background: rgba(168,85,247,.14); color: #a855f7; }
.tag--led{ background: rgba(245,158,11,.16); color: #f59e0b; }
.tag--validated{ background: rgba(20,184,166,.16); color: #14b8a6; }

.changelog__tools{ display:flex; flex-wrap:wrap; gap:8px; margin-top: 18px; }

/* ---------- Projects: manual snap carousel ---------- */
.projects__viewport{ position: relative; }

.projects__grid{
  display: flex;
  flex-wrap: nowrap;
  gap: 24px;
  overflow-x: auto;
  /* Explicit, not left to default: with overflow-x set to anything but
     visible, a browser computes an unset overflow-y as auto rather than
     visible (per spec), NOT as "no vertical scrolling". Since the
     card__wrap blobs below are intentionally allowed to bleed past
     their own box (so they can peek past the card), that bleed counted
     as scrollable overflow on this container once overflow-y silently
     became auto — turning this row into an (invisible, scrollbar-less)
     vertical scroll container too. On touch devices that's enough for
     the browser to capture a vertical swipe that starts over a card for
     this element instead of letting it scroll the page, which is what
     was trapping scroll in this section. Pinning overflow-y to hidden
     keeps this container horizontal-only. */
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  -webkit-overflow-scrolling: touch;
  /* Generous room on every side (vs. the 4px this section used before
     the blob restyle) so the blurred blob behind each card has space to
     peek past the card's own edges without being clipped by this scroll
     container, and so the carousel doesn't feel cramped against the
     section's edges. Left is wider than the rest so the first card
     doesn't sit flush against the section's left edge. perspective
     matches .about__cards, for the reused 3D tilt hover below. */
  padding-top: 40px;
  padding-right: 28px;
  padding-bottom: 46px;
  padding-left: 56px;
  perspective: 900px;
  scrollbar-width: none;
}
.projects__grid::-webkit-scrollbar{ display: none; }

/* The flex/scroll-snap item is now this wrap, not .project-card itself,
   so the blob (below) can extend past the card's own overflow:hidden
   box while still being a sibling the scroll math can measure via
   offsetLeft (projects-carousel.js reads offsets on this element). */
.project-card__wrap{
  position: relative;
  flex: 0 0 min(360px, 85vw);
  scroll-snap-align: start;
}

/* Soft colored blob peeking from behind each card — a large, heavily
   blurred radial-gradient on an oversized, irregularly-rounded
   pseudo-element. It's generated before the card in paint order (this
   ::before is the wrap's first "child", the <article> that follows it
   in markup paints on top), and the card's own opaque background fully
   covers it within the card's own bounds — so no explicit stacking is
   needed for it to sit behind, only around, the card. Colors cycle
   through the same 5-color accent palette already used by the
   generative card visuals (project-visuals.js), one per carousel
   position. Purely decorative/static per prefers-reduced-motion. */
.project-card__wrap::before{
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 125%;
  height: 122%;
  transform: translate(-50%, -50%);
  border-radius: 42% 58% 63% 37% / 47% 44% 56% 53%;
  background: radial-gradient(circle, var(--blob-color) 0%, var(--blob-color) 40%, transparent 72%);
  filter: blur(38px);
  opacity: .55;
  pointer-events: none;
}
.projects__grid .project-card__wrap:nth-child(5n+1){ --blob-color: var(--blue); }
.projects__grid .project-card__wrap:nth-child(5n+2){ --blob-color: var(--green); }
.projects__grid .project-card__wrap:nth-child(5n+3){ --blob-color: #a855f7; }
.projects__grid .project-card__wrap:nth-child(5n+4){ --blob-color: #f59e0b; }
.projects__grid .project-card__wrap:nth-child(5n+5){ --blob-color: #14b8a6; }

.project-card{
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-l);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  /* No `transform` here: the 3D tilt hover (project-card-tilt.js, the
     same GSAP parameters used for the About section's identity cards)
     drives this element's transform directly. A CSS transition on
     transform would fight GSAP's own per-frame updates, so only the
     non-transform hover states animate via CSS. */
  transition: box-shadow .35s var(--ease), border-color .35s var(--ease);
  will-change: transform, box-shadow;
}
.project-card:hover{
  box-shadow: 0 20px 50px rgba(100, 144, 255, 0.12);
  border-color: rgba(100, 144, 255, 0.18);
}

/* Generative abstract visual — an SVG is injected here at runtime by
   project-visuals.js, seeded off data-seed so it's deterministic per
   project (same seed always produces the same pattern). */
.project-card__media{
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--bg-alt);
}
.project-card__media svg{ display: block; width: 100%; height: 100%; }

/* Hover overlay: a subtle, understated dim on the preview area (not a
   dramatic scrim) that also carries the short description up over the
   visual. */
.project-card__overlay{
  position: absolute;
  top: 0; left: 0; right: 0;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  padding: 18px;
  background: linear-gradient(180deg, transparent 40%, rgba(10,12,20,.45) 100%);
  opacity: 0;
  transform: translateY(10px);
  transition: opacity .3s var(--ease), transform .3s var(--ease);
  pointer-events: none;
}
.project-card__overlay p{
  color: #fff;
  font-size: .85rem;
  line-height: 1.5;
  margin: 0;
}
.project-card:hover .project-card__overlay,
.project-card:focus-within .project-card__overlay{
  opacity: 1;
  transform: translateY(0);
}

/* Devices with no hover capability (touch) can't reliably trigger the
   effect above — tapping a non-interactive card doesn't produce a
   dependable :hover state across mobile browsers. So the description
   stays pinned in place, on top of the preview image (same dim gradient
   + white text as the desktop hover state), instead of only showing up
   as a caption below the image and above the title. */
@media (hover: none){
  .project-card__overlay{
    opacity: 1;
    transform: none;
  }
}

.project-card__body{ padding: 22px 22px 24px; display: flex; flex-direction: column; flex: 1; }
.project-card__heading{ display: flex; align-items: flex-start; justify-content: space-between; gap: 10px; margin-bottom: 4px; }
.project-card__heading h3{ font-family: var(--font-display); font-size: 1.1rem; margin: 0; text-align: left; }
.project-card__tag{
  flex: none;
  margin-top: 2px;
  font-family: var(--font-mono);
  font-size: 9.5px;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 4px 9px;
  border-radius: 999px;
  background: var(--blue-dim);
  color: var(--blue);
  white-space: nowrap;
}
.project-card__role{ font-family: var(--font-mono); font-size: 11.5px; color: var(--blue); margin-bottom: 12px; }
.project-card__line{ font-size: .88rem; color: var(--ink-dim); margin-bottom: 6px; }
.project-card__line strong{ color: var(--ink); }
.project-card__tools{ display: flex; flex-wrap: wrap; gap: 8px; margin: 14px 0; }
.project-card__actions{ display: flex; gap: 16px; margin-top: auto; padding-top: 8px; }

/* Prev/next arrows */
.projects__arrow{
  position: absolute;
  top: 38%;
  transform: translateY(-50%);
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--card-bg);
  border: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--ink);
  box-shadow: var(--shadow-m);
  transition: border-color .2s var(--ease), color .2s var(--ease), transform .2s var(--ease);
  z-index: 2;
}
.projects__arrow svg{ width: 20px; height: 20px; }
.projects__arrow--prev{ left: -10px; }
.projects__arrow--next{ right: -10px; }
.projects__arrow:hover{
  background: var(--blue);
  border-color: var(--blue);
  color: #fff;
  transform: translateY(-50%) scale(1.06);
  box-shadow: 0 10px 26px rgba(36, 81, 184, .28);
}
.projects__arrow:disabled{ opacity: .35; pointer-events: none; }

/* Progress dots */
.projects__dots{ display: flex; justify-content: center; gap: 8px; margin-top: 18px; }
.projects__dots button{
  width: 8px;
  height: 8px;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: var(--line);
  transition: background .2s var(--ease), transform .2s var(--ease);
}
.projects__dots button.is-active{ background: var(--blue); transform: scale(1.3); }

@media (max-width: 700px){
  /* Mobile intentionally has no card-sizing/peek system of its own
     anymore — .project-card__wrap's base rule (flex: 0 0 min(360px,
     85vw); scroll-snap-align: start) already shrinks the card to fit
     narrow viewports via the 85vw fallback, so it's reused as-is here.
     The only mobile-specific rule left is hiding the round arrow
     buttons, since there's no room for them and touch users swipe
     instead. */
  .projects__arrow{ display: none; }
}

/* The tilt hover itself is skipped entirely under reduced motion (see
   project-card-tilt.js) — this just makes the remaining hover states
   (dim overlay, shadow/border) snap instead of animate, instead of
   leaving their normal-speed transitions running on their own. Blob
   shapes are static regardless and need no reduced-motion rule. */
@media (prefers-reduced-motion: reduce){
  .project-card,
  .project-card__overlay{
    transition-duration: .001s;
  }
}
.link-btn{ font-size: .88rem; font-weight:600; color: var(--blue); position:relative; }
.link-btn::after{ content:''; position:absolute; left:0; right:0; bottom:-3px; height:1px; background: var(--blue); transform: scaleX(0); transition: transform .25s var(--ease); transform-origin: left; }
.link-btn:hover::after{ transform: scaleX(1); }
.link-btn--muted{ color: var(--ink-dim); }
.link-btn--muted::after{ background: var(--ink-dim); }

/* Projects action links only: a trailing arrow that nudges forward on
   hover ("Read case study →"), matching the reference's "text link
   with an arrow" style. Scoped to .project-card__actions so the
   underline treatment above stays exactly as-is everywhere else
   (Certifications footer links, etc.) — the underline ::after is
   swapped for this arrow glyph only inside Projects, not removed
   globally. */
.project-card__actions .link-btn{ display: inline-flex; align-items: center; gap: 4px; }
.project-card__actions .link-btn::after{
  content: "\2192";
  position: static;
  background: none;
  height: auto;
  transform: none;
  transition: transform .25s var(--ease);
}
.project-card__actions .link-btn:hover::after{ transform: translateX(3px); }

/* ---------- Pipeline ---------- */
.pipeline{
  display:grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 24px;
  margin-top: 32px;
}
.pipeline__stage{
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: 28px;
  padding: 28px 24px;
  text-align:left;
  font-size: .95rem;
  font-weight: 600;
  position:relative;
  counter-increment: stage;
  transition: opacity .55s var(--ease), transform .55s var(--ease), border-color .35s var(--ease), box-shadow .35s var(--ease), background .35s var(--ease), color .35s var(--ease);
  opacity: 0;
  transform: translateY(32px);
}

/* Persistent step icon, reusing the shared .ic badge (same component +
   hover treatment as the skills/experience-tools icons). Sized like the
   old number-circle it replaces. */
.pipeline__icon{
  width: 44px;
  height: 44px;
  border-radius: 14px;
  margin-bottom: 14px;
}

.pipeline__stage h3{
  font-family: var(--font-display);
  font-size: 1.05rem;
  color: var(--ink);
  margin: 0;
}

/* "Step #N" badge: hidden by default, fades/scales in from the top-right
   corner on hover and reverses on mouse-leave (plain CSS transition, no
   JS/GSAP). Reuses the existing counter (no HTML numbering needed).
   prefers-reduced-motion already forces near-zero transition-durations
   site-wide (see the global rule near the top of this file), so this
   automatically becomes an instant show/hide for those users. */
.pipeline__stage::before{
  content: "Step " counter(stage, decimal-leading-zero);
  position: absolute;
  top: 16px;
  right: 16px;
  padding: 4px 10px;
  border-radius: 999px;
  background: var(--blue);
  color: #fff;
  font-family: var(--font-mono);
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .03em;
  white-space: nowrap;
  opacity: 0;
  transform: scale(.85) translateY(-4px);
  transition: opacity .3s var(--ease), transform .3s var(--ease);
  pointer-events: none;
}
.pipeline__stage:hover::before{
  opacity: 1;
  transform: scale(1) translateY(0);
}

.pipeline__stage.is-visible{
  opacity: 1;
  transform: translateY(0);
}
.pipeline__stage.is-active{
  background: var(--blue);
  color: #fff;
  border-color: var(--blue);
  box-shadow: 0 30px 70px rgba(36,81,184,.12);
}
.pipeline__stage.is-active::before{
  background: rgba(255,255,255,.18);
  color: #fff;
}
.pipeline__stage.is-active h3{ color: #fff; }
.pipeline__stage.is-done{
  background: var(--green-dim);
  border-color: transparent;
  color: var(--green);
}
.pipeline__stage.is-done::before{ background: rgba(255,255,255,.24); color: #fff; }
.pipeline__stage:hover{ transform: translateY(-4px); box-shadow: 0 20px 45px rgba(36,81,184,.08); }

@media (max-width: 920px){
  .pipeline{
    grid-template-columns: 1fr;
    gap: 24px;
  }
}

/* ---------- Education ---------- */
.education__intro{
  max-width: 720px;
  margin: -8px 0 28px;
  color: var(--ink-dim);
  font-size: 1rem;
}
.education__grid{
  display:grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 24px;
}
.education-card{
  position: relative;
  background: linear-gradient(145deg, var(--card-bg), var(--bg-alt));
  border: 1px solid var(--line);
  border-radius: 24px;
  padding: 24px;
  overflow: hidden;
  transition: transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease), background .35s var(--ease);
  opacity: 0;
  transform: translateY(22px);
}
.education-card::before{
  content: '';
  position: absolute;
  right: -18px;
  bottom: -30px;
  width: 140px;
  height: 140px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(36,81,184,.16), transparent 72%);
  transition: transform .45s var(--ease);
}
.education-card.is-visible{ opacity: 1; transform: translateY(0); }
.education-card:hover{
  transform: translateY(-10px);
  box-shadow: 0 24px 70px rgba(20,20,15,.12);
  border-color: rgba(36,81,184,.16);
}
.education-card:hover::before{ transform: scale(1.09); }
.education-card__header{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 14px;
}
.education-card__title-group{ display:flex; align-items:center; gap:10px; }
.education-card__icon{ width:34px; height:34px; border-radius:10px; }
.education-card__tag{
  display:inline-flex;
  align-items:center;
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--blue-dim);
  color: var(--blue);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .04em;
}
.education-card__badge{
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--ink-dim);
  background: var(--bg-alt);
  padding: 5px 10px;
  border-radius: 999px;
  border: 1px solid var(--line);
}
.education-card h3{ font-family: var(--font-display); font-size: 1.08rem; margin-bottom: 6px; }
.education-card__meta{
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--blue);
  margin-bottom: 10px;
}
.education-card__body{
  color: var(--ink-dim);
  font-size: .95rem;
  line-height: 1.65;
}

@media (max-width: 640px){
  .education-card{ padding: 20px; }
  .education-card__header{ align-items:flex-start; }
}

/* ---------- Languages ----------
   Restyled to share the Experience section's visual language: same
   32px corner radius and blue-tinted soft shadow as
   .changelog__entry.is-visible, and the exact .tag / .tag--* pill
   classes from the Experience listing badges reused (not duplicated)
   for the proficiency label. Reveal is still the plain
   IntersectionObserver + .is-visible system already used here — no
   GSAP, no timeline mechanic, just the look. */
.languages__panel{
  background: linear-gradient(135deg, var(--card-bg), var(--bg-alt));
  border: 1px solid var(--line);
  border-radius: 32px;
  padding: 28px;
  box-shadow: 0 20px 45px rgba(36,81,184,.08);
}
.languages__headline{ margin-bottom: 22px; }
.languages__headline p{ color: var(--ink-dim); max-width: 700px; font-size: 1rem; }
.languages__list{ display:grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 18px; }
.language-card{
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: 24px;
  padding: 20px;
  transition: transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease);
}
.language-card:hover{ transform: translateY(-6px); box-shadow: 0 20px 45px rgba(36,81,184,.08); border-color: rgba(36,81,184,.16); }
.language-card__top{ display:flex; align-items:center; gap: 12px; margin-bottom: 14px; }

/* Flags are identity markers, not actions — kept static (no hover
   lift/tint like the .ic tool icons elsewhere on the site). */
.language-card__flag{
  flex: none;
  width: 42px;
  aspect-ratio: 4 / 3;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid var(--line);
  display:grid;
  place-items:center;
  background: var(--bg-alt);
}
.language-card__flag .fi{
  width: 100%;
  height: 100%;
  border-radius: 0;
}

.language-card h3{ font-family: var(--font-display); font-size: 1.05rem; margin-bottom: 6px; }
.language-card .tag{ padding: 3px 10px; }

.language-card__bar{ width: 100%; height: 10px; border-radius: 999px; background: var(--bg-alt); overflow: hidden; position: relative; }
.language-card__bar span{
  display:block;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--bar-start, var(--blue)), var(--bar-end, var(--green)));
  transition: width 1.1s cubic-bezier(.22,.61,.36,1);
  box-shadow: inset 0 1px 4px rgba(255,255,255,.2);
}
.language-card.is-visible .language-card__bar span{ width: var(--level, 0%); }

@media (max-width: 640px){
  .languages__panel{ padding: 22px; }
}

/* ---------- Certifications ---------- */
.certs__grid{ display:grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 28px; }
.cert-card{
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: 24px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  min-height: 100%;
  box-shadow: 0 10px 30px rgba(20,20,15,.04);
  transition: transform .35s var(--ease), box-shadow .35s var(--ease), border-color .35s var(--ease);
  opacity: 0;
  transform: translateY(28px);
}
.cert-card.is-visible{ opacity: 1; transform: translateY(0); }
.cert-card:hover{ transform: translateY(-8px); box-shadow: 0 22px 48px rgba(20,20,15,.10); border-color: rgba(36,81,184,.22); }
.cert-card__cover{
  width: 100%;
  aspect-ratio: 1200 / 630;
  overflow: hidden;
  background: var(--bg-alt);
  border-bottom: 1px solid var(--line);
}
.cert-card__cover img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform .5s var(--ease);
}
.cert-card:hover .cert-card__cover img{
  transform: scale(1.05);
}
.cert-card__body{
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 22px 24px 24px;
  flex: 1;
}
.cert-card__title-group{ display:flex; align-items:center; gap:8px; margin-bottom:4px; }
.cert-card__icon{ width:32px; height:32px; border-radius:9px; }
.cert-card__title-group .cert-card__mono{ margin-bottom: 0; }
.cert-card__mono{
  display:inline-block;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--blue);
  background: var(--blue-dim);
  padding: 4px 10px;
  border-radius: 8px;
  margin-bottom: 4px;
  width: max-content;
}
.cert-card h3{ font-size: 1.08rem; font-family: var(--font-display); margin-bottom: 0; line-height: 1.35; }
.cert-card__meta{ color: var(--ink-dim); font-size: .92rem; }
.cert-card__note{ color: var(--ink-dim); font-size: .85rem; font-style: italic; margin-top: -4px; }
.cert-card__footer{
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap: 12px;
  margin-top: auto;
  padding-top: 16px;
  border-top: 1px solid var(--line);
}
.cert-card__footer time{ color: var(--ink-dim); font-size: .85rem; }
.cert-card__footer .link-btn{ font-size: .85rem; }

.hero__title,
.hero__location,
.hero__role,
.hero__summary,
.hero__cta,
.hero__stats,
.release-tag,
.cert-card{
  animation: motionRise .85s ease both;
}

.hero__title{ animation-delay: 0.1s; }
.hero__location{ animation-delay: 0.14s; }
.hero__role{ animation-delay: 0.18s; }
.hero__summary{ animation-delay: 0.24s; }
.hero__cta{ animation-delay: 0.3s; }
.hero__stats{ animation-delay: 0.34s; }
.release-tag{ animation-delay: 0.1s; }
.cert-card{ opacity: 0; transform: translateY(20px); }

/* .section-title and .project-card entrance animation is handled by
   GSAP ScrollTrigger in scroll-animations.js; if that script fails to
   load these remain at their default visible, static-position state. */

@keyframes motionRise{
  from{ opacity: 0; transform: translateY(18px) scale(0.99); }
  to{ opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes pulseGlow{
  0%,100%{ box-shadow: 0 0 0 rgba(36,81,184,0); }
  50%{ box-shadow: 0 12px 50px rgba(36,81,184,.08); }
}

.btn--primary:hover,
.btn--ghost:hover{
  animation: pulseGlow 1.8s ease 1;
}

/* ---------- Testimonials: chat message ---------- */
.testimonial{ max-width: 640px; margin: 0 auto; text-align:left; }

.chat-message{ display:flex; flex-direction:column; align-items:flex-start; gap: 14px; }

.chat-message__sender{ display:flex; align-items:center; gap: 12px; }

/* Same rounded-shape + drift animation as the header's own avatar
   (.nav__avatar / @keyframes avatarDrift, both left untouched) — just
   reused on this new element at a slightly larger size. */
.testimonial__avatar{
  width: 44px;
  height: 44px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
  border: 2px solid var(--blue);
  box-shadow: var(--shadow-s);
  animation: avatarDrift 5s ease-in-out infinite;
}
.testimonial__avatar img{
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.chat-message__name{
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1rem;
  color: var(--ink);
}

.chat-message__bubble{
  position: relative;
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: 22px;
  padding: 22px 24px;
  max-width: 560px;
  box-shadow: 0 20px 45px rgba(36,81,184,.08);
}
.chat-message__bubble::before{
  content: '';
  position: absolute;
  top: -8px;
  left: 30px;
  width: 18px;
  height: 18px;
  background: var(--card-bg);
  border-left: 1px solid var(--line);
  border-top: 1px solid var(--line);
  border-radius: 4px 0 0 0;
  transform: rotate(45deg);
}
.chat-message__bubble p{
  font-family: var(--font-display);
  font-size: clamp(1.05rem, 2vw, 1.3rem);
  line-height: 1.55;
  color: var(--ink);
  margin: 0;
}

/* ==========================================================
   CONTACT — redesigned: availability badges, icon contact
   list, floating-label modern form, animated accents.
   ========================================================== */
.contact__badges{
  display:flex;
  flex-wrap:wrap;
  gap:10px;
  margin: -22px 0 40px;
}
.mini-badge{
  display:inline-flex;
  align-items:center;
  gap:7px;
  font-family: var(--font-mono);
  font-size: 11.5px;
  color: var(--ink-dim);
  border: 1px solid var(--line);
  background: var(--card-bg);
  padding: 7px 13px;
  border-radius: 999px;
  transition: transform .25s var(--ease), border-color .25s, color .25s;
}
.mini-badge:hover{ transform: translateY(-2px); border-color: var(--blue); color: var(--ink); }
.mini-badge__dot{
  width:6px; height:6px; border-radius:50%;
  background: var(--green);
  animation: pulse 2s infinite;
}

.contact__grid{ display:grid; grid-template-columns: 1fr 1.15fr; gap: 56px; align-items:start; }

.contact__info > p{ color: var(--ink-dim); margin-bottom: 26px; }

/* Reuses the site's existing .reveal + IntersectionObserver mechanism
   (script.js already watches any ".reveal" element and toggles
   .is-visible on scroll-into-view) — this is the same system other
   sections rely on, just given a real fade/slide transition here. */
.contact__location{
  display: flex;
  align-items: center;
  gap: 6px;
  opacity: 0;
  transform: translateY(12px);
  transition: opacity .6s var(--ease), transform .6s var(--ease);
}
.contact__location.is-visible{ opacity: 1; transform: translateY(0); }
.contact__location svg{ width: 15px; height: 15px; color: var(--blue); flex: none; }

.contact__list{ margin-bottom: 30px; display:flex; flex-direction:column; gap: 4px; }
.contact__list li{
  display:flex;
  align-items:center;
  gap: 14px;
  padding: 13px 8px;
  border-bottom: 1px solid var(--line);
  border-radius: var(--radius-s);
  transition: background .25s var(--ease), padding-left .25s var(--ease);
}
.contact__list li:hover{ background: var(--card-bg); padding-left: 14px; }
.contact__list li:last-child{ border-bottom: none; }

/* Shared icon-badge component: originally built for the contact list,
   now reused verbatim (base look + hover animation) by the skills
   section too, via .ic / .ic--sm and the shared hover-trigger list
   below. Any future tweak here applies everywhere it's used. */
.ic{
  flex: none;
  width: 38px; height: 38px;
  border-radius: 11px;
  display:flex; align-items:center; justify-content:center;
  background: var(--blue-dim);
  color: var(--blue);
  transition: transform .3s var(--ease), background .3s var(--ease), color .3s var(--ease);
}
.ic svg{ width:18px; height:18px; }

.ic--sm{
  width: 24px; height: 24px;
  border-radius: 8px;
}
.ic--sm svg{ width:13px; height:13px; }

.contact__list li:hover .ic,
.skill-card:hover .ic,
.skill-list li:hover .ic,
.changelog__tools .ic:hover,
.pipeline__stage:hover .ic,
.education-card:hover .ic,
.cert-card:hover .ic,
.footer__social .ic:hover{
  transform: translateY(-3px) rotate(-6deg);
  background: var(--blue);
  color: #fff;
}

.contact__list .meta{ display:flex; flex-direction:column; gap: 2px; min-width:0; }
.contact__list .meta > span{
  font-family: var(--font-mono);
  font-size: .72rem;
  letter-spacing: .04em;
  color: var(--ink-dim);
  text-transform: uppercase;
}
.contact__list .meta a{
  font-size: .96rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.contact__list .meta a:hover{ color: var(--blue); }

/* ---------- Modern floating-label form ---------- */
.contact__form{
  position: relative;
  background: var(--card-bg);
  border: 1px solid var(--line);
  border-radius: 28px;
  padding: 34px 32px 30px;
  display:flex; flex-direction:column; gap: 22px;
  overflow: hidden;
  transition: transform .35s var(--ease), box-shadow .35s var(--ease);
}
.contact__form::before{
  content:'';
  position:absolute; top:0; left:0; right:0; height:3px;
  background: linear-gradient(90deg, var(--blue), var(--green));
}
.contact__form:hover{
  transform: translateY(-4px);
  box-shadow: 0 20px 45px rgba(36,81,184,.08);
}

.field{
  display:flex;
  flex-direction:column;
  gap:6px;
}

/* ---------- Floating Label Fields ---------- */
.field--float{
  position:relative;
  display:block;
}

.field--float input,
.field--float textarea{
  width:100%;
  background:var(--bg);
  border:1px solid var(--line);
  border-radius:var(--radius-s);
  padding:18px 14px 18px 44px;
  color:var(--ink);
  font-size:.95rem;
  transition:
    border-color .25s var(--ease),
    box-shadow .25s var(--ease);
  resize:vertical;
}

.field--float textarea{
  padding-top:20px;
}

/* Focus effect */
.field--float input:focus,
.field--float textarea:focus{
  border-color:var(--blue);
  box-shadow:0 0 0 4px var(--blue-dim);
  outline:none;
}

/* Field icon — reuses the shared .ic badge (same component used for
   skills/tools/process icons), sized down for a text input. Purely
   decorative, never intercepts clicks. */
.field__icon{
  position:absolute;
  left:9px;
  top:50%;
  transform:translateY(-50%);
  width:26px;
  height:26px;
  border-radius:8px;
  z-index:1;
  pointer-events:none;
}
.field__icon svg{ width:13px; height:13px; }
.field__icon--top{ top:20px; transform:none; }

/* Floating label default position (leaves room for the icon) */
.field--float label{
  position:absolute;
  left:44px;
  top:50%;
  transform:translateY(-50%);
  font-size:.92rem;
  color:var(--ink-dim);
  pointer-events:none;
  padding:0 5px;
  background:var(--bg);

  transition:
    top .2s var(--ease),
    left .2s var(--ease),
    transform .2s var(--ease),
    font-size .2s var(--ease),
    color .2s var(--ease);
}


/* Move label above input, back to the box's own corner */
.field--float input:focus + label,
.field--float input:not(:placeholder-shown) + label,
.field--float textarea:focus + label,
.field--float textarea:not(:placeholder-shown) + label{

  top:0;
  left:14px;
  transform:translateY(-50%);
  font-size:.68rem;
  font-family:var(--font-mono);
  letter-spacing:.04em;
  color:var(--blue);
  text-transform:uppercase;
}

/* Success checkmark — fades/scales in once a field has content and
   passes native HTML5 validation (required/minlength/type=email), with
   no JS involved. */
.field__check{
  position:absolute;
  right:14px;
  top:50%;
  transform:translateY(-50%) scale(.5);
  width:20px;
  height:20px;
  color: var(--green);
  opacity:0;
  pointer-events:none;
  transition: opacity .25s var(--ease), transform .25s var(--ease);
}
.field__check svg{ width:100%; height:100%; }
.field__check--top{ top:20px; transform:scale(.5); }

.field--float input:not(:placeholder-shown):valid ~ .field__check,
.field--float textarea:not(:placeholder-shown):valid ~ .field__check{
  opacity:1;
  transform:translateY(-50%) scale(1);
}
.field--float textarea:not(:placeholder-shown):valid ~ .field__check--top{
  transform: scale(1);
}

/* ---------- Error / Status Messages ---------- */
.field.has-error input,
.field.has-error textarea{
  border-color:#C4483B;
}
.field.has-error{
  animation: fieldShake .4s var(--ease);
}
@keyframes fieldShake{
  0%, 100%{ transform: translateX(0); }
  20%{ transform: translateX(-6px); }
  40%{ transform: translateX(5px); }
  60%{ transform: translateX(-4px); }
  80%{ transform: translateX(3px); }
}

.field__error{
  font-size:.78rem;
  color:#C4483B;
  min-height:14px;
}

.field__note{
  font-size:.88rem;
  color:var(--green);
  min-height:18px;
  margin:0;
}

.field__disclaimer{
  font-size:.76rem;
  color:var(--ink-dim);
  margin:0;
}

.btn--submit{
  position: relative;
  overflow: hidden;
}
.btn--submit .arrow{
  display: inline-block;
  transition: transform .3s var(--ease);
}
.btn--submit:hover .arrow{ transform: translateX(5px); }

/* ---------- Footer ---------- */
.footer{
  padding: 84px 0 0;
  border-top: 1px solid var(--line);
  position: relative;
  overflow: hidden;
  background: linear-gradient(180deg, var(--bg) 0%, var(--bg-alt) 100%);
}
.footer__inner{ position: relative; z-index: 2; }

/* Reuses the site's existing .reveal + IntersectionObserver mechanism
   (script.js already watches any ".reveal" element and toggles
   .is-visible on scroll-into-view) — same system every other section's
   entrance relies on, one fade/slide for the whole content grid rather
   than staggering each column separately. */
.footer__top{
  display: grid;
  grid-template-columns: 1.3fr 1fr 1fr;
  gap: 48px;
  padding-bottom: 48px;
  opacity: 0;
  transform: translateY(24px);
  transition: opacity .7s var(--ease), transform .7s var(--ease);
}
.footer__top.is-visible{ opacity: 1; transform: translateY(0); }

.footer__brand-name{
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.15rem;
  margin-bottom: 10px;
}
.footer__tagline{
  color: var(--ink-dim);
  font-size: .92rem;
  line-height: 1.6;
  max-width: 340px;
  margin-bottom: 22px;
}
.footer__social{ display: flex; gap: 10px; }

.footer__col-title{
  font-family: var(--font-mono);
  font-size: .72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--ink-dim);
  margin-bottom: 18px;
}

.footer__links{ display: grid; grid-template-columns: 1fr 1fr; gap: 2px 16px; }
.footer__links a{
  display: inline-block;
  position: relative;
  color: var(--ink-dim);
  font-size: .92rem;
  padding: 6px 0;
  transition: color .2s var(--ease);
}
.footer__links a::after{
  content:'';
  position: absolute;
  left: 0; right: 100%; bottom: 3px;
  height: 1px;
  background: var(--blue);
  transition: right .25s var(--ease);
}
.footer__links a:hover{ color: var(--blue); }
.footer__links a:hover::after{ right: 0; }

.footer__contact-line{
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: .92rem;
  color: var(--ink-dim);
  margin-bottom: 12px;
}
.footer__contact-line a{ color: var(--ink); transition: color .2s var(--ease); }
.footer__contact-line a:hover{ color: var(--blue); }

.footer__cta{ margin-top: 10px; }

.footer__bottom{
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 12px;
  padding: 22px 0;
  border-top: 1px solid var(--line);
  font-size: .82rem;
  color: var(--ink-dim);
}
.footer__mono{ font-family: var(--font-mono); }
.footer__top-link{
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--ink-dim);
  transition: color .2s var(--ease), transform .2s var(--ease);
}
.footer__top-link:hover{ color: var(--blue); transform: translateY(-2px); }

/* Matches the site's standard tablet-and-below breakpoint (same one
   .about__grid/.contact__grid collapse at) rather than a one-off value.
   Everything centers here — the desktop grid's left-aligned columns
   only read cleanly side-by-side; stacked, centered content matches
   how the rest of the site already treats its mobile layout (Hero,
   etc.). Each stacked block also gets its own top divider instead of
   relying on gap alone, so the three sections (brand, links, contact)
   read as distinct, organized pieces rather than one long run of
   center-aligned text. */
@media (max-width: 900px){
  .footer__top{
    grid-template-columns: 1fr;
    gap: 0;
    text-align: center;
  }
  .footer__tagline{ max-width: 100%; margin-left: auto; margin-right: auto; }
  .footer__social{ justify-content: center; }
  .footer__contact-line{ justify-content: center; }

  .footer__col{
    padding-top: 32px;
    margin-top: 32px;
    border-top: 1px solid var(--line);
  }

  .footer__links{
    grid-template-columns: 1fr;
    gap: 0;
    max-width: 260px;
    margin: 0 auto;
  }

  .footer__bottom{ justify-content: center; text-align: center; }
}

/* ---------- Back to top ---------- */
.back-to-top{
  position: fixed; bottom: 28px; right: 28px;
  width: 44px; height:44px; border-radius:50%;
  background: var(--blue); color:#fff;
  display:flex; align-items:center; justify-content:center;
  box-shadow: var(--shadow-m);
  opacity:0; transform: translateY(12px); pointer-events:none;
  transition: opacity .3s var(--ease), transform .3s var(--ease);
  z-index: 150;
}
.back-to-top.is-visible{ opacity:1; transform: translateY(0); pointer-events:auto; }
.back-to-top:hover{ filter: brightness(1.08); }

/* ---------- Responsive ---------- */
@media (max-width: 900px){
  .about__grid, .contact__grid{ grid-template-columns:1fr; }

  .nav__links{
    display:flex;
    flex-direction:column;
    position:absolute;
    top:58px;
    left:0;
    right:0;
    background:var(--card-bg);
    border-bottom:1px solid var(--line);
    padding:12px 20px 20px;
    gap:4px;

    /* animation */
    opacity:0;
    transform:translateY(-15px);
    pointer-events:none;
    transition:
      opacity .3s var(--ease),
      transform .3s var(--ease);
  }

  .nav__burger{
    display:flex;
  }

  .nav.menu-open .nav__links{
    opacity:1;
    transform:translateY(0);
    pointer-events:auto;
  }
}

/* ---------- Signature effects (binary trail, split-text, clip-path, scramble) ---------- */
/* Populated/toggled entirely from signature-effects.js; see that file for behavior. */
/* Note: the OS default cursor is left untouched — only the "0"/"1" trail is added. */

.sig-binary{
  position: fixed;
  top: 0;
  left: 0;
  pointer-events: none;
  z-index: 9998;
  font-family: var(--font-mono);
  color: #fff;
  mix-blend-mode: difference;
  will-change: transform, opacity;
  -webkit-user-select: none;
  user-select: none;
}

.sig-split-char{
  display: inline-block;
  will-change: transform, opacity;
}

@media (prefers-reduced-motion: reduce){
  .sig-binary{
    display: none !important;
  }
}

@media (max-width:600px){
  .section{ padding:76px 0; }
  .section-inner{ padding:0 20px; }
  .hero__cta{ flex-direction:column; align-items:stretch; }
  .pipeline__stage{ flex:1 1 100%; }
  .contact__badges{ margin-top: -14px; }
  .contact__form{ padding: 26px 22px 24px; }
}