/* ==========================================================================
   Nazmul Hasan - front-end theme, 2026 redesign
   One stylesheet for the public site. Admin keeps assets/css/admin.css.
   ========================================================================== */

:root{
  --bg:#070a10;
  --bg-2:#0a0f18;
  --surface:rgba(255,255,255,.035);
  --surface-2:rgba(255,255,255,.06);
  --line:rgba(255,255,255,.09);
  --line-strong:rgba(255,255,255,.16);
  --text:#f2f6fb;
  --muted:#93a3b8;
  --accent:#2ee6a8;
  --accent-soft:rgba(46,230,168,.12);
  --accent-2:#6aa9ff;
  --radius:18px;
  --radius-sm:12px;
  --shadow:0 30px 80px -30px rgba(0,0,0,.9);
  --container:1180px;
  --ease:cubic-bezier(.22,.8,.28,1);
}

*,*::before,*::after{box-sizing:border-box}
html{scroll-behavior:smooth;scroll-padding-top:96px;-webkit-text-size-adjust:100%}
body{
  margin:0;
  background:var(--bg);
  color:var(--text);
  font-family:Inter,system-ui,-apple-system,"Segoe UI",sans-serif;
  font-size:16.5px;
  line-height:1.7;
  -webkit-font-smoothing:antialiased;
  overflow-x:hidden;
}
body::before{
  content:"";position:fixed;inset:0;z-index:-2;pointer-events:none;
  background:
    radial-gradient(760px 520px at 78% -8%,rgba(46,230,168,.10),transparent 62%),
    radial-gradient(680px 480px at 8% 6%,rgba(106,169,255,.09),transparent 60%),
    linear-gradient(180deg,#070a10 0%,#080d15 50%,#070a10 100%);
}
body::after{
  content:"";position:fixed;inset:0;z-index:-1;pointer-events:none;opacity:.5;
  background-image:linear-gradient(rgba(255,255,255,.022) 1px,transparent 1px),
                   linear-gradient(90deg,rgba(255,255,255,.022) 1px,transparent 1px);
  background-size:64px 64px;
  -webkit-mask-image:radial-gradient(circle at 50% 0%,#000 0%,transparent 70%);
  mask-image:radial-gradient(circle at 50% 0%,#000 0%,transparent 70%);
}

h1,h2,h3,h4,.display{font-family:Sora,Inter,system-ui,sans-serif;font-weight:700;letter-spacing:-.02em;line-height:1.12;margin:0}
p{margin:0}
a{color:var(--accent);text-decoration:none;transition:color .2s var(--ease)}
a:hover{color:#8ff5d3}
img{max-width:100%;height:auto}
::selection{background:rgba(46,230,168,.28);color:#fff}

.wrap{width:min(100% - 2.5rem,var(--container));margin-inline:auto}
.section{padding:clamp(64px,9vw,132px) 0;position:relative}
.section--tight{padding:clamp(48px,6vw,84px) 0}
.muted{color:var(--muted)}
.center{text-align:center}

/* ---------- shared bits ---------------------------------------------------- */
.eyebrow{
  display:inline-flex;align-items:center;gap:.5rem;
  font-family:Inter,sans-serif;font-size:.72rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase;
  color:var(--accent);background:var(--accent-soft);border:1px solid rgba(46,230,168,.24);
  padding:.4rem .8rem;border-radius:999px;
}
.eyebrow .dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 0 4px rgba(46,230,168,.18)}
.section-head{max-width:720px;margin-bottom:clamp(32px,4vw,56px)}
.section-head h2{font-size:clamp(1.9rem,3.6vw,3rem);margin:1rem 0 .9rem}
.section-head p{color:var(--muted);font-size:1.02rem}
.head-row{display:flex;align-items:flex-end;justify-content:space-between;gap:2rem;flex-wrap:wrap}

.btn{
  display:inline-flex;align-items:center;justify-content:center;gap:.55rem;
  font-family:Inter,sans-serif;font-weight:650;font-size:.95rem;line-height:1;
  padding:.92rem 1.35rem;border-radius:999px;border:1px solid transparent;cursor:pointer;
  transition:transform .2s var(--ease),background .2s var(--ease),border-color .2s var(--ease),box-shadow .2s var(--ease),color .2s var(--ease);
  white-space:nowrap;
}
.btn:hover{transform:translateY(-2px)}
.btn:active{transform:translateY(0)}
.btn--accent{background:linear-gradient(135deg,#2ee6a8,#5ff0c6);color:#04140e;box-shadow:0 14px 34px -12px rgba(46,230,168,.6)}
.btn--accent:hover{color:#04140e;box-shadow:0 18px 44px -12px rgba(46,230,168,.75)}
.btn--ghost{background:var(--surface);border-color:var(--line-strong);color:var(--text)}
.btn--ghost:hover{background:var(--surface-2);border-color:rgba(46,230,168,.45);color:#fff}
.btn--quiet{background:transparent;border-color:var(--line);color:var(--muted);padding:.7rem 1.05rem;font-size:.88rem}
.btn--quiet:hover{color:#fff;border-color:var(--line-strong)}
.btn--sm{padding:.62rem 1rem;font-size:.86rem}

.card{
  background:linear-gradient(180deg,rgba(255,255,255,.055),rgba(255,255,255,.022));
  border:1px solid var(--line);border-radius:var(--radius);padding:26px;
  transition:transform .28s var(--ease),border-color .28s var(--ease),background .28s var(--ease);
  height:100%;
}
.card:hover{transform:translateY(-4px);border-color:rgba(46,230,168,.3);background:linear-gradient(180deg,rgba(255,255,255,.08),rgba(255,255,255,.035))}
.icon-badge{
  width:46px;height:46px;border-radius:13px;display:grid;place-items:center;font-size:1.1rem;
  background:var(--accent-soft);color:var(--accent);border:1px solid rgba(46,230,168,.22);
}
.grid{display:grid;gap:22px}
.grid--2{grid-template-columns:repeat(2,1fr)}
.grid--3{grid-template-columns:repeat(3,1fr)}
.grid--4{grid-template-columns:repeat(4,1fr)}

/* ---------- header --------------------------------------------------------- */
.site-header{
  position:fixed;top:0;left:0;right:0;z-index:60;
  transform:translateY(-105%);opacity:0;pointer-events:none;
  transition:transform .45s var(--ease),opacity .35s var(--ease);
  background:rgba(7,10,16,.72);backdrop-filter:blur(20px) saturate(160%);
  -webkit-backdrop-filter:blur(20px) saturate(160%);
  border-bottom:1px solid var(--line);
}
.site-header.is-visible{transform:translateY(0);opacity:1;pointer-events:auto}
.nav{display:flex;align-items:center;gap:1.4rem;height:74px}
.nav__brand{display:flex;align-items:center;gap:.65rem;font-family:Sora,sans-serif;font-weight:700;color:var(--text)}
.nav__brand img{max-height:38px;width:auto}
.nav__brand .mark{width:40px;height:40px;border-radius:11px;display:grid;place-items:center;background:linear-gradient(135deg,var(--accent),var(--accent-2));color:#04140e;font-weight:800}
.nav__links{display:flex;align-items:center;gap:.35rem;margin:0 auto;padding:0;list-style:none}
.nav__links a{
  color:#c3d0e0;font-size:.9rem;font-weight:550;padding:.5rem .78rem;border-radius:999px;
  transition:color .2s var(--ease),background .2s var(--ease);
}
.nav__links a:hover,.nav__links a.is-active{color:#fff;background:var(--surface-2)}
.nav__cta{display:flex;align-items:center;gap:.6rem}

/* ---------- hero ----------------------------------------------------------- */
.hero{position:relative;min-height:100svh;display:flex;align-items:center;padding:110px 0 70px;overflow:hidden}
.hero__grid{display:grid;grid-template-columns:1.05fr .95fr;gap:clamp(30px,5vw,70px);align-items:center}
.hero h1{font-size:clamp(2.6rem,6.4vw,4.9rem);margin:1.4rem 0 0;letter-spacing:-.035em}
.hero h1 .grad{background:linear-gradient(120deg,#fff 15%,var(--accent) 55%,var(--accent-2) 95%);-webkit-background-clip:text;background-clip:text;color:transparent}
.hero__role{font-family:Sora,sans-serif;font-size:clamp(1.05rem,2.1vw,1.5rem);font-weight:600;color:#dbe6f3;margin-top:1.1rem;min-height:2.4em}
.hero__role .cursor{display:inline-block;width:2px;height:1.05em;background:var(--accent);vertical-align:-.15em;margin-left:.15em;animation:caret 1s steps(2,start) infinite}
@keyframes caret{50%{opacity:0}}
.hero__lead{color:var(--muted);max-width:56ch;margin-top:1.2rem;font-size:1.03rem}
.hero__facts{display:flex;flex-wrap:wrap;gap:.6rem;margin-top:1.6rem}
.fact{display:inline-flex;align-items:center;gap:.5rem;font-size:.86rem;color:#c9d6e5;background:var(--surface);border:1px solid var(--line);padding:.5rem .85rem;border-radius:999px}
.fact i{color:var(--accent);font-size:.8rem}
.hero__actions{display:flex;flex-wrap:wrap;gap:.75rem;margin-top:2rem}
.hero__social{display:flex;gap:.6rem;margin-top:2rem}
.hero__social a{
  width:42px;height:42px;border-radius:12px;display:grid;place-items:center;color:#cfdbea;
  background:var(--surface);border:1px solid var(--line);transition:all .2s var(--ease);
}
.hero__social a:hover{color:var(--accent);border-color:rgba(46,230,168,.45);transform:translateY(-3px)}
.scroll-hint{
  position:absolute;left:50%;bottom:26px;transform:translateX(-50%);
  display:flex;flex-direction:column;align-items:center;gap:.5rem;
  color:var(--muted);font-size:.7rem;letter-spacing:.22em;text-transform:uppercase;
}
.scroll-hint .rail{width:1px;height:42px;background:linear-gradient(180deg,transparent,var(--accent));animation:railPulse 2.2s var(--ease) infinite}
@keyframes railPulse{0%,100%{opacity:.25;transform:scaleY(.6)}50%{opacity:1;transform:scaleY(1)}}

/* ---------- coded self-portrait -------------------------------------------
   The stage covers the whole hero, because he gets up and walks across it.
   It sits under the copy (which is lifted to z-index 2), so he passes behind
   the words instead of over them, and it ignores the pointer entirely. */
.avatar-stage{position:relative;display:grid;place-items:center;aspect-ratio:1/1;width:min(100%,520px);margin-inline:auto}
.avatar-stage__glow{
  position:absolute;inset:8%;border-radius:50%;
  background:radial-gradient(circle at 50% 45%,rgba(46,230,168,.28),rgba(106,169,255,.12) 45%,transparent 70%);
  filter:blur(26px);animation:breathGlow 7s var(--ease) infinite;
}
@keyframes breathGlow{0%,100%{transform:scale(1);opacity:.85}50%{transform:scale(1.07);opacity:1}}
.avatar-svg{position:relative;width:100%;height:100%;overflow:visible}
.hero__copy{position:relative}

.orbit-ring{transform-origin:300px 300px;animation:spin 42s linear infinite}
.orbit-ring--rev{animation:spin 34s linear infinite reverse}
@keyframes spin{to{transform:rotate(360deg)}}
.orbit-chip{transform-box:fill-box;transform-origin:center;animation:spin 42s linear infinite reverse}
.orbit-ring--rev .orbit-chip{animation-duration:34s;animation-direction:normal}
.orbit-chip text{font-family:Inter,sans-serif;font-size:15px;font-weight:700;fill:#dbe7f5}
/* ---------- the code character, at his desk --------------------------------
   He sits and works. Nothing travels, nothing switches scenes: the only
   motion is typing, breathing, a blink and the code running inside him.
   Each body part is a clipped window onto a stream of source lines that
   scrolls by exactly its own block height (`--span`) — never a percentage,
   which SVG resolves against the whole viewBox and would drag the code out
   of the shape and leave it blank. --------------------------------------- */
.edge{fill:none;stroke:rgba(46,230,168,.6);stroke-width:5;stroke-linejoin:round}
.heartbeat{fill:#2ee6a8;animation:pulseLed 2.2s var(--ease) infinite}
@keyframes pulseLed{0%,100%{opacity:.35}50%{opacity:1}}

.stream__line{
  font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;
  font-size:11px;fill:rgba(150,180,210,.55);white-space:pre;
}
.stream__line--hot{fill:rgba(46,230,168,.9)}
.stream__line--cool{fill:rgba(106,169,255,.85)}
.stream{animation:codeRun 9s linear infinite;will-change:transform}
.stream--torso{animation-duration:11s}
.stream--arm{animation-duration:7s}
.stream--head{animation-duration:6s}
@keyframes codeRun{to{transform:translateY(calc(var(--span) * -1))}}

/* The idle float lives on the inner group: a running animation on transform
   would override the walk translate on .coder entirely. */
.coder__gait{animation:idleFloat 7s var(--ease) infinite}
@keyframes idleFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}
.head{transform-box:view-box;transform-origin:300px 196px;animation:headTilt 9s var(--ease) infinite}
@keyframes headTilt{0%,100%{transform:rotate(-1.8deg)}50%{transform:rotate(2.2deg)}}
.torso{transform-box:view-box;transform-origin:300px 352px;animation:breathe 4.6s var(--ease) infinite}
@keyframes breathe{0%,100%{transform:scaleY(1)}50%{transform:scaleY(1.02)}}

/* the arms are reaching forward to the keyboard, and type */
.limb{transform-box:view-box}
.limb--arm-l{transform-origin:232px 248px;animation:typeL .34s ease-in-out infinite}
.limb--arm-r{transform-origin:368px 248px;animation:typeR .34s ease-in-out infinite}
@keyframes typeL{0%,100%{transform:rotate(-30deg)}50%{transform:rotate(-26deg)}}
@keyframes typeR{0%,100%{transform:rotate(30deg)}50%{transform:rotate(26deg)}}

/* the face is punctuation: two bracket eyes and a steady mouth */
.eye{fill:#2ee6a8;transform-box:fill-box;transform-origin:center;animation:blink 5.4s infinite}
.eye--r{animation-delay:.04s}
@keyframes blink{0%,94%,100%{transform:scaleY(1)}96.5%,98%{transform:scaleY(.1)}}
.mouth{fill:none;stroke:#2ee6a8;stroke-width:3.4;stroke-linecap:round}

/* the desk, and the laptop he is working on */
.desk{fill:#0d1826;stroke:rgba(46,230,168,.42);stroke-width:2.5}
.laptop__lid{fill:#0a1420;stroke:rgba(46,230,168,.55);stroke-width:2.5}
.laptop__base{fill:#101d2c;stroke:rgba(46,230,168,.5);stroke-width:2.5}
.laptop__glow{fill:rgba(46,230,168,.16);filter:blur(8px)}
.screen-line{
  fill:rgba(46,230,168,.85);transform-box:fill-box;transform-origin:left center;
  animation:typeLine 3.4s ease-in-out infinite;
}
.screen-line--2{fill:rgba(255,255,255,.32);animation-delay:.4s}
.screen-line--3{fill:rgba(106,169,255,.7);animation-delay:.8s}
.screen-line--4{animation-delay:1.2s}

.code-card{animation:cardFloat 8s var(--ease) infinite}
.code-card--b{animation:cardFloat 9s var(--ease) infinite;animation-delay:1.6s}
@keyframes cardFloat{0%,100%{transform:translateY(0)}50%{transform:translateY(-14px)}}
/* the code card is a code editor, so it is the one that gets the mono face */
.code-card text{font-family:"JetBrains Mono",ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;fill:#9fb2c8}
/* the stat card is a label, not code: same type as the rest of the site. It
   needs its own fill or SVG falls back to black text on a black card. */
.code-card--b text{font-family:Inter,system-ui,sans-serif;font-size:11.5px;fill:#9fb2c8}
.code-card--b .card-title{fill:#f2f6fb;font-size:14px;font-weight:650}
.code-line{transform-box:fill-box;transform-origin:left center;animation:typeLine 6s ease-in-out infinite}
.code-line--2{animation-delay:.45s}
.code-line--3{animation-delay:.9s}
@keyframes typeLine{0%,6%{transform:scaleX(0);opacity:0}16%,84%{transform:scaleX(1);opacity:1}96%,100%{transform:scaleX(0);opacity:0}}
.spark{animation:sparkle 3.4s var(--ease) infinite;transform-box:fill-box;transform-origin:center}
.spark--b{animation-delay:1.1s}
.spark--c{animation-delay:2.2s}
@keyframes sparkle{0%,100%{opacity:.15;transform:scale(.7)}50%{opacity:1;transform:scale(1.15)}}

/* ---------- about ---------------------------------------------------------- */
.about__grid{display:grid;grid-template-columns:.85fr 1.15fr;gap:clamp(30px,5vw,64px);align-items:center}
.about__photo{position:relative}
.about__photo-frame{
  position:relative;border-radius:26px;overflow:hidden;
  background:linear-gradient(165deg,rgba(46,230,168,.16),rgba(106,169,255,.10) 55%,rgba(255,255,255,.03));
  border:1px solid var(--line);padding:14px 14px 0;
}
.about__photo-frame::before{
  content:"";position:absolute;left:50%;top:12%;transform:translateX(-50%);
  width:78%;aspect-ratio:1/1;border-radius:50%;background:radial-gradient(circle,rgba(46,230,168,.22),transparent 68%);
}
.about__photo-frame img{display:block;width:100%;border-radius:18px}
.about__ai-background{
  position:absolute;z-index:1;inset:0;width:100%;height:100%;object-fit:cover;
  opacity:.92;filter:saturate(.88) contrast(1.04);
}
.about__photo-frame .about__portrait{
  position:relative;z-index:2;margin-top:18%;object-fit:contain;object-position:center bottom;
  filter:drop-shadow(0 14px 22px rgba(0,0,0,.4));
}
.about__badge{
  position:absolute;z-index:3;right:-14px;bottom:26px;background:#0b111b;border:1px solid var(--line-strong);
  border-radius:16px;padding:14px 18px;box-shadow:var(--shadow);text-align:center;
}
.about__badge strong{display:block;font-family:Sora,sans-serif;font-size:1.7rem;color:var(--accent);line-height:1}
.about__badge span{font-size:.74rem;color:var(--muted);letter-spacing:.05em}
.about__tags{display:flex;flex-wrap:wrap;gap:.55rem;margin-top:1.6rem}
.tag{font-size:.83rem;color:#cfdbea;background:var(--surface);border:1px solid var(--line);border-radius:999px;padding:.45rem .9rem}
.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;margin-top:2rem}
.stat{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-sm);padding:16px 14px}
.stat strong{display:block;font-family:Sora,sans-serif;font-size:1.45rem;color:var(--accent);line-height:1.2}
.stat span{font-size:.78rem;color:var(--muted)}

/* ---------- work websites -------------------------------------------------- */
.site-card{display:flex;flex-direction:column;gap:.9rem}
.site-card__top{display:flex;align-items:center;gap:.9rem}

/* ---------- product ventures ---------------------------------------------- */
#ventures{background:linear-gradient(180deg,transparent,rgba(46,230,168,.025),transparent)}
.venture-lead{padding:clamp(22px,3vw,34px);display:grid;grid-template-columns:minmax(230px,.72fr) 1.28fr;gap:20px 34px;align-items:center;border-color:rgba(46,230,168,.24)}
.venture-lead__identity,.venture-card__top{display:flex;align-items:center;gap:14px}
.venture-lead h3,.venture-card h3{font-size:1.25rem;margin:.25rem 0}
.venture-status{display:inline-flex;color:var(--accent);font-size:.72rem;font-weight:750;letter-spacing:.055em;text-transform:uppercase}
.venture-lead>p{color:#dbe6f3;margin:0;line-height:1.75}
.venture-proof{grid-column:1/-1;display:flex;flex-wrap:wrap;gap:9px}
.venture-proof span{padding:.45rem .75rem;border-radius:999px;background:rgba(46,230,168,.07);border:1px solid rgba(46,230,168,.18);color:#ccefe3;font-size:.78rem}
.venture-lead .work-card__link{grid-column:1/-1}
.venture-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px;margin-top:18px}
.venture-card{padding:clamp(20px,2.6vw,28px);display:flex;flex-direction:column;gap:.85rem}
.venture-card__top{justify-content:space-between;align-items:flex-start}
.venture-card__top .venture-status{text-align:right;max-width:210px}
.venture-card__type{color:#dce9f6;font-weight:650;margin-top:-.3rem}
.venture-impact{display:grid;gap:.35rem;padding:14px 16px;border-left:2px solid var(--accent);background:rgba(46,230,168,.045);border-radius:0 10px 10px 0}
.venture-impact strong{font-size:.78rem;color:var(--accent);text-transform:uppercase;letter-spacing:.05em}
.venture-impact span{font-size:.86rem;color:var(--muted);line-height:1.6}
.venture-card .work-card__link{margin-top:auto}
.venture-value{margin-top:20px;display:flex;gap:13px;align-items:flex-start;padding:18px 20px;border:1px solid var(--line);border-radius:var(--radius-sm);background:var(--surface)}
.venture-value i{color:var(--accent);margin-top:.25rem}.venture-value p{margin:0;color:#cbd9e8;line-height:1.7}

/* ---------- client reviews ------------------------------------------------- */
#reviews{background:linear-gradient(180deg,rgba(106,169,255,.025),rgba(46,230,168,.025))}
.review-summary{display:grid;grid-template-columns:.75fr repeat(3,1fr);border:1px solid var(--line);border-radius:var(--radius);background:var(--surface);overflow:hidden;margin-bottom:20px}
.review-summary>div{padding:20px;border-right:1px solid var(--line);display:flex;flex-direction:column;justify-content:center;gap:.3rem}.review-summary>div:last-child{border-right:0}
.review-summary__score strong{font-family:Sora,sans-serif;font-size:2.35rem;color:var(--accent);line-height:1}.review-stars{color:#ffd166;letter-spacing:.08em}.review-stars b{color:#e4edf7;font-size:.78rem;margin-left:.25rem}
.review-summary small,.review-summary__metric span{color:var(--muted);font-size:.78rem;line-height:1.45}.review-summary__metric strong{font-family:Sora,sans-serif;color:#e8f1fb;font-size:1.05rem}
.review-layout{display:grid;grid-template-columns:1.1fr .9fr;gap:20px;align-items:start}.review-list{display:grid;gap:14px}
.client-review{padding:20px;display:grid;gap:13px}.client-review__head{display:flex;justify-content:space-between;gap:18px;align-items:flex-start}.client-review__head div{display:grid;gap:.2rem}.client-review__head span{color:var(--muted);font-size:.78rem}
.client-review__status{padding:.35rem .6rem;border:1px solid rgba(46,230,168,.2);border-radius:999px;color:var(--accent)!important;white-space:nowrap}.client-review blockquote{margin:0;color:#dbe6f3;line-height:1.65;font-size:.94rem}
.client-review__meta{display:flex;gap:24px;padding-top:12px;border-top:1px solid var(--line)}.client-review__meta span{display:grid;color:var(--muted);font-size:.72rem}.client-review__meta strong{color:#eef5fc;font-size:.88rem}
.review-proof{padding:12px;position:sticky;top:100px}.review-proof img{width:100%;border-radius:12px;background:#fff}.review-proof p{display:flex;gap:9px;margin:12px 5px 4px;color:var(--muted);font-size:.76rem;line-height:1.55}.review-proof i{color:var(--accent);margin-top:.15rem}
.review-teamwork{display:flex;gap:15px;align-items:flex-start;margin-top:20px;padding:20px;border:1px solid rgba(46,230,168,.22);border-radius:var(--radius-sm);background:rgba(46,230,168,.045)}.review-teamwork p{margin:.35rem 0 0;color:var(--muted);line-height:1.65;font-size:.88rem}
.review-project-head,.review-library__head{display:flex;justify-content:space-between;align-items:flex-end;gap:20px;margin:28px 0 16px}.review-project-head h3,.review-library__head h3{font-size:clamp(1.25rem,2vw,1.65rem);margin:.45rem 0 .25rem}.review-project-head p{margin:0;color:var(--muted);font-size:.9rem}.review-library{margin-top:42px}.review-library__head>span{color:var(--muted);font-size:.8rem}.review-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px}.client-review--compact{align-content:start}.client-review--compact .client-review__head{margin-bottom:0}.client-review--compact .review-stars{white-space:nowrap;font-size:.78rem}.client-review--compact blockquote{font-size:.88rem}.review-disclosure{display:flex;gap:9px;align-items:flex-start;color:var(--muted);font-size:.76rem;line-height:1.55;margin:16px 2px 0}.review-disclosure i{color:var(--accent);margin-top:.15rem}
.site-card h3{font-size:1.15rem}
.site-card .domain{font-size:.84rem;color:var(--accent)}
.site-card .role{font-size:.86rem;color:#cfdbea}

/* ---------- work / case studies ------------------------------------------- */
.work-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:22px}
.work-card{
  position:relative;display:flex;flex-direction:column;overflow:hidden;
  background:linear-gradient(180deg,rgba(255,255,255,.055),rgba(255,255,255,.02));
  border:1px solid var(--line);border-radius:var(--radius);
  transition:transform .3s var(--ease),border-color .3s var(--ease);
}
.work-card:hover{transform:translateY(-6px);border-color:rgba(46,230,168,.35)}
.work-card__media{position:relative;aspect-ratio:16/10;overflow:hidden;background:#0c121c}
.work-card__media img{width:100%;height:100%;object-fit:cover;transition:transform .6s var(--ease)}
.work-card:hover .work-card__media img{transform:scale(1.06)}
.work-card__media::after{content:"";position:absolute;inset:0;background:linear-gradient(180deg,transparent 45%,rgba(7,10,16,.8))}
.work-card__cat{
  position:absolute;top:14px;left:14px;z-index:2;font-size:.7rem;font-weight:700;letter-spacing:.1em;text-transform:uppercase;
  background:rgba(7,10,16,.7);border:1px solid var(--line-strong);color:#d5e2f1;padding:.35rem .7rem;border-radius:999px;
  -webkit-backdrop-filter:blur(8px);backdrop-filter:blur(8px);
}
.work-card__body{padding:20px 22px 22px;display:flex;flex-direction:column;gap:.85rem;flex:1}
.work-card h3{font-size:1.08rem;line-height:1.35}
.work-card p{font-size:.9rem;color:var(--muted)}
.work-card__link{
  display:inline-flex;align-items:center;gap:.45rem;margin-top:auto;font-size:.88rem;font-weight:650;color:var(--accent);
}
.work-card__link i{transition:transform .25s var(--ease)}
.work-card:hover .work-card__link i{transform:translate(3px,-3px)}
.work-card__ph{width:100%;height:100%;display:grid;place-items:center;color:#3d4c60;font-size:2.2rem;background:radial-gradient(circle at 50% 40%,rgba(46,230,168,.1),transparent 70%)}

/* ---------- process -------------------------------------------------------- */
.process{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.process__item{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-sm);padding:20px;transition:border-color .25s var(--ease)}
.process__item:hover{border-color:rgba(46,230,168,.32)}
.process__num{font-family:Sora,sans-serif;font-size:.78rem;color:var(--accent);letter-spacing:.14em}
.process__item h3{font-size:1rem;margin:.5rem 0 .4rem}
.process__item p{font-size:.86rem;color:var(--muted)}

/* ---------- skills --------------------------------------------------------- */
.skill-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
.skill{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-sm);padding:18px}
.skill__top{display:flex;justify-content:space-between;align-items:baseline;gap:1rem}
.skill__top strong{font-size:.96rem;font-weight:600}
.skill__top span{font-family:Sora,sans-serif;font-size:.82rem;color:var(--accent)}
.bar{height:5px;border-radius:99px;background:rgba(255,255,255,.08);margin-top:.85rem;overflow:hidden}
.bar i{display:block;height:100%;width:0;border-radius:99px;background:linear-gradient(90deg,var(--accent),var(--accent-2));transition:width 1.1s var(--ease)}
.is-inview .bar i,.modal .bar i{width:var(--lvl)}

/* ---------- tools ---------------------------------------------------------- */
.tool-group{margin-top:2rem}
.tool-group h3{font-size:.78rem;letter-spacing:.16em;text-transform:uppercase;color:var(--muted);margin-bottom:.9rem}
.tool-row{display:flex;flex-wrap:wrap;gap:.6rem}
.tool{
  display:inline-flex;align-items:center;gap:.55rem;font-size:.88rem;color:#d7e3f1;
  background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:.55rem .95rem;
  transition:all .2s var(--ease);
}
.tool:hover{border-color:rgba(46,230,168,.4);color:#fff;transform:translateY(-2px)}
.tool i{color:var(--accent);font-size:.9rem}

/* ---------- experience ----------------------------------------------------- */
.timeline{position:relative;display:grid;gap:20px;padding-left:34px}
.timeline::before{content:"";position:absolute;left:9px;top:8px;bottom:8px;width:1px;background:linear-gradient(180deg,var(--accent),rgba(255,255,255,.08))}
.tl-item{position:relative}
.tl-item::before{
  content:"";position:absolute;left:-34px;top:26px;width:14px;height:14px;border-radius:50%;
  background:var(--bg);border:2px solid var(--accent);box-shadow:0 0 0 5px rgba(46,230,168,.10);
}
.tl-head{display:flex;flex-wrap:wrap;align-items:baseline;gap:.5rem 1rem}
.tl-item h3{font-size:1.12rem}
.tl-item .company{font-size:.92rem;color:var(--accent)}
.tl-meta{display:flex;flex-wrap:wrap;gap:.5rem;margin:.7rem 0 .9rem}
.tl-meta span{font-size:.78rem;color:#cfdbea;background:var(--surface-2);border:1px solid var(--line);border-radius:999px;padding:.3rem .75rem}
.tl-meta .is-main{color:var(--accent);border-color:rgba(46,230,168,.35);background:var(--accent-soft)}
.tl-item p{font-size:.94rem;color:var(--muted)}
.tl-tech{font-size:.8rem;color:#7f8fa5;margin-top:.8rem}

/* ---------- plan band ------------------------------------------------------ */
.plan{
  position:relative;overflow:hidden;border-radius:26px;padding:clamp(30px,4.5vw,60px);
  background:linear-gradient(135deg,rgba(46,230,168,.14),rgba(106,169,255,.10) 58%,rgba(255,255,255,.03));
  border:1px solid rgba(46,230,168,.24);
}
.plan::before{
  content:"";position:absolute;right:-90px;top:-90px;width:340px;height:340px;border-radius:50%;
  background:radial-gradient(circle,rgba(46,230,168,.25),transparent 68%);filter:blur(20px);
}
.plan__grid{position:relative;display:grid;grid-template-columns:1.15fr .85fr;gap:clamp(24px,4vw,56px);align-items:center}
.plan h2{font-size:clamp(1.7rem,3.2vw,2.6rem);margin:1rem 0 1rem}
.plan__points{display:grid;gap:.75rem;margin:0;padding:0;list-style:none}
.plan__points li{display:flex;gap:.7rem;align-items:flex-start;font-size:.95rem;color:#dce7f4}
.plan__points i{color:var(--accent);margin-top:.35rem;font-size:.85rem}

/* ---------- service charges ------------------------------------------------ */
.pricing-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px}
.pricing-card{display:flex;flex-direction:column;align-items:flex-start;min-height:100%;padding:clamp(22px,2.5vw,30px)}
.pricing-card h3{font-size:1.05rem;margin:1.1rem 0 .55rem}
.pricing-card__price{display:inline-flex;margin-bottom:.8rem;color:var(--accent);font:700 1rem/1.2 Sora,Inter,sans-serif}
.pricing-card p{font-size:.88rem;margin-bottom:1.1rem}
.pricing-card .work-card__link{margin-top:auto}
.pricing-note{display:flex;align-items:flex-start;gap:.65rem;margin:22px 0 0;padding:1rem 1.1rem;border:1px solid rgba(46,230,168,.2);border-radius:14px;background:var(--accent-soft);color:#c9d8e8;font-size:.84rem}
.pricing-note i{color:var(--accent);margin-top:.12rem}

/* ---------- contact -------------------------------------------------------- */
.contact__grid{display:grid;grid-template-columns:.8fr 1.2fr;gap:clamp(28px,4vw,56px)}
.contact-list{display:grid;gap:.9rem;margin-top:1.8rem}
.contact-list a,.contact-list span{display:flex;align-items:center;gap:.85rem;color:#d7e3f1;font-size:.95rem}
.contact-list i{width:38px;height:38px;border-radius:11px;display:grid;place-items:center;background:var(--accent-soft);color:var(--accent);border:1px solid rgba(46,230,168,.2);font-size:.9rem;flex:none}
.form-card{background:linear-gradient(180deg,rgba(255,255,255,.055),rgba(255,255,255,.022));border:1px solid var(--line);border-radius:var(--radius);padding:clamp(22px,3vw,34px)}
.field{display:grid;gap:.45rem}
.field label{font-size:.8rem;font-weight:600;color:#c6d3e2;letter-spacing:.02em}
.field input,.field select,.field textarea{
  width:100%;background:rgba(7,10,16,.6);border:1px solid var(--line-strong);color:var(--text);
  border-radius:12px;padding:.8rem .95rem;font:inherit;font-size:.94rem;transition:border-color .2s var(--ease),box-shadow .2s var(--ease);
}
.field input:focus,.field select:focus,.field textarea:focus{outline:none;border-color:var(--accent);box-shadow:0 0 0 3px rgba(46,230,168,.15)}
.field select{
  width:100%;min-height:47px;
  border:1px solid var(--line-strong)!important;
  border-radius:12px!important;
  color:var(--text)!important;
  padding:.8rem 2.75rem .8rem .95rem!important;
  font:inherit!important;font-size:.94rem!important;
  appearance:none!important;-webkit-appearance:none!important;color-scheme:dark;cursor:pointer;
  background:rgba(7,10,16,.6) url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='%2399a9bc' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m5 7 5 5 5-5'/%3E%3C/svg%3E") no-repeat right .95rem center/14px!important;
  box-shadow:none!important;
  transition:border-color .2s var(--ease),box-shadow .2s var(--ease);
}
.field select:hover{border-color:rgba(255,255,255,.25)!important}
.field select:focus{border-color:var(--accent)!important;box-shadow:0 0 0 3px rgba(46,230,168,.15)!important}
.field select option{background:#0b1018;color:var(--text)}
.field textarea{resize:vertical;min-height:132px}
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}
.form-grid .span-2{grid-column:span 2}
.hp-field{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}
.alert{border-radius:14px;padding:.9rem 1.1rem;margin-bottom:1.2rem;font-size:.92rem;border:1px solid}
.alert-success{background:rgba(46,230,168,.10);border-color:rgba(46,230,168,.35);color:#a9f7dc}
.alert-danger{background:rgba(255,99,99,.10);border-color:rgba(255,99,99,.35);color:#ffc2c2}

/* ---------- footer --------------------------------------------------------- */
.site-footer{border-top:1px solid var(--line);background:#05070c;padding:clamp(64px,9vw,110px) 0 30px}
.footer-thanks{display:grid;justify-items:center;gap:clamp(20px,3vw,34px);text-align:center}
/* ---- the sign-off --------------------------------------------------------
   Each letter lands one after another and an outline of it peels away as the
   colour arrives — written, rather than faded in. The gradient copy is always
   painted, so the word is never blank at any point in the loop. */
/* The row carries the type scale. The word and the robot both size themselves
   in em against it, so they stay in proportion from a phone to a wide desktop
   with no second set of breakpoints. align-items:flex-end stands the robot on
   the same line the letters sit on. */
.footer-thanks__line{
  display:flex;align-items:flex-end;justify-content:center;gap:0;
  font-size:clamp(4.4rem,17vw,15rem);
}
.footer-thanks__word{
  position:relative;display:flex;justify-content:center;
  font-family:Sora,Inter,system-ui,sans-serif;font-weight:750;letter-spacing:-.045em;line-height:.92;
  font-size:1em;
}
.footer-thanks__word::before{
  content:"";position:absolute;left:50%;top:52%;translate:-50% -50%;
  width:118%;aspect-ratio:2.4/1;z-index:-1;
  background:radial-gradient(ellipse at center,rgba(46,230,168,.16),rgba(106,169,255,.08) 45%,transparent 72%);
  filter:blur(26px);
}
/* Two nested spans per letter, because two different things move it:
   the outer one writes the word in, the inner one takes the shove from the
   robot's kick. One transform each, so they never fight.

   Both animations run 9s and never stop, so the delays act as a phase shift
   rather than a one-off offset — the stagger repeats every loop. --n staggers
   the writing-in left to right; --r staggers the kick right to left, starting
   at the s that was actually kicked. */
.footer-thanks__ch{
  position:relative;display:inline-block;
  animation:thanksLand 9s var(--ease) infinite;
  animation-delay:calc(var(--n) * 200ms);
}
.footer-thanks__glyph{
  position:relative;display:inline-block;
  /* The gradient is clipped to the glyph, and a box narrower than the ink
     clips the ink too. Negative letter-spacing makes every box ~3px narrower
     than the letter it holds, which was slicing the right edge off the k and
     the s. The padding gives the paint room; the matching negative margin
     takes that room back out of the layout, so nothing moves. */
  padding-right:.07em;margin-right:-.07em;
  background:linear-gradient(112deg,#ffffff 6%,var(--accent) 44%,var(--accent-2) 86%,#ffffff 108%);
  background-size:600% 100%;
  background-position:calc(var(--n) * 18%) 0;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  animation:thanksKick 9s var(--ease) infinite;
  animation-delay:calc(var(--r) * 200ms);
}
/* the outline that peels off once the colour has arrived */
.footer-thanks__glyph::before{
  content:attr(data-ch);position:absolute;left:0;top:0;
  -webkit-text-fill-color:transparent;
  -webkit-text-stroke:1.6px rgba(46,230,168,.75);
  animation:thanksPeel 9s var(--ease) infinite;
  animation-delay:calc(var(--n) * 200ms);
}
@keyframes thanksLand{
  0%     {opacity:0;transform:translateY(22px) rotate(-9deg) scale(.9)}
  9%     {opacity:1}
  16%    {transform:translateY(-9px) rotate(0) scale(1.04)}
  22%,100%{opacity:1;transform:translateY(0) rotate(0) scale(1)}
}
@keyframes thanksPeel{
  0%,10%  {opacity:1;transform:scale(1)}
  26%     {opacity:0;transform:scale(1.16)}
  100%    {opacity:0;transform:scale(1.16)}
}
/* the boot landing. 47% is the moment the robot's foot arrives; each letter
   further from the s feels it 200ms later than the one before. */
@keyframes thanksKick{
  0%,46%   {transform:translateX(0) rotate(0)}
  49%      {transform:translateX(-.10em) rotate(-5deg)}
  53%      {transform:translateX(.045em) rotate(2deg)}
  57%      {transform:translateX(-.022em) rotate(-1deg)}
  61%,100% {transform:translateX(0) rotate(0)}
}
/* a hairline on each side, so the word sits on something rather than floating */
.footer-thanks__rule{
  display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:clamp(14px,3vw,30px);
  width:100%;
}
.footer-thanks__rule::before,.footer-thanks__rule::after{
  content:"";height:1px;
  background:linear-gradient(90deg,transparent,rgba(46,230,168,.35),transparent);
}

/* ---- poking him ----------------------------------------------------------
   The robot is a button. Clicking runs a short scene over the top of the idle
   loop: he looks out at you, plainly put out but pretending otherwise; then
   he checks what his kick did to the far end of the word; then he turns on the
   s and pulls a face at it; then he faces you and says thanks.

   While a beat is running the looping animations on the parts involved are
   switched off and the pose is set directly, with a transition doing the
   travel. That is why the poses read cleanly instead of fighting the loop. */
.thanks-stage{
  /* the flex-item duties moved here from .thanks-bot when it gained a wrapper */
  flex:none;align-self:flex-end;position:relative;
  width:clamp(58px,.814em,254px);
  margin:0 0 .011em -.15em;
  padding:0;border:0;background:none;
  cursor:pointer;-webkit-tap-highlight-color:transparent;
}
.thanks-stage:focus-visible{outline:2px solid rgba(46,230,168,.7);outline-offset:6px;border-radius:10px}
.thanks-bot{display:block;width:100%;height:auto;overflow:visible;margin:0}

/* the bubble, same language as the one over the hero */
.thanks-say{
  position:absolute;left:50%;bottom:calc(100% - .04em);
  transform:translate(-50%,0) scale(.92);transform-origin:50% 100%;
  width:max-content;max-width:min(220px,44vw);
  background:rgba(9,14,22,.97);
  border:1px solid rgba(46,230,168,.32);
  border-radius:12px;padding:.42rem .7rem;
  box-shadow:0 18px 40px -20px rgba(0,0,0,.95);
  opacity:.85;pointer-events:none;
  transition:opacity .22s linear,transform .34s var(--ease),border-color .3s var(--ease);
}
.thanks-say::after{
  content:"";position:absolute;left:50%;top:100%;
  border:7px solid transparent;border-top-color:rgba(9,14,22,.97);
  transform:translateX(-50%);
}
.thanks-say b{
  display:block;color:#9fd8c2;font-weight:550;
  font-family:Inter,sans-serif;font-size:.78rem;line-height:1.4;letter-spacing:0;
}
.thanks-stage:hover .thanks-say{opacity:1;transform:translate(-50%,-3px) scale(1)}
/* once he has actually said something it is no longer an invitation */
.footer-thanks__line[data-poke] .thanks-say{
  opacity:1;transform:translate(-50%,-3px) scale(1);
  border-color:rgba(46,230,168,.6);
}
.footer-thanks__line[data-poke] .thanks-say b{color:#eaf6f1;font-weight:650}

/* ---- the beats ----------------------------------------------------------- */
.footer-thanks__line[data-poke] .tb-face,
.footer-thanks__line[data-poke] .tb-head,
.footer-thanks__line[data-poke] .tb-lean,
.footer-thanks__line[data-poke] .tb-phone,
.footer-thanks__line[data-poke] .tb-arm-l,
.footer-thanks__line[data-poke] .tb-arm-r,
.footer-thanks__line[data-poke] .tb-leg-l,
.footer-thanks__line[data-poke] .tb-leg-r,
.footer-thanks__line[data-poke] .tb-eye{
  animation:none;
  transition:transform .42s var(--ease),opacity .3s linear,fill .45s linear;
}
.footer-thanks__line[data-poke] .tb-mouth,
.footer-thanks__line[data-poke] .tb-tongue,
.footer-thanks__line[data-poke] .tb-blush,
.footer-thanks__line[data-poke] .tb-visor,
.footer-thanks__line[data-poke] .tb-antenna,
.footer-thanks__line[data-poke] .tb-head > .tb-body{
  animation:none;
  transition:opacity .25s linear,fill .45s linear,stroke .45s linear;
}

/* the phone goes away the moment he is interrupted, in every beat */
.footer-thanks__line[data-poke] .tb-phone{opacity:0;transform:translate(-7px,22px) rotate(-20deg)}
.footer-thanks__line[data-poke] .tb-mouth--flat{opacity:0}
.footer-thanks__line[data-poke] .tb-mouth--cross{opacity:0}
.footer-thanks__line[data-poke] .tb-mouth--smile{opacity:0}
.footer-thanks__line[data-poke] .tb-tongue{opacity:0}
.footer-thanks__line[data-poke] .tb-blush{opacity:0}

/* 1. someone prodded him. He looks straight out at you, and the only thing
      giving him away is the lamp. The mouth stays flat: he is not going to
      admit it bothered him. */
.footer-thanks__line[data-poke="cross"] .tb-face{transform:translateX(-7px) scaleX(.96)}
.footer-thanks__line[data-poke="cross"] .tb-head{transform:rotate(-2deg)}
.footer-thanks__line[data-poke="cross"] .tb-lean{transform:rotate(-4deg)}
.footer-thanks__line[data-poke="cross"] .tb-eye{transform:scaleY(.45);fill:#ff5656}
.footer-thanks__line[data-poke="cross"] .tb-antenna{fill:#ff5656}
.footer-thanks__line[data-poke="cross"] .tb-visor{fill:#2a0b10;stroke:rgba(255,86,86,.85)}
.footer-thanks__line[data-poke="cross"] .tb-head > .tb-body{stroke:rgba(255,86,86,.8)}
.footer-thanks__line[data-poke="cross"] .tb-mouth--flat{opacity:1}
.footer-thanks__line[data-poke="cross"] .tb-arm-l{transform:rotate(18deg)}
.footer-thanks__line[data-poke="cross"] .tb-arm-r{transform:rotate(-14deg)}

/* 2b. he watches where it went */
.footer-thanks__line[data-poke="check"] .tb-face{transform:translateX(-15px) scaleX(.86)}
.footer-thanks__line[data-poke="check"] .tb-head{transform:rotate(-7deg)}
.footer-thanks__line[data-poke="check"] .tb-lean{transform:rotate(-2deg)}
.footer-thanks__line[data-poke="check"] .tb-eye{transform:scaleY(1.1);fill:#2ee6a8}
.footer-thanks__line[data-poke="check"] .tb-mouth--flat{opacity:1}
.footer-thanks__line[data-poke="check"] .tb-arm-l{transform:rotate(14deg)}
.footer-thanks__line[data-poke="check"] .tb-arm-r{transform:rotate(-6deg)}


/* ---- the shove, on demand ------------------------------------------------
   The loop has its own scheduled kick; this is the one a visitor causes. Same
   shape, same right-to-left stagger off --r, but run once. It is declared for
   the kick beat and the tease beat with identical timing, so changing between
   those two does not restart it — the wave carries on travelling while he is
   already pulling a face. */
.footer-thanks__line[data-poke="kick"] .footer-thanks__glyph,
.footer-thanks__line[data-poke="tease"] .footer-thanks__glyph{
  animation:thanksKickOnce 1.05s var(--ease) 1 both;
  animation-delay:calc(var(--r) * 170ms);
}
@keyframes thanksKickOnce{
  0%   {transform:translateX(0) rotate(0)}
  18%  {transform:translateX(-.10em) rotate(-5deg)}
  38%  {transform:translateX(.045em) rotate(2deg)}
  58%  {transform:translateX(-.022em) rotate(-1deg)}
  100% {transform:translateX(0) rotate(0)}
}

/* ---- him, mid-kick -------------------------------------------------------
   Turned away from you, weight forward, heel going back into the letter. */
.footer-thanks__line[data-poke="kick"] .tb-face{transform:translateX(-15px) scaleX(.86)}
.footer-thanks__line[data-poke="kick"] .tb-head{transform:rotate(-6deg)}
.footer-thanks__line[data-poke="kick"] .tb-lean{transform:rotate(12deg)}
.footer-thanks__line[data-poke="kick"] .tb-eye{transform:scaleY(.4);fill:#ff5656}
.footer-thanks__line[data-poke="kick"] .tb-antenna{fill:#ff5656}
.footer-thanks__line[data-poke="kick"] .tb-visor{fill:#2a0b10;stroke:rgba(255,86,86,.85)}
.footer-thanks__line[data-poke="kick"] .tb-head > .tb-body{stroke:rgba(255,86,86,.8)}
.footer-thanks__line[data-poke="kick"] .tb-mouth--cross{opacity:1}
.footer-thanks__line[data-poke="kick"] .tb-arm-l{transform:rotate(-18deg)}
.footer-thanks__line[data-poke="kick"] .tb-arm-r{transform:rotate(-26deg)}
.footer-thanks__line[data-poke="kick"] .tb-leg-l{transform:rotate(76deg)}
.footer-thanks__line[data-poke="kick"] .tb-leg-r{transform:rotate(-11deg)}

/* 3. and pulls a face at the letter that started it */
.footer-thanks__line[data-poke="tease"] .tb-face{transform:translateX(-16px) scaleX(.84)}
.footer-thanks__line[data-poke="tease"] .tb-head{transform:rotate(-11deg)}
.footer-thanks__line[data-poke="tease"] .tb-lean{transform:rotate(-6deg)}
.footer-thanks__line[data-poke="tease"] .tb-eye{transform:scaleY(.3)}
.footer-thanks__line[data-poke="tease"] .tb-tongue{opacity:1}
.footer-thanks__line[data-poke="tease"] .tb-mouth--smile{opacity:1}
.footer-thanks__line[data-poke="tease"] .tb-arm-l{transform:rotate(22deg)}
.footer-thanks__line[data-poke="tease"] .tb-arm-r{transform:rotate(-20deg)}

/* 4. back to you, and he means it */
.footer-thanks__line[data-poke="thanks"] .tb-face{transform:translateX(-7px) scaleX(.96)}
.footer-thanks__line[data-poke="thanks"] .tb-head{transform:rotate(3deg)}
.footer-thanks__line[data-poke="thanks"] .tb-lean{transform:rotate(-9deg)}
.footer-thanks__line[data-poke="thanks"] .tb-eye{transform:scaleY(.55);fill:#2ee6a8}
.footer-thanks__line[data-poke="thanks"] .tb-mouth--smile{opacity:1}
.footer-thanks__line[data-poke="thanks"] .tb-blush{opacity:1}
.footer-thanks__line[data-poke="thanks"] .tb-arm-l{transform:rotate(-8deg)}
.footer-thanks__line[data-poke="thanks"] .tb-arm-r{transform:rotate(26deg)}

.tb-tongue{fill:#ff7a90;opacity:0}

@media (prefers-reduced-motion:reduce){
  .footer-thanks__line[data-poke] *{transition:none !important}
}

/* ---- the robot loafing against the word ----------------------------------
   He is propped back against the s, scrolling. The story, one 9s loop shared
   with the letters:

     0.0s  standing there on his phone, back to the word
     1.1s  something on the screen gets him — he laughs
     1.9s  the s lands behind him and shoves him off it
     2.8s  he is not amused
     3.8s  leans forward, away from the letter, and winds up
     4.2s  back-heels the s. The letters take it, s first, on down to the T
     5.7s  remembers he is in public, gives you a sheepish grin
     7.2s  settles back against the letter and finds his phone again

   Percentages are of 9s: 11% = 1s. */
/* The size and placement live on .thanks-stage above — the wrapper is the flex
   item now. This rule used to carry them too, and being the later of the two it
   won silently, which made the wrapper's numbers do nothing. */
.thanks-bot{display:block;width:100%;height:auto;overflow:visible;margin:0}
.tb-body,.tb-line,.tb-visor,.tb-palm{fill:#0d1826;stroke:rgba(46,230,168,.55);stroke-width:2.5}
.tb-visor{fill:#06101a}
.tb-fill{fill:#2ee6a8}
.thanks-bot__shadow{fill:rgba(46,230,168,.12)}
.thanks-bot [class^="tb-"]{transform-box:view-box}



/* ---- seeing red ----------------------------------------------------------
   While he is cross the whole face goes red and so does the lamp on his head.
   Colour is animated rather than switched so the temper rises and cools
   instead of flicking on and off. */
.tb-visor{animation:tbVisorMood 9s var(--ease) infinite}
@keyframes tbVisorMood{
  0%,28%   {fill:#06101a;stroke:rgba(46,230,168,.55)}
  34%,52%  {fill:#2a0b10;stroke:rgba(255,86,86,.85)}   /* held through the kick */
  60%,100% {fill:#06101a;stroke:rgba(46,230,168,.55)}
}
.tb-head > .tb-body{animation:tbHeadMood 9s var(--ease) infinite}
@keyframes tbHeadMood{
  0%,28%   {stroke:rgba(46,230,168,.55)}
  34%,52%  {stroke:rgba(255,86,86,.8)}
  60%,100% {stroke:rgba(46,230,168,.55)}
}

/* ---- looking back --------------------------------------------------------
   He is turned right, so the face sits over on that side of the head. When the
   s lands behind him the face slides left and he is looking over his shoulder
   at it — a whole head-turn for the price of one translate. */
.tb-face{transform-origin:66px 50px;animation:tbFace 9s var(--ease) infinite}
@keyframes tbFace{
  0%,20%   {transform:translateX(0) scaleX(1)}
  23%      {transform:translateX(-13px) scaleX(.88)}   /* whips round */
  31%,44%  {transform:translateX(-15px) scaleX(.86)}   /* still glaring at it */
  50%      {transform:translateX(-8px) scaleX(.94)}
  58%,74%  {transform:translateX(0) scaleX(1)}         /* back to you, sheepish */
  84%,100% {transform:translateX(0) scaleX(1)}
}
.tb-phone-body{fill:#0a1420;stroke:rgba(46,230,168,.6);stroke-width:2}

/* ---- the phone ----------------------------------------------------------- */
.tb-screen{fill:rgba(46,230,168,.35);animation:tbScreen 3.1s var(--ease) infinite}
@keyframes tbScreen{0%,100%{opacity:.45}45%{opacity:.85}}
/* it goes down while he deals with the interruption, and comes back after */
.tb-phone{transform-origin:60px 100px;animation:tbPhone 9s var(--ease) infinite}
@keyframes tbPhone{
  0%,20%   {transform:translate(0,0) rotate(0);opacity:1}
  22%      {transform:translate(-3px,4px) rotate(-11deg);opacity:1}  /* jostled */
  30%      {transform:translate(-6px,16px) rotate(-18deg);opacity:.25}
  34%,72%  {transform:translate(-7px,22px) rotate(-20deg);opacity:0} /* at his side */
  80%      {transform:translate(0,0) rotate(0);opacity:1}
  100%     {transform:translate(0,0) rotate(0);opacity:1}
}

/* ---- eyes ---------------------------------------------------------------- */
.tb-eye{fill:#2ee6a8;transform-origin:66px 49px;animation:tbEyes 9s var(--ease) infinite,tbEyeMood 9s var(--ease) infinite}
@keyframes tbEyeMood{0%,28%{fill:#2ee6a8}34%,52%{fill:#ff5656}60%,100%{fill:#2ee6a8}}
.tb-eye--r{animation-name:tbEyesR}
@keyframes tbEyes{
  0%,10%   {transform:scaleY(1) translate(0,2px)}      /* looking down at it */
  13%,17%  {transform:scaleY(.25) translate(0,0)}      /* screwed shut, laughing */
  20%      {transform:scaleY(1) translate(0,2px)}
  31%,44%  {transform:scaleY(.35) translate(2px,0)}    /* narrowed at the letter */
  50%      {transform:scaleY(1.15) translate(0,0)}
  62%,74%  {transform:scaleY(.55) translate(0,0)}      /* bashful */
  80%,100% {transform:scaleY(1) translate(0,2px)}
}
@keyframes tbEyesR{
  0%,10%   {transform:scaleY(1) translate(0,2px)}
  13%,17%  {transform:scaleY(.25) translate(0,0)}
  20%      {transform:scaleY(1) translate(0,2px)}
  31%,44%  {transform:scaleY(.35) translate(-2px,0)}
  50%      {transform:scaleY(1.15) translate(0,0)}
  62%,74%  {transform:scaleY(.55) translate(0,0)}
  80%,100% {transform:scaleY(1) translate(0,2px)}
}

/* ---- mouths -------------------------------------------------------------- */
.tb-mouth--flat{fill:rgba(46,230,168,.55);animation:tbMouthFlat 9s var(--ease) infinite}
.tb-mouth--cross,.tb-mouth--smile{fill:none;stroke:#2ee6a8;stroke-width:3;stroke-linecap:round;opacity:0}
.tb-mouth--cross{animation:tbMouthCross 9s var(--ease) infinite}
.tb-mouth--smile{animation:tbMouthSmile 9s var(--ease) infinite}
@keyframes tbMouthFlat { 0%,9%{opacity:1} 12%,20%{opacity:0} 23%,26%{opacity:1} 29%,76%{opacity:0} 82%,100%{opacity:1} }
/* the smile does double duty: the laugh at 12–20%, and the sheepish one later */
@keyframes tbMouthSmile{ 0%,10%{opacity:0} 13%,19%{opacity:1} 22%,54%{opacity:0} 62%,74%{opacity:1} 80%,100%{opacity:0} }
@keyframes tbMouthCross{ 0%,27%{opacity:0} 32%,47%{opacity:1} 52%,100%{opacity:0} }

.tb-blush{fill:rgba(255,120,140,.65);opacity:0;animation:tbBlush 9s var(--ease) infinite}
@keyframes tbBlush{ 0%,56%{opacity:0} 63%,74%{opacity:1} 81%,100%{opacity:0} }

/* the lamp on his head goes red with the rest of him */
@keyframes tbLampMood{0%,28%{fill:#2ee6a8}32%,52%{fill:#ff5656}60%,100%{fill:#2ee6a8}}
@keyframes tbPulse{0%,100%{opacity:.4}50%{opacity:1}}

/* ---- the body ------------------------------------------------------------
   The laugh is a shoulder shake; the shove at 21% is the s arriving behind
   him; the rest is him getting his own back. */
.thanks-bot__all{transform-origin:60px 142px;animation:tbStand 9s var(--ease) infinite}
@keyframes tbStand{
  0%,10%   {transform:translate(0,0) rotate(0)}
  13%      {transform:translate(8px,-3px) rotate(0)}    /* laughing. The step right
                                                             keeps his head from sinking
                                                             into the s as it tips back */
  15%      {transform:translate(7px,0) rotate(0)}
  17%      {transform:translate(8px,-3px) rotate(0)}
  20%      {transform:translate(0,0) rotate(0)}
  22%      {transform:translate(7px,0) rotate(3deg)}    /* the shove */
  26%      {transform:translate(2px,0) rotate(-1deg)}
  30%,100% {transform:translate(0,0) rotate(0)}
}

/* ---- the lean ------------------------------------------------------------
   Negative tips his shoulders back onto the letter; positive tips him off it.
   He is only upright while he is dealing with the s. */
.tb-lean{transform-origin:60px 120px;animation:tbLean 9s var(--ease) infinite}
@keyframes tbLean{
  0%,10%   {transform:rotate(-11deg)}     /* propped, absorbed in the phone */
  13%,17%  {transform:rotate(-12.5deg)}   /* head back, laughing — kept shallow so
                                             his head does not sink into the s */
  20%      {transform:rotate(-11deg)}
  22%      {transform:rotate(-2deg)}      /* knocked off it */
  31%,36%  {transform:rotate(1deg)}       /* standing squarely now */
  42%      {transform:rotate(9deg)}       /* forward, to swing the heel back */
  47%      {transform:rotate(13deg)}
  56%      {transform:rotate(2deg)}
  64%,74%  {transform:rotate(-5deg)}      /* facing you, embarrassed */
  84%,100% {transform:rotate(-11deg)}     /* back on the letter */
}

/* the head: down on the phone, back for the laugh, round at the letter */
.tb-head{transform-origin:66px 76px;animation:tbHead 9s var(--ease) infinite}
@keyframes tbHead{
  0%,10%   {transform:rotate(4deg)}       /* tipped down at the screen */
  13%,17%  {transform:rotate(-9deg)}      /* thrown back laughing */
  20%      {transform:rotate(4deg)}
  24%      {transform:rotate(-3deg)}
  32%,44%  {transform:rotate(-8deg)}      /* glaring over his shoulder */
  50%      {transform:rotate(5deg)}
  62%,74%  {transform:rotate(7deg)}       /* ducked, sheepish */
  84%,100% {transform:rotate(4deg)}
}
/* Both animations live on one declaration. They were split across two rules,
   and the later one silently dropped the colour change — an animation shorthand
   replaces the whole list, it does not add to it. */
.tb-aerial{transform-origin:66px 30px;animation:tbAerial 9s var(--ease) infinite}
.tb-antenna{
  transform-origin:66px 30px;
  animation:tbAerial 9s var(--ease) infinite,
            tbLampMood 9s var(--ease) infinite,
            tbPulse 2.4s var(--ease) infinite;
}
@keyframes tbAerial{
  0%,10%   {transform:rotate(0)}
  13%      {transform:rotate(-13deg)}
  16%      {transform:rotate(11deg)}
  20%,20.1%{transform:rotate(0)}
  22%      {transform:rotate(-17deg)}
  26%      {transform:rotate(8deg)}
  30%,44%  {transform:rotate(0)}
  47%      {transform:rotate(15deg)}
  54%,100% {transform:rotate(0)}
}

/* ---- arms ----------------------------------------------------------------
   Both come in around the phone. Positive swings toward the word, so the arm
   on that side uses small negative angles to stay tucked and out of the way. */
.tb-arm-l{transform-origin:28px 88px;animation:tbArmL 9s var(--ease) infinite}
@keyframes tbArmL{
  0%,20%   {transform:rotate(-8deg)}      /* hanging behind him */
  22%      {transform:rotate(14deg)}      /* thrown out by the shove */
  26%      {transform:rotate(-2deg)}
  32%,44%  {transform:rotate(18deg)}      /* hand on hip, unimpressed */
  47%      {transform:rotate(-16deg)}     /* out for balance on the kick */
  56%      {transform:rotate(-2deg)}
  64%,74%  {transform:rotate(8deg)}       /* rubbing the back of his neck */
  84%,100% {transform:rotate(-8deg)}
}
.tb-arm-r{transform-origin:92px 88px;animation:tbArmR 9s var(--ease) infinite}
@keyframes tbArmR{
  0%,20%   {transform:rotate(9deg)}       /* holding the phone up in front */
  22%      {transform:rotate(-6deg)}      /* jolted */
  26%      {transform:rotate(4deg)}
  32%,44%  {transform:rotate(-14deg)}     /* phone down at his side */
  47%      {transform:rotate(-24deg)}     /* swung out on the kick */
  56%      {transform:rotate(-8deg)}
  64%,74%  {transform:rotate(-4deg)}
  84%,100% {transform:rotate(9deg)}
}

/* ---- the back-heel -------------------------------------------------------
   Positive swings toward the word, which is behind him — so this is a heel
   kicked backwards, and 47% is the instant thanksKick starts on the s. */
.tb-leg-l{transform-origin:52px 122px;animation:tbLegL 9s var(--ease) infinite}
@keyframes tbLegL{
  0%,30%   {transform:rotate(-4deg)}      /* weight back on the letter */
  36%      {transform:rotate(0)}
  42%      {transform:rotate(-22deg)}     /* forward, to load it */
  47%      {transform:rotate(76deg)}      /* and back into the s */
  51%      {transform:rotate(44deg)}
  56%      {transform:rotate(-6deg)}
  64%,100% {transform:rotate(-4deg)}
}
.tb-leg-r{transform-origin:68px 122px;animation:tbLegR 9s var(--ease) infinite}
@keyframes tbLegR{
  0%,30%   {transform:rotate(-4deg)}
  36%,44%  {transform:rotate(0)}
  47%      {transform:rotate(-11deg)}     /* the standing leg braces */
  56%      {transform:rotate(-2deg)}
  64%,100% {transform:rotate(-4deg)}
}

/* Nobody should have to watch a robot boot a letter on a loop if their system
   asks for less motion. Everything settles into the resting pose. */
@media (prefers-reduced-motion:reduce){
  .thanks-bot *{animation:none !important}
  .tb-lean{transform:rotate(-11deg)}
  .tb-arm-l{transform:rotate(-31deg)}
  .tb-arm-r{transform:rotate(31deg)}
  .tb-mouth--cross,.tb-mouth--smile,.tb-blush{opacity:0}
  .footer-thanks__ch,.footer-thanks__glyph{animation:none !important}
  .footer-thanks__glyph::before{opacity:0;animation:none !important}
}
.footer-bottom{margin-top:clamp(44px,6vw,72px);padding-top:22px;border-top:1px solid var(--line);display:flex;flex-wrap:wrap;gap:1rem;justify-content:space-between;color:var(--muted);font-size:.85rem}

/* ---------- modal ---------------------------------------------------------- */
.modal-content{background:#0b111b;border:1px solid var(--line-strong);border-radius:22px;color:var(--text)}
.modal-header,.modal-footer{border-color:var(--line)}
.modal-title{font-family:Sora,sans-serif;font-size:1.15rem}
.modal-body{max-height:min(70vh,640px);overflow-y:auto}
.modal-cat{font-size:.75rem;letter-spacing:.14em;text-transform:uppercase;color:var(--accent);margin:1.6rem 0 .8rem}
.modal-cat:first-child{margin-top:0}
.modal-list{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}
.modal-list .skill{padding:12px 14px}
.btn-close{filter:invert(1) grayscale(1);opacity:.7}

/* ---------- reveal --------------------------------------------------------- */
.reveal{opacity:0;transform:translateY(26px);transition:opacity .7s var(--ease),transform .7s var(--ease)}
.reveal.is-inview{opacity:1;transform:none}
.reveal[data-delay="1"]{transition-delay:.08s}
.reveal[data-delay="2"]{transition-delay:.16s}
.reveal[data-delay="3"]{transition-delay:.24s}
.reveal[data-delay="4"]{transition-delay:.32s}


/* ---------- responsive ----------------------------------------------------- */
@media (max-width:1100px){
  .nav__links{display:none}
  .nav{gap:.8rem}
  .nav__cta{margin-left:auto}
  .grid--4,.work-grid,.skill-grid,.process,.pricing-grid{grid-template-columns:repeat(2,1fr)}
  .grid--3{grid-template-columns:repeat(2,1fr)}
  .modal-list{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:900px){
  .hero{min-height:auto;padding:100px 0 60px}
  .hero__grid,.about__grid,.plan__grid,.contact__grid{grid-template-columns:1fr}
  .hero__grid{gap:22px}
  /* the character sits above the copy on a phone, but kept small enough that
     the name and the role still land on the first screen */
  .avatar-stage{width:min(64%,240px);order:-1;margin-bottom:-6px}
  .about__photo{max-width:420px}
  .scroll-hint{display:none}
  .nav__cta .btn--ghost{display:none}
  .venture-lead{grid-template-columns:1fr}
  .venture-lead .work-card__link,.venture-proof{grid-column:auto}
  .review-layout{grid-template-columns:1fr}.review-proof{position:static;max-width:620px}.review-summary{grid-template-columns:repeat(2,1fr)}.review-summary>div:nth-child(2){border-right:0}.review-summary>div:nth-child(-n+2){border-bottom:1px solid var(--line)}.review-grid{grid-template-columns:1fr}
}
@media (max-width:640px){
  .pricing-grid{grid-template-columns:1fr}
  .about__ai-background{opacity:.78;transform:scale(1.04)}
  .about__photo-frame .about__portrait{margin-top:20%}
  body{font-size:16px}
  .grid--2,.grid--3,.grid--4,.work-grid,.skill-grid,.process,.form-grid,.modal-list{grid-template-columns:1fr}
  .venture-grid{grid-template-columns:1fr}
  .review-summary{grid-template-columns:1fr}.review-summary>div{border-right:0!important;border-bottom:1px solid var(--line)}.review-summary>div:last-child{border-bottom:0}.client-review__head{display:grid}.client-review__status{width:max-content}.client-review__meta{gap:14px;justify-content:space-between}.review-project-head,.review-library__head{display:grid;align-items:start}.review-library__head>span{order:-1}
  /* 1fr can still be pushed wide by a long label like "International" */
  .stat-row{grid-template-columns:repeat(2,minmax(0,1fr))}
  .form-grid .span-2{grid-column:span 1}
  .about__badge{right:0;bottom:-16px}
  .hero__actions .btn{flex:1 1 100%}
}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms!important;animation-iteration-count:1!important;transition-duration:.001ms!important}
  html{scroll-behavior:auto}
  .reveal{opacity:1;transform:none}
  .bar i{width:var(--lvl)}
}

/* ---- the odd stretch of the legs -----------------------------------------
   Two modes on .avatar-stage[data-mode]: work, and stroll. He gets up, takes
   a short walk beside the desk, and sits back down. One keyframe carries him
   out and back, so there is nothing to keep in step. */
.legs{opacity:0;transition:opacity .4s linear}
.limb--leg-l{transform-origin:282px 372px}
.limb--leg-r{transform-origin:318px 372px}
@keyframes stepA{0%,100%{transform:rotate(0)}25%{transform:rotate(19deg)}75%{transform:rotate(-19deg)}}
@keyframes stepB{0%,100%{transform:rotate(0)}25%{transform:rotate(-19deg)}75%{transform:rotate(19deg)}}

.avatar-stage[data-mode="stroll"] .legs{opacity:1}
/* seated: sunk behind the desk until the walk lifts him out of the chair */
.coder{transform:translateY(var(--sit));transition:transform .5s var(--ease)}
.avatar-stage[data-mode="stroll"] .coder{animation:stroll 11s var(--ease) 1}
/* He is SEATED by default, sunk behind the desk. The walk starts by standing
   him up and ends by sitting him back down, which is what makes getting up
   read as getting up rather than just sprouting legs. */
@keyframes stroll{
  0%{transform:translate(0,var(--sit))}
  9%{transform:translate(0,0)}
  32%,46%{transform:translate(-168px,0)}
  74%{transform:translate(0,0)}
  90%,100%{transform:translate(0,var(--sit))}
}
.avatar-stage[data-mode="stroll"] .limb--leg-l,
.avatar-stage[data-mode="stroll"] .limb--arm-r{animation:stepA .7s ease-in-out infinite}
.avatar-stage[data-mode="stroll"] .limb--leg-r,
.avatar-stage[data-mode="stroll"] .limb--arm-l{animation:stepB .7s ease-in-out infinite}
.avatar-stage[data-mode="stroll"] .coder__gait{animation:bob .35s ease-in-out infinite}
@keyframes bob{0%,100%{transform:translateY(0)}50%{transform:translateY(-5px)}}

/* the screen keeps glowing while he is away from it */
.avatar-stage[data-mode="stroll"] .laptop__glow{opacity:.55}
.laptop__glow{transition:opacity .6s linear}
/* Narrow screens: he keeps to his desk - there is nowhere to stroll to. */
@media (max-width:900px){
  .avatar-stage[data-mode="stroll"] .coder{animation:none}
  .avatar-stage[data-mode="stroll"] .legs{opacity:0}
}

/* ---- poke him and he answers --------------------------------------------
   Light text on a dark bubble, placed over his head by site.js so it follows
   him when he is on his feet. */
.coder,.coder__hit{cursor:pointer}
/* a transparent fill is not reliably hit-tested, so say so outright — this is
   what makes the gaps between his limbs clickable too */
.coder__hit{pointer-events:all}
.avatar-stage{--say-x:50%;--say-y:0;--sit:46px}
.coder-say{
  position:absolute;left:var(--say-x);top:var(--say-y);
  width:max-content;max-width:min(260px,80%);
  transform:translate(-50%,-100%) scale(.9);transform-origin:50% 100%;
  opacity:0;pointer-events:none;
  transition:opacity .22s linear,transform .34s var(--ease);
  background:rgba(9,14,22,.97);
  border:1px solid rgba(46,230,168,.55);
  border-radius:14px;padding:.55rem .8rem;
  box-shadow:0 18px 40px -20px rgba(0,0,0,.95);
}
.coder-say::after{
  content:"";position:absolute;left:50%;top:100%;
  border:8px solid transparent;border-top-color:rgba(9,14,22,.97);
  transform:translateX(-50%);
}
.coder-say p{
  margin:0;color:#eaf6f1;
  font-family:Inter,sans-serif;font-size:.82rem;line-height:1.45;font-weight:600;
}
.coder-say.is-open{opacity:1;transform:translate(-50%,-100%) scale(1)}

/* ---- how he reacts to what he just said ---------------------------------
   Every mood is ONE run of a 2.4s move that starts and ends on the pose he
   types in — left arm -30deg, right arm +30deg. That matters: the earlier
   versions began at the finished pose, so the arm teleported up, waved, and
   teleported back. Now the lift, the action and the return are all on screen,
   and going back to work needs no jump because the animation has already put
   him there. site.js holds the mood for 2.6s, a little longer than the move,
   so nothing is ever cut off mid-gesture. */
.mouth--grin,.mouth--oh{opacity:0;transition:opacity .3s var(--ease)}
.mouth--idle{transition:opacity .3s var(--ease)}
.mouth--oh{fill:rgba(46,230,168,.25)}
.avatar-stage[data-mood] .mouth--idle{opacity:0}
.avatar-stage[data-mood="wave"] .mouth--grin,
.avatar-stage[data-mood="laugh"] .mouth--grin,
.avatar-stage[data-mood="proud"] .mouth--grin,
.avatar-stage[data-mood="point"] .mouth--grin{opacity:1}
.avatar-stage[data-mood="think"] .mouth--oh,
.avatar-stage[data-mood="shrug"] .mouth--oh{opacity:1}

/* hello: the hand leaves the keyboard, rises, waves, and goes back to it */
.avatar-stage[data-mood="wave"] .limb--arm-r{animation:waveHi 2.4s var(--ease) 1 both}
@keyframes waveHi{
  0%   {transform:rotate(30deg) scaleY(1)}
  16%  {transform:rotate(-136deg) scaleY(.92)}   /* the lift, plainly visible */
  29%  {transform:rotate(-118deg) scaleY(.92)}
  42%  {transform:rotate(-152deg) scaleY(.92)}
  55%  {transform:rotate(-120deg) scaleY(.92)}
  68%  {transform:rotate(-148deg) scaleY(.92)}
  78%  {transform:rotate(-132deg) scaleY(.92)}
  100% {transform:rotate(30deg) scaleY(1)}       /* and back to the keys */
}
.avatar-stage[data-mood="wave"] .coder__gait{animation:waveLean 2.4s var(--ease) 1 both}
@keyframes waveLean{0%,100%{transform:translateY(0)}20%,80%{transform:translateY(-4px)}}

/* laughing: the shoulders go, the eyes squeeze, then everything settles */
.avatar-stage[data-mood="laugh"] .coder__gait{animation:giggle 2.4s ease-in-out 1 both}
@keyframes giggle{
  0%,100%{transform:translateY(0) rotate(0)}
  10%{transform:translateY(-5px) rotate(1.6deg)}
  20%{transform:translateY(0) rotate(-1.2deg)}
  30%{transform:translateY(-5px) rotate(1.6deg)}
  40%{transform:translateY(0) rotate(-1.2deg)}
  50%{transform:translateY(-5px) rotate(1.6deg)}
  60%{transform:translateY(0) rotate(-1.2deg)}
  70%{transform:translateY(-4px) rotate(1deg)}
  84%{transform:translateY(0) rotate(0)}
}
.avatar-stage[data-mood="laugh"] .eye{animation:squeeze 2.4s ease-in-out 1 both}
@keyframes squeeze{
  0%,100%{transform:scaleY(1)}
  10%,70%{transform:scaleY(.3)}
  84%{transform:scaleY(1)}
}

/* thinking: the hand climbs to the chin, stays there, then goes back down */
.avatar-stage[data-mood="think"] .limb--arm-r{animation:toChin 2.4s var(--ease) 1 both}
@keyframes toChin{
  0%      {transform:rotate(30deg) scaleY(1)}
  20%     {transform:rotate(126deg) scaleY(.66)}
  24%,74% {transform:rotate(122deg) scaleY(.66)}
  100%    {transform:rotate(30deg) scaleY(1)}
}
.avatar-stage[data-mood="think"] .head{animation:tiltThink 2.4s var(--ease) 1 both}
@keyframes tiltThink{0%,100%{transform:rotate(0)}22%,76%{transform:rotate(-6deg)}}

/* a shrug: both arms come up off the keys and drop back */
.avatar-stage[data-mood="shrug"] .limb--arm-l{animation:shrugL 2.4s var(--ease) 1 both}
.avatar-stage[data-mood="shrug"] .limb--arm-r{animation:shrugR 2.4s var(--ease) 1 both}
@keyframes shrugL{
  0%,100%{transform:rotate(-30deg)}
  22%,40%{transform:rotate(6deg)}
  58%,76%{transform:rotate(4deg)}
}
@keyframes shrugR{
  0%,100%{transform:rotate(30deg)}
  22%,40%{transform:rotate(-6deg)}
  58%,76%{transform:rotate(-4deg)}
}
.avatar-stage[data-mood="shrug"] .coder__gait{animation:shrugLift 2.4s var(--ease) 1 both}
@keyframes shrugLift{0%,100%{transform:translateY(0)}24%,74%{transform:translateY(-5px)}}

/* pleased with himself: sits up, holds it, settles back down */
.avatar-stage[data-mood="proud"] .coder__gait{animation:sitTall 2.4s var(--ease) 1 both}
@keyframes sitTall{0%,100%{transform:translateY(0)}18%,78%{transform:translateY(-8px)}}
.avatar-stage[data-mood="proud"] .heartbeat{animation:proudLed 2.4s var(--ease) 1 both}
@keyframes proudLed{0%,100%{opacity:.35}16%,80%{opacity:1}}

/* pointing down the page, then back to the keyboard */
.avatar-stage[data-mood="point"] .limb--arm-r{animation:pointDown 2.4s var(--ease) 1 both}
@keyframes pointDown{
  0%,100%{transform:rotate(30deg)}
  18%,76%{transform:rotate(-34deg)}
}
.avatar-stage[data-mood="point"] .head{animation:tiltPoint 2.4s var(--ease) 1 both}
@keyframes tiltPoint{0%,100%{transform:rotate(0)}20%,74%{transform:rotate(5deg)}}

/* resting state: the bubble stays up asking to be clicked, a little quieter
   than when he is actually saying something */
.coder-say.is-invite{border-color:rgba(46,230,168,.32)}
.coder-say.is-invite p{color:#9fd8c2;font-weight:550}
.coder-say.is-invite::after{border-top-color:rgba(9,14,22,.97)}
.coder-say.is-invite{animation:inviteNudge 3.2s var(--ease) infinite}
@keyframes inviteNudge{
  0%,88%,100%{transform:translate(-50%,-100%) scale(1)}
  93%{transform:translate(-50%,-104%) scale(1.04)}
}

/* ---- phones -------------------------------------------------------------- */
@media (max-width:640px){
  /* The card links were 24px tall — fine to read, awkward to hit. Give them
     a proper thumb-sized target without changing how they look. */
  .work-card__link,.site-card .work-card__link,.about__tags .tag{min-height:44px;align-items:center}
  .work-card__link,.site-card .work-card__link{display:inline-flex;padding-block:.55rem}

  .coder-say{max-width:min(220px,72vw)}
  .coder-say p{font-size:.78rem}
}

/* ==========================================================================
   The dock — the phone's navigation.
   A small flat icon parked at the bottom. Tapping it sends the page back to
   the top and rolls the section links out of the icon: up, and to the right.
   Tapping again rolls them back inside. Desktop keeps the bar at the top.
   ========================================================================== */
.dock{display:none}

/* from 1100px down, because that is where the top bar drops its links */
@media (max-width:1100px){
  .dock{
    display:block;position:fixed;z-index:70;
    right:18px;bottom:calc(18px + env(safe-area-inset-bottom,0px));
  }

  .dock__toggle{
    position:relative;z-index:2;
    width:42px;height:42px;border-radius:50%;border:1px solid rgba(46,230,168,.5);
    background:rgba(9,14,22,.96);color:var(--accent);
    display:grid;place-items:center;cursor:pointer;
    box-shadow:0 16px 34px -16px rgba(0,0,0,.95);
    transition:transform .45s var(--ease),border-color .3s var(--ease);
  }
  .dock.is-open .dock__toggle{border-color:rgba(46,230,168,.9)}

  /* the kick it gives as it fires them out, or sucks them back in */
  .dock__toggle.is-kick{animation:dockKick .42s var(--ease) 1}
  @keyframes dockKick{
    0%{transform:scale(1)}
    26%{transform:scale(.82)}
    58%{transform:scale(1.14)}
    78%{transform:scale(.97)}
    100%{transform:scale(1)}
  }

  /* the icon is three flat bars that become a cross when it opens */
  .dock__bars,
  .dock__bars::before,
  .dock__bars::after{
    content:"";display:block;width:15px;height:2px;border-radius:2px;
    background:currentColor;transition:transform .4s var(--ease),opacity .25s linear;
  }
  .dock__bars::before{position:absolute;transform:translateY(-5px)}
  .dock__bars::after{position:absolute;transform:translateY(5px)}
  /* the bars cross over; the button itself must not rotate or its rounded
     square turns into a diamond */
  .dock.is-open .dock__bars{opacity:0}
  .dock.is-open .dock__bars::before{transform:translateY(0) rotate(45deg);opacity:1}
  .dock.is-open .dock__bars::after{transform:translateY(0) rotate(-45deg);opacity:1}

  .dock__items{
    position:absolute;right:0;bottom:52px;
    list-style:none;margin:0;padding:0;
    display:grid;gap:10px;justify-items:end;
  }
  .dock__items li{
    /* parked inside the toggle: no size, wound up, sitting on top of it */
    opacity:0;pointer-events:none;
    transform:translate(0,54px) rotate(-540deg) scale(.2);
    transform-origin:50% 50%;
    transition:transform .5s var(--ease),opacity .3s linear;
    transition-delay:calc((10 - var(--i)) * 26ms);
  }
  /* A keyframe, not a transition: a transition is interpolated as a matrix,
     and a matrix cannot hold more than half a turn, so -540deg silently
     became -180deg and the spin was lost. */
  .dock.is-open .dock__items li{
    opacity:1;pointer-events:auto;transform:none;
    animation:dockFling .58s var(--ease) both;
    animation-delay:calc(var(--i) * 46ms);
  }
  @keyframes dockFling{
    from{opacity:0;transform:translate(0,54px) rotate(-540deg) scale(.2)}
    70%{opacity:1}
    to{opacity:1;transform:translate(0,0) rotate(0deg) scale(1)}
  }

  /* Going back in is the same move played backwards, and in reverse order:
     the one furthest out leaves first. It has to be a keyframe for the same
     reason as the way out — a transition flattens the spin to half a turn. */
  .dock.is-closing .dock__items li{
    opacity:0;
    animation:dockFold .5s var(--ease) both;
    animation-delay:calc((10 - var(--i)) * 40ms);
  }
  @keyframes dockFold{
    from{opacity:1;transform:translate(0,0) rotate(0deg) scale(1)}
    30%{opacity:1}
    to{opacity:0;transform:translate(0,54px) rotate(-540deg) scale(.2)}
  }
  /* icon only, so the dock stays small */
  .dock__items a,
  .dock__items button{
    display:grid;place-items:center;
    width:46px;height:46px;padding:0;
    border-radius:50%;border:1px solid rgba(46,230,168,.34);
    background:rgba(9,14,22,.96);
    color:#eaf6f1;font-size:1rem;cursor:pointer;
    box-shadow:0 14px 28px -18px rgba(0,0,0,.95);
    transition:color .2s var(--ease),border-color .2s var(--ease);
  }
  .dock__items a:hover,
  .dock__items button:hover{color:var(--accent);border-color:rgba(46,230,168,.75)}
  /* the two jumps read as controls, not places */
  .dock__jump a,.dock__jump button{border-style:dashed;color:var(--accent)}

  /* the top bar keeps the brand and Contact Me; the links live in the dock */
  .nav__links{display:none}
}

/* ---- the WhatsApp button (desktop) ---------------------------------------
   The dock owns the bottom-right corner on a phone, so this only appears
   where the dock does not. Flat: no gradient, no shadow stack, one colour. */
.wa-dock{display:none}
@media (min-width:1101px){
  .wa-dock{
    display:grid;place-items:center;position:fixed;z-index:70;
    right:24px;bottom:24px;width:52px;height:52px;
    border-radius:50%;border:1px solid rgba(37,211,102,.55);
    background:rgba(9,14,22,.96);color:#25d366;font-size:1.4rem;
    cursor:pointer;
    transition:transform .25s var(--ease),background .25s var(--ease),border-color .25s var(--ease);
  }
  .wa-dock:hover{background:#25d366;color:#05130a;border-color:#25d366;transform:translateY(-3px)}
  .wa-dock:active{transform:translateY(0) scale(.94)}
  /* the same recoil the dock icon gives, so the two feel related */
  .wa-dock.is-kick{animation:dockKick .42s var(--ease) 1}
  @keyframes dockKick{
    0%{transform:scale(1)}
    26%{transform:scale(.82)}
    58%{transform:scale(1.14)}
    78%{transform:scale(.97)}
    100%{transform:scale(1)}
  }
}

/* ---- 404 ------------------------------------------------------------------
   Centred, generous, and the same palette as everything else. The code line
   is the only decoration: it earns its place by restating the URL the visitor
   actually typed, which is the one fact this page knows and they do not. */
.nf{padding-top:clamp(120px,18vh,190px);padding-bottom:clamp(70px,12vh,120px);min-height:74svh;display:grid;place-items:center}
.nf__wrap{max-width:660px;text-align:center}
.nf__code{
  display:inline-block;text-align:left;
  font:500 clamp(.78rem,2.4vw,.92rem)/1.9 "JetBrains Mono","Fira Code",Consolas,monospace;
  color:var(--text);
  background:var(--surface);border:1px solid var(--line);border-radius:var(--radius-sm);
  padding:.85rem 1.1rem;margin-bottom:clamp(24px,4vw,34px);
  max-width:100%;overflow-x:auto;white-space:nowrap;
}
.nf__punc{color:var(--muted)}
.nf__fn{color:var(--accent-2)}
.nf__str{color:var(--accent)}
.nf__num{color:#ff8f6b;font-weight:700}
.nf__ret{display:block;color:var(--muted)}
.nf__title{font-size:clamp(1.9rem,5vw,3rem);line-height:1.1;margin:0 0 1rem;letter-spacing:-.02em}
.nf__lead{color:var(--muted);max-width:48ch;margin:0 auto;font-size:1.02rem}
.nf__path{
  color:var(--text);background:var(--surface-2);border-radius:6px;
  padding:1px 7px;font-size:.92em;word-break:break-all;
}
.nf__hint{color:var(--muted);font-size:.88rem;margin:clamp(28px,5vw,40px) 0 .8rem;font-weight:550}
.nf__list{list-style:none;padding:0;margin:0 0 .5rem;display:grid;gap:8px;text-align:left}
.nf__list a{
  display:flex;align-items:center;gap:12px;
  padding:.8rem 1rem;border:1px solid var(--line);border-radius:var(--radius-sm);
  background:var(--surface);color:var(--text);text-decoration:none;
  transition:border-color .2s var(--ease),transform .2s var(--ease),background .2s var(--ease);
}
.nf__list a:hover{border-color:var(--accent);background:var(--accent-soft);transform:translateX(3px)}
.nf__kind{
  flex:none;font-size:.68rem;font-weight:700;letter-spacing:.08em;text-transform:uppercase;
  color:var(--accent);background:var(--accent-soft);border-radius:999px;padding:3px 9px;
}
.nf__label{flex:1 1 auto;min-width:0;font-weight:550;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.nf__list i{flex:none;color:var(--muted);font-size:.8rem}
.nf__list a:hover i{color:var(--accent)}
.nf__actions{display:flex;flex-wrap:wrap;gap:10px;justify-content:center;margin-top:clamp(28px,5vw,40px)}
@media (max-width:560px){
  .nf__actions .btn{width:100%;justify-content:center}
}
