/* ============================================================
   UNITIS — v0 placeholder
   One page, seven design languages. The switcher is the point:
   the brand promise is "technology that adapts", so the site does.
   ============================================================ */

/* ---------- fonts (self-hosted, subset to the glyphs this page uses) ---------- */
@font-face { font-family:"Cabinet Grotesk"; src:url("/assets/fonts/cabinet500.woff2") format("woff2");
             font-weight:500; font-display:swap; }
@font-face { font-family:"Cabinet Grotesk"; src:url("/assets/fonts/cabinet800.woff2") format("woff2");
             font-weight:800; font-display:swap; }
@font-face { font-family:"Inter"; src:url("/assets/fonts/inter.woff2") format("woff2");
             font-weight:100 900; font-display:swap; }
@font-face { font-family:"IBM Plex Mono"; src:url("/assets/fonts/plexmono0.woff2") format("woff2");
             font-weight:400; font-display:swap; }
@font-face { font-family:"IBM Plex Mono"; src:url("/assets/fonts/plexmono1.woff2") format("woff2");
             font-weight:500; font-display:swap; }
@font-face { font-family:"Geist"; src:url("/assets/fonts/geist0.woff2") format("woff2");
             font-weight:100 900; font-display:swap; }
@font-face { font-family:"Archivo Black"; src:url("/assets/fonts/archivo.woff2") format("woff2");
             font-weight:400; font-display:swap; }
@font-face { font-family:"Press Start 2P"; src:url("/assets/fonts/pixel.woff2") format("woff2");
             font-weight:400; font-display:swap; }
@font-face { font-family:"Caveat"; src:url("/assets/fonts/caveat0.woff2") format("woff2");
             font-weight:400 700; font-display:swap; }
@font-face { font-family:"Chakra Petch"; src:url("/assets/fonts/chakra0.woff2") format("woff2");
             font-weight:400; font-display:swap; }
@font-face { font-family:"Chakra Petch"; src:url("/assets/fonts/chakra1.woff2") format("woff2");
             font-weight:700; font-display:swap; }
@font-face { font-family:"Share Tech Mono"; src:url("/assets/fonts/techmono.woff2") format("woff2");
             font-weight:400; font-display:swap; }
@font-face { font-family:"Fredoka"; src:url("/assets/fonts/fredoka0.woff2") format("woff2");
             font-weight:300 700; font-display:swap; }
@font-face { font-family:"Inter Tight"; src:url("/assets/fonts/intertight0.woff2") format("woff2");
             font-weight:100 900; font-display:swap; }

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing:border-box; margin:0; padding:0; }
/* Everything lives in one fixed screen — there is nothing to scroll to, and
   locking the page is what lets a horizontal swipe read as a swipe instead of
   fighting an overscroll or a pinch. */
html, body { height:100%; overflow:hidden; overscroll-behavior:none; }
html { -webkit-text-size-adjust:100%; touch-action:pan-x; }
/* Claiming both schemes is what makes Chrome / Samsung Internet hand colour control
   back to the page instead of auto-inverting it under a dark system theme. Every
   language below sets its own colours explicitly, so nothing here follows the OS. */
:root { color-scheme:light dark; }
body {
  min-height:100dvh; font-family:var(--font-body); background:var(--bg); color:var(--fg);
  overflow:hidden; -webkit-font-smoothing:antialiased;
  forced-color-adjust:none;
  transition:background-color .7s var(--ease), color .7s var(--ease);
}
button { font:inherit; color:inherit; background:none; border:0; cursor:pointer; }
a { color:inherit; text-decoration:none; }
canvas { display:block; }
:focus-visible { outline:2px solid var(--focus); outline-offset:3px; border-radius:2px; }

/* ============================================================
   TOKENS — defaults are the "Basic" language; each [data-style]
   below overrides only what that language actually changes.
   ============================================================ */
:root {
  --ease: cubic-bezier(.16,1,.3,1);
  --ease-spring: cubic-bezier(.34,1.56,.64,1);
  --ease-balloon: cubic-bezier(.18,1.9,.36,1);

  --bg:#f3f3f5;  --fg:#0e0e12;  --fg-dim:#6b6b77;
  --focus:#5b45f6;

  /* Basic speaks in three voices: Cabinet Grotesk names it,
     Inter says what it does, IBM Plex Mono handles the machine talk. */
  --font-display:"Cabinet Grotesk", system-ui, sans-serif;
  --font-slogan:"Inter", system-ui, sans-serif;
  --font-body:"IBM Plex Mono", ui-monospace, monospace;

  --brand-size: clamp(3.4rem, 13.5vw, 8.4rem);
  --brand-weight: 800;
  --brand-tracking: .012em;
  --brand-leading: .9;
  --brand-fg: var(--fg);

  /* JS solves the slogan's size so its ink lands flush on the wordmark's;
     this tracking is the ratio it keeps while doing it. */
  --slogan-tracking: .3em;
  --slogan-transform: uppercase;
  --slogan-weight: 500;
  --slogan-fg: var(--fg-dim);
  --lockup-gap: .7rem;

  --hello-size: clamp(1.05rem, 3.4vw, 1.55rem);
  --hello-weight: 400;
  --hello-fg: var(--fg);
  --soon-size: clamp(.75rem, 2.1vw, .9rem);
  --soon-tracking: .3em;
  --soon-fg: var(--fg-dim);

  --mark-w: clamp(96px, 20vw, 168px);
  --mark-stroke: 39;
  --mark-color: url(#ug);
  --mark-filter: none;
  --mark-drop: none;
  --draw-dur: 2.6s;

  --stack-align: center;
  --stack-gap: clamp(1.1rem, 3vw, 1.9rem);

  /* switcher chips */
  --chip-fg:#5a5a66; --chip-bg:transparent; --chip-bd:#d0d0d8;
  --chip-fg-on:#fff;  --chip-bg-on:#141419; --chip-bd-on:#141419;
  --chip-radius: 999px; --chip-font: var(--font-body);
  --chip-size:.68rem; --chip-tracking:.08em; --chip-transform:uppercase; --chip-weight:500;

  /* contacts. --dot is the bar and the toggle; --net-dot is the circle inside it,
     kept smaller so a hover never collides with the bar's edge or its rounded ends. */
  --dot: 46px;
  --net-dot: 36px;
  --con-bg:#ffffff; --con-bd:#e0e0e6; --con-fg:#141419;
  --con-shadow: 0 6px 26px rgb(16 16 28 / .13);
  --con-radius: 999px;
  /* Shape the menu takes once it stops being a pill and becomes a box (the
     two-row phone layout). A pill radius on a tall rectangle just looks like
     a clipped oval, and every language used to get the same 24px regardless
     of its own visual voice — this is what gives each one its own box. */
  --con-radius-open: 22px;
  --con-ease: var(--ease);
  --con-dur: .8s;

  --glow: transparent;
  --scanlines: 0;
  --grain: .028;
}

/* ============================================================
   LAYOUT
   ============================================================ */
.stage {
  position:relative; z-index:1;
  min-height:100dvh;
  display:flex; flex-direction:column;
  align-items:var(--stack-align); justify-content:center;
  text-align:var(--stack-align);
  gap:var(--stack-gap);
  padding: 6.5rem clamp(1.25rem, 6vw, 4rem) 9.5rem;
  touch-action: none;   /* the page owns every gesture: horizontal is the swipe, nothing else scrolls */
}

.scene { display:flex; flex-direction:column; align-items:center; gap:var(--stack-gap); }
.lockup { display:flex; flex-direction:column; align-items:center; gap:var(--lockup-gap); }

.brand {
  font-family:var(--font-display);
  font-size:var(--brand-size);
  font-weight:var(--brand-weight);
  letter-spacing:var(--brand-tracking);
  line-height:var(--brand-leading);
  color:var(--brand-fg);
  /* margin-right is JS's: it skews the box so the flex column centres the ink */
}
.slogan {
  font-family:var(--font-slogan);
  font-weight:var(--slogan-weight);
  letter-spacing:var(--slogan-tracking);
  text-transform:var(--slogan-transform);
  color:var(--slogan-fg);
  white-space:nowrap;
  font-size:1rem;                    /* JS solves this to match the wordmark's ink */
}
.hello {
  font-family:var(--font-body);
  font-size:var(--hello-size);
  font-weight:var(--hello-weight);
  color:var(--hello-fg);
  display:flex; align-items:center; justify-content:var(--stack-align);
  min-height:1.6em;
  perspective:700px;
}
.soon {
  font-family:var(--font-body);
  font-size:var(--soon-size);
  letter-spacing:var(--soon-tracking);
  text-transform:uppercase;
  color:var(--soon-fg);
  margin-right:calc(-1 * var(--soon-tracking));
}
/* "coming soon" only lands once "hello world" has finished writing itself.
   A one-shot animation, not a transition: the class lands seconds after the
   style switch, and a transition that late can be missed entirely. */
.soon { opacity:0; }
.is-typed .soon { animation:soon-in 1.1s var(--ease) both; }
@keyframes soon-in { from { opacity:0; transform:translateY(9px); } to { opacity:1; transform:none; } }

/* ---------- per-character reveals — .in is set by the typer ---------- */
.ch { display:inline-block; white-space:pre; opacity:0; }
.ch.in.ch-pop   { animation:ch-pop   .85s var(--ease-balloon) both; }
.ch.in.ch-blur  { animation:ch-blur  1.1s cubic-bezier(.28,.11,.32,1) both; }
.ch.in.ch-drop  { animation:ch-drop  .8s  var(--ease-spring) both; }
/* hand: a left-to-right ink wipe, as if the nib were laying the letter down now.
   The insets stay negative on every side except the one doing the wiping —
   script faces throw ascenders and tails well outside the line box, and a 0
   would slice them off. */
.ch.in.ch-ink   { animation:ch-ink   .34s linear both; }
.ch-ink { clip-path:inset(-60% 100% -60% 0); }
@keyframes ch-pop  { 0%   { opacity:0; transform:scale(.2) translateY(.5em); }
                     60%  { opacity:1; }
                     100% { opacity:1; transform:none; } }
@keyframes ch-blur { from { opacity:0; filter:blur(12px); transform:translateY(.2em) scale(1.06); }
                     to   { opacity:1; filter:none; transform:none; } }
@keyframes ch-drop { from { opacity:0; transform:translateY(-.9em) rotateX(92deg); }
                     to   { opacity:1; transform:none; } }
@keyframes ch-ink  { from { opacity:1; clip-path:inset(-60% 100% -60% 0); }
                     to   { opacity:1; clip-path:inset(-60% -25% -60% 0); } }

.caret {
  display:inline-block; width:.09em; height:1.05em; margin-left:.12em;
  background:currentColor; animation:blink 1.06s steps(1) infinite;
}
.is-typed .caret { animation:blink 1.6s steps(1) infinite; opacity:.5; }
@keyframes blink { 0%,50%{opacity:1} 50.01%,100%{opacity:0} }

/* ============================================================
   MARK
   ============================================================ */
/* The glow lives on the .mark DIV, not on the inner <svg>. The W's loops nearly
   touch the top of the viewBox, and a drop-shadow on the svg element is clipped to
   the svg viewport — so the upward glow got sheared off in a hard horizontal line
   (most visible in Cyber). An HTML element's drop-shadow blooms past its box
   freely, so the glow escapes cleanly. */
.mark {
  position:relative; width:var(--mark-w); aspect-ratio:591/506;
  filter:var(--mark-drop);
  transition:filter .6s var(--ease);
}
.mark-svg { width:100%; height:100%; overflow:visible; }
/* The freeze-until-you-move-the-mouse bug (Plush + Hand only): the mark's heavy
   SVG filter (turbulence / specular lighting) sits under an infinite scale
   animation. Its timeline keeps advancing, but repainting a full filter every
   frame is so costly the browser defers it and only redraws when some other input
   forces a frame. Caching the filtered SVG as its own compositor texture turns the
   scale into a cheap composited transform of a bitmap, so it animates on its own.
   Scoped to these two: on 3D this translateZ would flatten the extrusion. */
[data-style="plush"] .mark-svg,
[data-style="hand"] .mark-svg { will-change:transform; transform:translateZ(0); }
[data-style="plush"] .mark,
[data-style="hand"] .mark { will-change:transform; }
.mark-face {
  fill:none;
  stroke:var(--mark-color);
  stroke-width:var(--mark-stroke);
  stroke-linejoin:round;
  stroke-linecap:round;
  filter:var(--mark-filter);
  transition:stroke-width .6s var(--ease);
}
.mark-sheen { display:none; }
.mark-extrude { display:none; }
.mark-extrude use {
  fill:none; stroke-linejoin:round; stroke-width:var(--mark-stroke);
  /* --i is the copy's depth index; pointer or gyroscope steers --ex-dx / --ex-dy */
  transform: translate(calc(var(--i) * var(--ex-dx, .4px)),
                       calc(var(--i) * var(--ex-dy, 2px)));
}
.mark-pixels { display:none; position:absolute; inset:0; width:100%; height:100%;
               image-rendering:pixelated; image-rendering:crisp-edges; }

/* the mark draws itself in on load and on every style change */
.mark-face { stroke-dasharray:var(--len); stroke-dashoffset:0; }
.is-drawing .mark-face { animation:draw var(--draw-dur) var(--ease) both; }
@keyframes draw { from { stroke-dashoffset:var(--len); } to { stroke-dashoffset:0; } }

/* ============================================================
   CONTACTS — circle in the corner, unfolds along the top edge
   ============================================================ */
.contacts {
  position:fixed; z-index:5;
  top:clamp(.75rem, 2vw, 1.15rem);
  left:clamp(.75rem, 2vw, 1.15rem);
  right:clamp(.75rem, 2vw, 1.15rem);
  pointer-events:none;
}
.contacts-bar {
  pointer-events:auto;
  display:flex; align-items:center; gap:.4rem;
  width:var(--dot);                 /* a circle until it is asked to be a menu */
  height:var(--dot);
  padding:0;
  border:1px solid var(--con-bd);
  border-radius:var(--con-radius);
  background:var(--con-bg);
  box-shadow:var(--con-shadow);
  overflow:hidden;
  will-change:width, height;
  transition:width var(--con-dur) var(--con-ease),
             background-color .6s var(--ease), border-color .6s var(--ease);
}
.contacts.is-open .contacts-bar { width:100%; }

.contacts-toggle {
  flex:0 0 auto;
  width:var(--dot); height:var(--dot);
  display:grid; place-items:center;
  border-radius:var(--con-radius);
  color:var(--con-fg);
}
.contacts-cross { position:relative; width:15px; height:15px; display:block;
                  transition:transform var(--con-dur) var(--con-ease); }
.contacts-cross i {
  position:absolute; inset:50% 0 auto 0; height:1.6px; margin-top:-.8px;
  background:currentColor; border-radius:2px;
  transition:transform var(--con-dur) var(--con-ease);
}
.contacts-cross i:last-child { transform:rotate(90deg); }
.contacts.is-open .contacts-cross { transform:rotate(135deg); }

.contacts-list {
  list-style:none; display:flex; align-items:center;
  flex:1 1 auto; min-width:0;
  justify-content:space-evenly;
  padding:.3rem .3rem .3rem 0;
  /* ten filtered icons are not free: skip rendering them entirely while shut */
  visibility:hidden;
}
.contacts.is-open .contacts-list { visibility:visible; }
/* Only once the row genuinely runs out of room does it become a scroller — that
   way the hover lift is never clipped on a desktop that has space to spare.
   Overflowing space-evenly collapses every gap to zero, so it starts flush left
   and nothing gets stranded off the edge. */
.contacts-list.is-tight { overflow-x:auto; scrollbar-width:none; }
.contacts-list.is-tight::-webkit-scrollbar { display:none; }
.contacts-list li { display:flex; }
.contact {
  flex:0 0 auto;                 /* never let a circle squash into an ellipse */
  width:var(--net-dot); height:var(--net-dot);
  display:grid; place-items:center;
  border-radius:var(--con-radius);
  color:var(--con-fg);
  opacity:0; transform:scale(.3);
  /* the icon's own filter (pixel/hand/plush) shouldn't be re-rasterised on every
     frame of its scale-in — this promotes it to its own layer so the browser
     paints the filtered icon once and animates the cached result. Without it,
     Safari re-runs the filter for every frame of the entrance, which is what
     reads as the menu stuttering open on iPhone. */
  will-change:transform, opacity;
  transition:transform .3s var(--ease-spring), background-color .3s var(--ease), color .3s var(--ease);
}
/* every mark goes through its language's own treatment, exactly like the logo */
.contact svg { width:60%; height:60%; fill:currentColor; pointer-events:none;
               filter:var(--net-filter, none); }
.contact span { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); }
/* accounts that do not exist yet: present, but not pretending to be links */
.contact.is-soon { opacity:.42; cursor:default; }
.contacts.is-open .contact.is-soon { animation-name:contact-in-soon; }
@keyframes contact-in-soon { from { opacity:0; transform:scale(.3) translateY(-6px); }
                             to   { opacity:.42; transform:none; } }
.contact.is-soon:hover { background:none; color:var(--con-fg); transform:none; }
.contacts.is-open .contact { animation:contact-in .5s var(--ease-spring) both;
                             animation-delay:calc(var(--n) * 55ms + 110ms); }
@keyframes contact-in { from { opacity:0; transform:scale(.3) translateY(-6px); }
                        to   { opacity:1; transform:none; } }
/* no lift, no growth: the bar clips its overflow, so a hover that grows past
   --net-dot gets sliced by the pill's edge. The colour does the talking. */
.contact:hover { background:var(--net); color:#fff; }
.contact:active { transform:scale(.92); }

/* each mark keeps its own brand colour on hover */
[data-net="instagram"] { --net:#E4405F; }
[data-net="threads"]   { --net:#000000; }
[data-net="telegram"]  { --net:#26A5E4; }
[data-net="viber"]     { --net:#7360F2; }
[data-net="whatsapp"]  { --net:#25D366; }
[data-net="facebook"]  { --net:#0866FF; }
[data-net="messenger"] { --net:#0084FF; }
[data-net="tiktok"]    { --net:#000000; }
[data-net="phone"]     { --net:#5b45f6; }
[data-net="mail"]      { --net:#5b45f6; }

/* ============================================================
   SWITCHER
   ============================================================ */
.switch {
  position:fixed; z-index:3; left:0; right:0; bottom:0;
  display:flex; flex-direction:column; align-items:center; gap:.6rem;
  padding: 1rem clamp(.75rem,4vw,2rem) calc(1.15rem + env(safe-area-inset-bottom));
  background:linear-gradient(to top, var(--bg) 42%, transparent);
  pointer-events:none;
}
.switch > * { pointer-events:auto; }
.switch-hint {
  font-family:var(--font-body);
  font-size:.6rem; letter-spacing:.2em; text-transform:uppercase;
  color:var(--fg-dim); opacity:.72;
}
.switch-row {
  display:flex; gap:.3rem; max-width:100%;
  overflow-x:auto; scrollbar-width:none; scroll-snap-type:x proximity;
  padding:.25rem;
}
.switch-row::-webkit-scrollbar { display:none; }
.switch-row.is-scrollable {
  -webkit-mask-image:linear-gradient(to right, transparent, #000 22px,
                                     #000 calc(100% - 22px), transparent);
          mask-image:linear-gradient(to right, transparent, #000 22px,
                                     #000 calc(100% - 22px), transparent);
}
.switch-row button {
  flex:0 0 auto; scroll-snap-align:center;
  font-family:var(--chip-font);
  font-size:var(--chip-size);
  font-weight:var(--chip-weight);
  letter-spacing:var(--chip-tracking);
  text-transform:var(--chip-transform);
  color:var(--chip-fg);
  background:var(--chip-bg);
  border:1px solid var(--chip-bd);
  border-radius:var(--chip-radius);
  padding:.5rem .8rem;
  white-space:nowrap;
  transition:color .3s var(--ease), background-color .3s var(--ease),
             border-color .3s var(--ease), transform .18s var(--ease-spring);
}
.switch-row button:hover { transform:translateY(-1px); }
.switch-row button:active { transform:translateY(0) scale(.97); }
.switch-row button[aria-checked="true"] {
  color:var(--chip-fg-on); background:var(--chip-bg-on); border-color:var(--chip-bd-on);
}

/* The chips stay on every device: swipe is the shortcut, not the only way in.
   The composition itself follows the finger during a drag, so it needs no
   separate page-position indicator underneath. */

/* the whole composition follows the finger, then commits or springs back */
.stage.is-dragging { transition:none; }
.stage.is-settling { transition:transform .42s var(--ease-spring), opacity .42s var(--ease); }

/* ============================================================
   FX LAYERS — fixed, never on a scrolling container
   ============================================================ */
.fx { position:fixed; inset:0; z-index:0; pointer-events:none; }
.fx-glow {
  position:absolute; inset:0; opacity:0; background:var(--glow);
  transition:opacity .8s var(--ease);
}
.fx-scanlines {
  position:absolute; inset:0; opacity:var(--scanlines);
  background:repeating-linear-gradient(to bottom,
    rgb(0 0 0 / .30) 0 1px, transparent 1px 3px);
  transition:opacity .6s var(--ease);
  mix-blend-mode:multiply;
}
.fx-grain {
  position:absolute; inset:-50%; opacity:var(--grain);
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
  transition:opacity .6s var(--ease);
}
.fx-canvas, .fx-scene { position:absolute; inset:0; width:100%; height:100%; }
.fx-canvas { display:none; }
.fx-canvas.on { display:block; }

/* ============================================================
   SCENES — each language stages its own world behind the content.
   Only the active one is ever built; JS tears the rest down.
   ============================================================ */

/* Basic's light is painted on the canvas now: a 44px-wide texture stretched over
   the viewport, so the browser's upscale does the blurring for nothing. */
.veil {
  position:absolute; inset:0;
  background:radial-gradient(52vmax 40vmax at 50% 46%,
             rgb(255 255 255 / .62) 0%, rgb(255 255 255 / .3) 45%, transparent 72%);
}

/* 3D: a room with real depth, suspended around the mark. The tilt (pointer or
   the phone's own gyroscope) turns the whole room; every occupant then drifts,
   spins and breathes on its own, using the individual `translate`/`rotate`/
   `scale` properties instead of the shorthand `transform` — that is what lets
   a static depth (z) and a live animation share one element without one
   overwriting the other every frame. */
.blocks {
  position:absolute; inset:0;
  perspective:1300px; perspective-origin:50% 45%;
}
.blocks-inner {
  position:absolute; inset:0; transform-style:preserve-3d;
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transition:transform .4s var(--ease);
}
/* the deep light: a slow volumetric pulse behind everything else, as if the
   room itself were breathing. Scoped to three only — .fx-glow is shared. */
[data-style="three"] .fx-glow { animation:glow-breathe 7s ease-in-out infinite; }
@keyframes glow-breathe {
  0%, 100% { scale:1;    opacity:.85; }
  50%      { scale:1.14; opacity:1;   }
}
/* soft volumetric bokeh, scattered through the depth of the room. A radial
   glow blurred by an amount tied to its own depth (--ob) is what sells "far
   away" far better than a flat, evenly-lit circle ever could. */
.orb {
  position:absolute; border-radius:50%;
  background:radial-gradient(circle at 36% 30%, var(--oc) 0%, transparent 70%);
  filter:blur(var(--ob));
  transform-style:preserve-3d;
  translate:0 0 var(--z);
  opacity:var(--o);
  will-change:translate, scale, opacity;
  animation:orb-drift var(--t) ease-in-out var(--d) infinite,
            orb-pulse var(--pt) ease-in-out var(--pd) infinite;
}
@keyframes orb-drift {
  0%, 100% { translate:0 0 var(--z); }
  50%      { translate:var(--dx) var(--dy) var(--z); }
}
@keyframes orb-pulse {
  0%, 100% { scale:1;    opacity:var(--o); }
  50%      { scale:1.4;  opacity:calc(var(--o) * 1.7); }
}
/* Real cubes tumbling on their own arbitrary axis — the solid, faceted
   counterpart to the orbs' softness. Six translucent faces so you see through
   the near ones to the far ones as it turns, which is what sells it as a volume
   rather than a spinning card. Fixed depth (translate), animated rotation only. */
.cube {
  position:absolute;
  width:var(--s); height:var(--s);
  transform-style:preserve-3d;
  translate:0 0 var(--z);
  opacity:var(--o);
  will-change:rotate;
  animation:cube-tumble var(--t) linear var(--d) infinite;
}
.cube i {
  position:absolute; inset:0;
  background:linear-gradient(150deg, color-mix(in srgb, var(--cc) 55%, transparent), transparent 80%);
  border:1px solid color-mix(in srgb, var(--cc) 75%, transparent);
  /* each face pushed out to half the cube's side to close the box */
  transform:var(--f) translateZ(calc(var(--s) / 2));
}
@keyframes cube-tumble {
  from { rotate:var(--ax) 0deg; }
  to   { rotate:var(--ax) 360deg; }
}

/* Plush: balloons rising, each one lit from the same corner as the mark */
.balloon {
  position:absolute; border-radius:50%;
  background:
    radial-gradient(circle at 32% 26%, rgb(255 255 255 / .95) 0 6%, rgb(255 255 255 / .35) 12%, transparent 34%),
    radial-gradient(circle at 68% 78%, rgb(255 255 255 / .3) 0 14%, transparent 42%),
    radial-gradient(circle at 40% 36%, var(--c1), var(--c2) 76%);
  box-shadow: inset -6px -10px 18px rgb(120 30 95 / .25), 0 12px 26px rgb(150 40 120 / .18);
  animation:rise-up var(--t) linear var(--d) infinite;
  will-change:transform, opacity;
}
@keyframes rise-up {
  0%   { transform:translate3d(0, 22vh, 0) scale(.9); opacity:0; }
  12%  { opacity:.85; }
  88%  { opacity:.85; }
  100% { transform:translate3d(var(--dx), -118vh, 0) scale(1.05); opacity:0; }
}

/* Hand drawn: paper that has been folded and flattened out again. Static image,
   baked once by the browser: turbulence lit from one side is what reads as creases. */
.crumple {
  position:absolute; inset:0;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='620' height='620'%3E%3Cfilter id='c'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.012 0.015' numOctaves='4' seed='8' result='n'/%3E%3CfeDiffuseLighting in='n' lighting-color='%23ffffff' surfaceScale='2.4'%3E%3CfeDistantLight azimuth='45' elevation='56'/%3E%3C/feDiffuseLighting%3E%3C/filter%3E%3Crect width='620' height='620' filter='url(%23c)'/%3E%3C/svg%3E");
  background-size:760px 760px;
  mix-blend-mode:multiply;
  opacity:.42;
}

/* Hand drawn: doodles in the margins, drawing themselves in */
/* The graphite is on from the first stroke, not painted on afterwards. The filter
   is deliberately light (2 octaves) and the doodles are small, so re-running it
   during the short staggered draw stays cheap. */
.doodle { position:absolute; overflow:visible; opacity:.5; filter:url(#f-pencil); }
.doodle path, .doodle circle, .doodle line, .doodle polyline, .doodle ellipse {
  fill:none; stroke:#211f1b; stroke-width:2.4; stroke-linecap:round; stroke-linejoin:round;
  stroke-dasharray:var(--l); stroke-dashoffset:var(--l);
  animation:scribble 1.1s var(--ease) var(--d) forwards;
}
@keyframes scribble { to { stroke-dashoffset:0; } }

/* Minimal: one soft light, catching the surface where the pointer is. Nothing else. */
.sheet {
  position:absolute; inset:0;
  background:radial-gradient(58vmax 58vmax at var(--mx, 50%) var(--my, 32%),
             rgb(0 113 227 / .07) 0%, rgb(88 86 214 / .04) 34%, transparent 68%);
  transition:background-position 1.6s var(--ease);
}
.sheet-breath {
  position:absolute; left:50%; top:38%; width:70vmax; height:70vmax;
  transform:translate(-50%,-50%);
  background:radial-gradient(closest-side, rgb(0 0 0 / .035), transparent 70%);
  animation:breath-slow 9s ease-in-out infinite;
}
@keyframes breath-slow { 0%,100% { scale:1; opacity:.8; } 50% { scale:1.08; opacity:.45; } }

/* ============================================================
   1 · BASIC — Cabinet Grotesk names it, Inter explains, Plex Mono reports
   ============================================================ */
[data-style="basic"] { --glow: radial-gradient(120% 80% at 50% 0%, #e9e6ff 0%, transparent 60%); }
[data-style="basic"] .fx-glow { opacity:1; }

/* ============================================================
   2 · 3D — an extruded object in a dark studio, steered by pointer or gyroscope
   ============================================================ */
[data-style="three"] {
  --bg:#0e1016; --fg:#ffffff; --fg-dim:#8e93a6; --focus:#8b7bff;
  --font-display:"Archivo Black", system-ui, sans-serif;
  --font-slogan:"Geist", system-ui, sans-serif;
  --font-body:"Geist", system-ui, sans-serif;
  --brand-weight:400; --brand-tracking:-.015em; --brand-size:clamp(3rem,12vw,7.4rem);
  --slogan-tracking:.34em; --slogan-weight:500;
  --lockup-gap:1.1rem;
  --mark-w:clamp(118px,24vw,205px);
  --mark-stroke:41;
  --draw-dur:2.2s;
  --chip-fg:#a7abbd; --chip-bd:#2b2f3d;
  --chip-fg-on:#0b0d13; --chip-bg-on:#ffffff; --chip-bd-on:#ffffff;
  --con-bg:#171a24; --con-bd:#2b2f3d; --con-fg:#e7e9f2;
  --con-shadow: 0 10px 34px rgb(0 0 0 / .55);
  --con-radius-open: 16px;
  --glow: radial-gradient(90% 70% at 50% 18%, #2a3049 0%, transparent 62%),
          radial-gradient(55% 34% at 50% 92%, #1c2133 0%, transparent 70%);
  --grain:.05;
}
[data-style="three"] .fx-glow { opacity:1; }
/* one 3D scene: mark and wordmark tilt together, so it reads as an object, not a filter */
[data-style="three"] .scene {
  perspective:1100px; perspective-origin:50% 45%;
  transform-style:preserve-3d;
}
[data-style="three"] .mark,
[data-style="three"] .lockup {
  transform: rotateX(var(--tilt-x, 0deg)) rotateY(var(--tilt-y, 0deg));
  transform-style:preserve-3d;
  /* shorter, promoted: a long transition read as the object "lagging behind" the
     pointer and revealing edges late; will-change keeps the layer ready so it
     tracks instead of catching up */
  transition:transform .18s linear;
  will-change:transform;
}
[data-style="three"] .mark { animation:rise .95s var(--ease) both, float3d 7s ease-in-out 1s infinite; }
@keyframes float3d {
  0%,100% { translate:0 0; } 50% { translate:0 -10px; }
}
[data-style="three"] .mark-extrude { display:block; }
/* a specular streak travelling across the face sells the "solid object" more than depth alone */
[data-style="three"] .mark-sheen {
  display:block; fill:none; stroke:#fff; stroke-width:var(--mark-stroke);
  stroke-linejoin:round; stroke-linecap:round;
  stroke-dasharray:120 2300; opacity:.5;
  filter:blur(5px); mix-blend-mode:overlay;
  animation:sheen 5.5s cubic-bezier(.5,0,.5,1) 1.2s infinite;
}
@keyframes sheen { 0% { stroke-dashoffset:2420; } 55%,100% { stroke-dashoffset:0; } }
/* floor contact shadow, so the object sits in the studio instead of floating in space */
[data-style="three"] .mark::after {
  content:""; position:absolute; left:12%; right:12%; bottom:-16%; height:16px;
  background:radial-gradient(closest-side, rgb(0 0 0 / .6), transparent);
  filter:blur(7px); transform:translateZ(-90px);
  animation:shadow3d 7s ease-in-out 1s infinite;
}
@keyframes shadow3d { 0%,100% { opacity:.7; scale:1; } 50% { opacity:.45; scale:.88; } }
/* the icons are extruded objects too, lit from the same corner as the mark */
[data-style="three"] { --net-filter: drop-shadow(1px 1px 0 #6d4bf0) drop-shadow(2px 2px 0 #4f3bc6)
                                     drop-shadow(3px 3px 0 #2c2789) drop-shadow(4px 5px 4px rgb(0 0 0 / .5)); }
/* The wordmark is a real extruded object: each letter is a stack of copies
   pushed back along Z (built in JS as .g3 > i layers). When the lockup tilts,
   the stack reveals actual sides instead of a flat shadow sliding around.
   The whole chain from the rotation down to the layers must stay preserve-3d. */
[data-style="three"] .lockup { transform-style:preserve-3d; }
[data-style="three"] .brand { transform-style:preserve-3d; text-shadow:none; }
.brand.is3d { transform-style:preserve-3d; }
.brand.is3d .g3 { display:inline-block; position:relative; transform-style:preserve-3d; }
.brand.is3d .g3 > i { font-style:normal; }
.brand.is3d .g3 > i.front { position:relative; }
.brand.is3d .g3 > i:not(.front) { position:absolute; inset:0; }
/* a hair of shine sitting just proud of the front face */
.brand.is3d .g3 > i.front { text-shadow:0 1px 0 rgb(255 255 255 / .6); }
[data-style="three"] .hello { text-shadow:2px 2px 0 rgb(0 0 0 / .5); }

/* ============================================================
   3 · PIXEL GEEK — the mark is rasterised to 8-bit on a canvas
   ============================================================ */
[data-style="pixel"] {
  --bg:#0d0b1f; --fg:#e9e4ff; --fg-dim:#8b7fd4; --focus:#01c1de;
  --font-display:"Press Start 2P", monospace;
  --font-slogan:"Press Start 2P", monospace;
  --font-body:"Press Start 2P", monospace;
  --brand-size:clamp(1.6rem,6.8vw,3.9rem);
  --brand-weight:400; --brand-tracking:.02em; --brand-leading:1.3;
  --slogan-tracking:.1em; --slogan-weight:400;
  --lockup-gap:1rem;
  --hello-size:clamp(.6rem,2.1vw,.95rem);
  --soon-size:clamp(.48rem,1.5vw,.62rem); --soon-fg:#01c1de; --soon-tracking:.12em;
  --mark-w:clamp(104px,21vw,176px);
  --draw-dur:2s;
  --stack-gap:clamp(1.3rem,3.4vw,2.2rem);
  --chip-fg:#8b7fd4; --chip-bd:#2c2456; --chip-radius:0;
  --chip-fg-on:#0d0b1f; --chip-bg-on:#01c1de; --chip-bd-on:#01c1de;
  --chip-size:.5rem; --chip-tracking:0; --chip-transform:none;
  --con-bg:#171142; --con-bd:#3b2f7a; --con-fg:#e9e4ff;
  --con-radius:0; --con-radius-open:0; --con-shadow: 4px 4px 0 #01c1de;
  --con-ease: steps(9, end); --con-dur:.55s;
  --glow: radial-gradient(100% 70% at 50% 45%, #1d1747 0%, transparent 65%);
  --scanlines:.55;
  --grain:.06;
}
[data-style="pixel"] .fx-glow { opacity:1; }
[data-style="pixel"] .mark-svg { visibility:hidden; }
[data-style="pixel"] .mark-pixels { display:block; }
/* the mark paints itself on in bands, top to bottom, like a sprite loading a
   scanline at a time. steps() makes the clip jump row-block by row-block. */
.mark-pixels.reveal { animation:pix-rows 1s steps(16) both; }
@keyframes pix-rows { from { clip-path:inset(0 0 100% 0); } to { clip-path:inset(0 0 0 0); } }
[data-style="pixel"] .caret { width:.6em; height:1em; margin-left:.18em; animation-duration:.72s; }
[data-style="pixel"] .brand { text-shadow:3px 3px 0 #01c1de; }
[data-style="pixel"] .contact { border-radius:0; }
[data-style="pixel"] .contacts-cross i { border-radius:0; height:2px; }
/* the icons get quantised to a grid, same as the mark does on its canvas */
[data-style="pixel"] { --net-filter:url(#f-pixel-icon); }
/* a sprite never eases: it hops on whole frames, like a character idling in a game */
[data-style="pixel"] .mark { animation:rise .95s var(--ease) both, hop 1.6s steps(1) 1s infinite; }
@keyframes hop {
  0%, 45%  { translate:0 0; }
  50%      { translate:0 -6px; }
  55%      { translate:0 -9px; }
  60%      { translate:0 -6px; }
  65%,100% { translate:0 0; }
}
[data-style="pixel"] .mark-pixels { animation:blink-frame 2.9s steps(1) 2s infinite; }
@keyframes blink-frame { 0%,97%,100% { opacity:1; } 98% { opacity:.25; } 99% { opacity:1; } }

/* ============================================================
   4 · HAND DRAWN — ink on paper, pressed in with a pencil
   ============================================================ */
[data-style="hand"] {
  --bg:#faf7ef; --fg:#211f1b; --fg-dim:#6f6a5e; --focus:#c0562f;
  --font-display:"Caveat", cursive;
  --font-slogan:"Caveat", cursive;
  --font-body:"Caveat", cursive;
  --brand-size:clamp(3.2rem,11.5vw,6.8rem);
  --brand-weight:700; --brand-tracking:.01em; --brand-leading:.95;
  --slogan-tracking:.04em; --slogan-transform:none; --slogan-weight:400;
  --lockup-gap:.35rem;
  --hello-size:clamp(1.3rem,4vw,1.9rem);
  --soon-size:clamp(.9rem,2.4vw,1.1rem); --soon-tracking:.06em;
  --mark-w:clamp(104px,21vw,178px);
  --mark-stroke:33;
  --mark-color:#211f1b;
  --mark-filter:url(#f-hand);
  --draw-dur:3s;
  --chip-fg:#6f6a5e; --chip-bd:#c9c2b0; --chip-radius:14px 10px 16px 8px;
  --chip-fg-on:#faf7ef; --chip-bg-on:#211f1b; --chip-bd-on:#211f1b;
  --chip-size:.92rem; --chip-tracking:0; --chip-transform:none; --chip-weight:700;
  --con-bg:#fffdf6; --con-bd:#c9c2b0; --con-fg:#211f1b;
  --con-radius: 26px 22px 28px 20px;
  --con-radius-open: 22px 30px 18px 26px;
  --con-shadow: 0 3px 0 rgb(33 31 27 / .16);
  --con-ease: cubic-bezier(.5,1.5,.4,1); --con-dur:.75s;
  --glow: radial-gradient(110% 80% at 50% 0%, #fffdf6 0%, transparent 55%);
  --grain:.09;
}
[data-style="hand"] .fx-glow { opacity:1; }
[data-style="hand"] .soon { text-transform:none; }
[data-style="hand"] .slogan { transform:rotate(-1.1deg); }
/* nobody writes with a blinking cursor: the nib itself is the cue here */
[data-style="hand"] .caret { display:none; }
/* the wipe reaches outside the line box, so the line must not clip it */
[data-style="hand"] .hello { overflow:visible; padding:.25em 0; }
/* ruled paper, drawn under the content */
[data-style="hand"] .fx-scanlines {
  opacity:.5; mix-blend-mode:multiply;
  background:repeating-linear-gradient(to bottom, transparent 0 41px, #cfd9e6 41px 42px);
}
/* Pencil pressed into paper: the groove catches a shadow at the top-left and
   throws a paper highlight at the bottom-right. Applied to every mark on the page. */
[data-style="hand"] .brand,
[data-style="hand"] .slogan,
[data-style="hand"] .hello,
[data-style="hand"] .soon {
  text-shadow:
    0 -1px 0 rgb(0 0 0 / .22),
    0  1px 0 rgb(255 255 255 / .95),
    0  2px 1px rgb(255 255 255 / .55);
}
[data-style="hand"] .mark-svg {
  filter: drop-shadow(0 -1px 0 rgb(0 0 0 / .18)) drop-shadow(0 1.5px .5px rgb(255 255 255 / .9));
}
[data-style="hand"] .contacts-bar,
[data-style="hand"] .switch-row button {
  box-shadow: inset 0 1px 2px rgb(33 31 27 / .14), 0 1px 0 rgb(255 255 255 / .9);
}
/* the icons are drawn by the same hand, at icon scale and holding still */
[data-style="hand"] { --net-filter:url(#f-hand-icon); }

/* ============================================================
   5 · CYBERPUNK — neon, chromatic split, glitch bursts
   ============================================================ */
[data-style="cyber"] {
  --bg:#05060b; --fg:#dff6ff; --fg-dim:#5f7b91; --focus:#ff2d78;
  --font-display:"Chakra Petch", sans-serif;
  --font-slogan:"Chakra Petch", sans-serif;
  --font-body:"Share Tech Mono", monospace;
  --brand-size:clamp(3.4rem,14vw,8.6rem);
  --brand-weight:700; --brand-tracking:.05em;
  --slogan-tracking:.36em; --slogan-fg:#ff2d78; --slogan-weight:400;
  --lockup-gap:.9rem;
  --hello-fg:#01c1de;
  --soon-fg:#5f7b91;
  --mark-w:clamp(104px,21vw,180px);
  --mark-stroke:35;
  --mark-drop: drop-shadow(0 0 4px #01c1de) drop-shadow(0 0 18px rgb(1 193 222 / .75))
               drop-shadow(0 0 44px rgb(117 35 249 / .55));
  --draw-dur:2.4s;
  --chip-fg:#5f7b91; --chip-bd:#153042; --chip-radius:0;
  --chip-fg-on:#05060b; --chip-bg-on:#01c1de; --chip-bd-on:#01c1de;
  --chip-size:.62rem; --chip-tracking:.16em;
  --con-bg:#080d16; --con-bd:#153042; --con-fg:#01c1de;
  --con-radius:0; --con-radius-open:0; --con-shadow: 0 0 22px rgb(1 193 222 / .3);
  --con-ease: cubic-bezier(.9,0,.1,1); --con-dur:.5s;
  --glow: radial-gradient(80% 60% at 50% 100%, rgb(1 193 222 / .18) 0%, transparent 65%),
          radial-gradient(70% 50% at 50% 0%, rgb(117 35 249 / .22) 0%, transparent 65%);
  --scanlines:.5;
  --grain:.07;
}
[data-style="cyber"] .fx-glow { opacity:1; }
[data-style="cyber"] .fx-scanlines { mix-blend-mode:normal; }
[data-style="cyber"] .contact { border-radius:0; box-shadow:inset 0 0 0 1px rgb(1 193 222 / .25); }
[data-style="cyber"] { --net-filter: drop-shadow(0 0 3px currentColor) drop-shadow(0 0 9px rgb(1 193 222 / .8)); }
[data-style="cyber"] .contacts-cross i { border-radius:0; }
/* A failing neon tube: the gas strikes in bursts, drops out, catches again, then
   burns steady for a while. Irregular on purpose — an even blink reads as a caret,
   not a bulb. Two layers: the tube (brightness) and the glitch (chromatic tear). */
[data-style="cyber"] .brand {
  color:#eaffff;
  animation:bulb 7s steps(1) infinite, glitch 6.5s steps(1) infinite;
}
@keyframes bulb {
  0%, 3%   { opacity:.28; text-shadow:0 0 2px rgb(1 193 222 / .3); }
  4%       { opacity:1;   text-shadow:0 0 7px #01c1de, 0 0 26px rgb(1 193 222 / .65), 0 0 60px rgb(1 193 222 / .3); }
  5%       { opacity:.35; text-shadow:0 0 3px rgb(1 193 222 / .4); }
  6%, 22%  { opacity:1;   text-shadow:0 0 7px #01c1de, 0 0 26px rgb(1 193 222 / .65), 0 0 60px rgb(1 193 222 / .3); }
  23%      { opacity:.4;  text-shadow:0 0 3px rgb(1 193 222 / .4); }
  24%      { opacity:1;   text-shadow:0 0 7px #01c1de, 0 0 26px rgb(1 193 222 / .65), 0 0 60px rgb(1 193 222 / .3); }
  57%      { opacity:.85; text-shadow:0 0 5px rgb(1 193 222 / .7), 0 0 18px rgb(1 193 222 / .4); }
  58%      { opacity:.3;  text-shadow:0 0 2px rgb(1 193 222 / .3); }
  59%      { opacity:1;   text-shadow:0 0 7px #01c1de, 0 0 26px rgb(1 193 222 / .65), 0 0 60px rgb(1 193 222 / .3); }
  60%      { opacity:.5;  text-shadow:0 0 4px rgb(1 193 222 / .5); }
  61%, 89% { opacity:1;   text-shadow:0 0 7px #01c1de, 0 0 26px rgb(1 193 222 / .65), 0 0 60px rgb(1 193 222 / .3); }
  90%      { opacity:.32; text-shadow:0 0 2px rgb(1 193 222 / .3); }
  91%,100% { opacity:1;   text-shadow:0 0 7px #01c1de, 0 0 26px rgb(1 193 222 / .65), 0 0 60px rgb(1 193 222 / .3); }
}
@keyframes glitch {
  0%,93%   { transform:none; clip-path:none; }
  94%      { transform:translate(-3px,1px) skewX(-4deg); }
  95%      { transform:translate(2px,-1px) skewX(3deg); }
  96%      { transform:translate(-1px,0); }
  97%,100% { transform:none; }
}
[data-style="cyber"] .slogan { animation:flicker 4.2s ease-in-out infinite; }
@keyframes flicker { 0%,100%{opacity:1} 47%{opacity:1} 48%{opacity:.35} 49%{opacity:1} 78%{opacity:.6} 79%{opacity:1} }

/* ============================================================
   6 · PLUSH — an inflated balloon: bright, glossy, squeezable
   ============================================================ */
[data-style="plush"] {
  --bg:#ffe9f6; --fg:#3d1f6b; --fg-dim:#8358c9; --focus:#ff3fa4;
  --font-display:"Fredoka", system-ui, sans-serif;
  --font-slogan:"Fredoka", system-ui, sans-serif;
  --font-body:"Fredoka", system-ui, sans-serif;
  --brand-size:clamp(3.6rem,14vw,8.6rem);
  --brand-weight:600; --brand-tracking:-.005em;
  --slogan-tracking:.16em; --slogan-weight:500; --slogan-fg:#8358c9;
  --lockup-gap:.85rem;
  --hello-size:clamp(1.25rem,4vw,1.9rem); --hello-weight:500;
  --soon-fg:#ff3fa4; --soon-size:clamp(.8rem,2.2vw,.98rem);
  --mark-w:clamp(124px,25vw,215px);
  --mark-stroke:52;
  --mark-filter:url(#f-plush);
  --mark-drop: drop-shadow(0 18px 24px rgb(150 40 120 / .3));
  --draw-dur:2.4s;
  --chip-fg:#8358c9; --chip-bd:#ffc0e6; --chip-bg:#fff6fc;
  --chip-fg-on:#fff; --chip-bg-on:#ff3fa4; --chip-bd-on:#ff3fa4;
  --chip-size:.74rem; --chip-tracking:.02em; --chip-transform:none; --chip-weight:600;
  --con-bg:#fff6fc; --con-bd:#ffc0e6; --con-fg:#7b3fd4;
  --con-radius-open: 30px 34px 26px 30px;
  --con-shadow: 0 6px 0 rgb(255 63 164 / .28), 0 14px 30px rgb(150 40 120 / .22);
  --con-ease: var(--ease-balloon); --con-dur:.95s;
  --glow: radial-gradient(85% 65% at 50% 12%, #fffdff 0%, transparent 62%),
          radial-gradient(60% 45% at 85% 95%, #d6f0ff 0%, transparent 70%);
  --grain:.035;
}
[data-style="plush"] .fx-glow { opacity:1; }
/* The wordmark is inflated by the same lighting model as the mark, not faked with
   flat shadows: a gradient skin under a real specular highlight. The rounded face
   of Fredoka is what lets it read as rubber. */
[data-style="plush"] .brand {
  color:transparent;
  /* brighter and more saturated than before: a hot-pink crown falling to electric
     magenta and vivid violet. The gloss still comes from #f-plush-text's specular
     highlight and the extruded body from the stacked shadow — only the skin is punchier. */
  background:linear-gradient(168deg, #ffb0ea 0%, #ff1493 28%, #e40ecd 58%, #7d18f5 100%);
  -webkit-background-clip:text; background-clip:text;
  filter:url(#f-plush-text);
  /* the balloon skin sits on the letters; the drop below gives it a body to cast from */
  text-shadow:
    0 2px 0 #ffcbf0, 0 4px 0 #f75cc4, 0 6px 0 #db2f9f,
    0 8px 0 #b81d84, 0 11px 0 #8c1268, 0 20px 24px rgb(120 20 100 / .45);
}
[data-style="plush"] .brand-inflate { animation:inflate 4s ease-in-out 1.2s infinite; }
@keyframes inflate { 0%,100% { scale:1 1; } 50% { scale:1.018 .988; } }
/* the whole balloon breathes, and squashes when you poke it */
[data-style="plush"] .mark { animation:rise .95s var(--ease) both, breathe 4s ease-in-out 1s infinite; }
@keyframes breathe { 0%,100%{ scale:1 1; } 50%{ scale:1.06 .96; } }
[data-style="plush"] .mark:hover,
[data-style="plush"] .mark:active { animation:squish .8s var(--ease-balloon); }
@keyframes squish {
  0%   { scale:1 1; }
  28%  { scale:1.22 .78; }
  52%  { scale:.88 1.14; }
  74%  { scale:1.06 .95; }
  100% { scale:1 1; }
}
[data-style="plush"] .switch-row button { box-shadow:0 3px 0 rgb(255 63 164 / .3); }
[data-style="plush"] .switch-row button[aria-checked="true"] { box-shadow:0 3px 0 #c9268a; }
[data-style="plush"] .contact { box-shadow:0 3px 0 rgb(255 63 164 / .22); }
/* the icons inflate too, re-cut for a 24-unit box */
[data-style="plush"] { --net-filter:url(#f-plush-icon); }

/* ============================================================
   7 · MINIMAL — apple.com: SF Pro, tight tracking, air, their easing
   ============================================================ */
[data-style="minimal"] {
  --bg:#fbfbfd; --fg:#1d1d1f; --fg-dim:#86868b; --focus:#0071e3;
  /* Real SF Pro on Apple hardware. Inter Tight is the stand-in everywhere else. */
  --font-display:-apple-system, BlinkMacSystemFont, "SF Pro Display", "Inter Tight", system-ui, sans-serif;
  --font-slogan:-apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter Tight", system-ui, sans-serif;
  --font-body:-apple-system, BlinkMacSystemFont, "SF Pro Text", "Inter Tight", system-ui, sans-serif;
  /* Apple's own headline easing: slow out, long settle. */
  --ease: cubic-bezier(.28,.11,.32,1);
  --brand-size:clamp(2.8rem,10vw,6rem);
  --brand-weight:600; --brand-tracking:-.028em; --brand-leading:1.05;
  --slogan-tracking:.012em; --slogan-transform:none; --slogan-weight:400; --slogan-fg:#86868b;
  --lockup-gap:.5rem;
  --hello-size:clamp(1rem,2.8vw,1.28rem); --hello-fg:#86868b; --hello-weight:400;
  --soon-size:clamp(.76rem,2vw,.88rem); --soon-tracking:0; --soon-fg:#86868b;
  --mark-w:clamp(74px,15vw,120px);
  --mark-stroke:30;
  --draw-dur:3.2s;
  --stack-gap:clamp(1.5rem,4vw,2.6rem);
  --chip-fg:#86868b; --chip-bd:#d2d2d7;
  --chip-fg-on:#fff; --chip-bg-on:#1d1d1f; --chip-bd-on:#1d1d1f;
  --chip-size:.72rem; --chip-tracking:0; --chip-transform:none; --chip-weight:400;
  --con-bg:rgb(251 251 253 / .72); --con-bd:rgb(0 0 0 / .08); --con-fg:#1d1d1f;
  --con-shadow: 0 4px 22px rgb(0 0 0 / .08);
  --con-radius-open: 26px;
  --con-dur:.9s;
  --grain:0;
}
[data-style="minimal"] .soon { text-transform:none; letter-spacing:0; }
[data-style="minimal"] .brand { font-weight:600; }
/* Apple's chrome is frosted, not solid */
[data-style="minimal"] .contacts-bar {
  backdrop-filter:saturate(180%) blur(20px);
  -webkit-backdrop-filter:saturate(180%) blur(20px);
}
/* hover only where a hover truly exists — on a phone a tap leaves :hover stuck,
   and this darkened the active Minimal chip's label to dark-on-dark until you
   tapped elsewhere. The active chip keeps its own on-colour regardless. */
@media (hover:hover) {
  [data-style="minimal"] .contact:hover { background:#f5f5f7; color:#1d1d1f; transform:none; }
  [data-style="minimal"] .switch-row button:hover:not([aria-checked="true"]) { transform:none; color:#1d1d1f; }
}
@media (prefers-reduced-transparency: reduce) {
  [data-style="minimal"] .contacts-bar { background:#fbfbfd; backdrop-filter:none; -webkit-backdrop-filter:none; }
}

/* ============================================================
   TRANSITION between languages
   ============================================================ */
::view-transition-old(root) { animation:vt-out .45s var(--ease) both; }
::view-transition-new(root) { animation:vt-in  .7s  var(--ease) both; }
@keyframes vt-out { to   { opacity:0; filter:blur(9px);  transform:scale(.97); } }
@keyframes vt-in  { from { opacity:0; filter:blur(14px); transform:scale(1.035); } }

/* entry / re-entry of the composition — .soon is excluded, it waits for the typer */
.scene, .hello { animation:rise .95s var(--ease) both; }
.scene  { animation-delay:.04s; }
.hello  { animation-delay:.34s; }
@keyframes rise { from { opacity:0; transform:translateY(18px); } to { opacity:1; transform:none; } }

/* ============================================================
   MOTION OFF — everything collapses to static, nothing is lost
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration:.001ms !important; animation-iteration-count:1 !important;
    transition-duration:.001ms !important;
  }
  .caret { opacity:1; }
  .soon { opacity:1; }
  .contact { opacity:1; transform:none; }
  .ch { opacity:1; clip-path:none; }
  ::view-transition-old(root), ::view-transition-new(root) { animation:none; }
}

/* ============================================================
   RESPONSIVE
   ============================================================ */
@media (max-width:640px) {
  .stage { padding-bottom:10rem; gap:1.15rem; }
  .switch { align-items:stretch; }
  .switch-hint { text-align:center; }
  :root { --dot:44px; --net-dot:38px; }

  /* Nine icons will not sit in one row on a phone without becoming tap-hostile,
     so the bar opens downward into two instead. Height is an explicit number,
     not `auto`, because `auto` will not animate. */
  .contacts-bar { align-items:flex-start; }
  .contacts.is-open .contacts-bar {
    /* +30px, not +18: the icons' own filters (pixel's blocks, hand's wobble,
       plush's specular bulge, three/cyber's drop-shadows) bleed a few px past
       their box on every side. 18px of slack was tight enough that Safari's
       edge-rounding clipped that bleed on the top and bottom rows — the
       "contenaires obrізаються" bug. */
    height:calc(var(--net-dot) * 2 + 30px);
    border-radius:var(--con-radius-open);
    transition:width var(--con-dur) var(--con-ease),
               height var(--con-dur) var(--con-ease),
               border-radius var(--con-dur) var(--con-ease),
               background-color .6s var(--ease), border-color .6s var(--ease);
  }
  /* A wrapping flex row fills greedily and leaves 7 on top and 2 marooned below.
     A grid splits them evenly instead. */
  .contacts-list {
    display:grid; grid-template-columns:repeat(5, 1fr);
    justify-items:center; align-content:center;
    align-self:stretch; row-gap:6px; padding:9px .3rem 9px 0;
  }
  /* the grid wraps, so it never needs to scroll */
  .contacts-list.is-tight { overflow-x:visible; }
}

@media (min-width:1024px) {
  .switch-row { justify-content:center; }
}
