/* =========================================================
   Motion: the Skull's world.

   The mascot is a rig (src/components/skull.mjs) and this file
   is how it moves. /js/skull.js adds the parts that need to know
   where the pointer is; everything else is CSS.

   The language:
   - Physical. Things hop, squash, overshoot and settle, the way
     something with weight would. Nothing just fades.
   - Quick. Most of it is over before you would think to wait.
   - Earned. Every movement is feedback, attention or character.
     Ambient motion is kept to the Skull blinking and the odd
     small moment, and it pauses when it is off screen.
   - Never a loop inside a drawing. Chrome cannot composite an
     animation on an SVG's children, so a looping blink or bob
     repaints the whole drawing every frame. Blinks are a class
     (skull.js); idle drift is on HTML wrappers (play.css).

   Timing, loosely:
     --t-micro      120-180ms   presses, face changes
     --t-ui         240-300ms   hover lifts, arrows
     --t-character  450-700ms   hops, leans, reactions
     --t-scene      800-1200ms  entrances, a book opening
     ambient        seconds     blinks, bobbing, drifting shapes

   Everything that moves sits inside the no-preference query.
   What stays outside is state: which face is showing, whether
   the book is open. Under reduced motion the Skull still reacts,
   it just does not move to do it.
   ========================================================= */

:root {
  --t-micro: 150ms;
  --t-ui: 260ms;
  --t-character: 560ms;
  --t-scene: 950ms;
  /* A spring: overshoots, then settles. */
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
  /* A snappier one for small parts. */
  --snap: cubic-bezier(0.2, 1.8, 0.45, 1);
  --settle: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* ═══════════════════════════ THE RIG ═══════════════════════════ */

.skull__svg {
  display: block;
  width: 100%;
  height: auto;
  overflow: visible;
}

.sk-char,
.sk-lean {
  transform-box: fill-box;
  transform-origin: 50% 92%;
}

.sk-eye,
.sk-prop,
.sk-prop * {
  transform-box: fill-box;
  transform-origin: center;
}

/* ---------- Faces ----------
   One face shows at a time. The base, unless the card around the
   Skull is hovered (its hover face) or the script has given it a
   mood (an alternate face). State, not motion: always on. */

.sk-face--alt,
.sk-face--hover,
.sk.has-mood .sk-face--base,
.sk.has-mood .sk-face--hover {
  display: none;
}

.sk.has-mood .sk-face--alt.is-on {
  display: inline;
}

:is(.capability, .service-card, .problem, .skull, .page-head__art):is(:hover, :focus-within)
  .sk--hover:not(.has-mood)
  .sk-face--base {
  display: none;
}

:is(.capability, .service-card, .problem, .skull, .page-head__art):is(:hover, :focus-within)
  .sk--hover:not(.has-mood)
  .sk-face--hover {
  display: inline;
}

/* Motion marks exist only while their motion plays. */
.sk-motion {
  opacity: 0;
}

/* A book that opens: shut until its card is engaged. */
.sk-prop--textbook .sk-pages {
  opacity: 0;
  transform: scaleX(0.2);
}

.sk-prop--textbook .sk-cover {
  transform-origin: 0% 50%;
}

:is(.skull):is(:hover, :focus-within) .sk-prop--textbook .sk-pages {
  opacity: 1;
  transform: none;
}

:is(.skull):is(:hover, :focus-within) .sk-prop--textbook .sk-cover {
  opacity: 0;
  transform: scaleX(0);
}

/* The bulb lights when the idea lands. */
:is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--bulb .sk-glass {
  fill: #f7df8a;
}

/* The network comes up. */
:is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--nodes .sk-node {
  fill: var(--sol);
}

.skull__svg[data-skull-poke] {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* ═══════════════════════════ MOVEMENT ═══════════════════════════ */

@media (prefers-reduced-motion: no-preference) {
  /* Ambient work pauses off screen. Without the script, .is-inview
     never arrives and the animations simply run. */
  .sk-js .sk:not(.is-inview),
  .sk-js .sk:not(.is-inview) * {
    animation-play-state: paused !important;
  }

  /* ---------- Idle ----------
     Blinking is skull.js's job (.is-blinking in play.css). */

  .sk-eye {
    transition: transform 70ms linear;
  }

  .sk-look {
    transform: translate(calc(var(--lx, 0) * 1px), calc(var(--ly, 0) * 1px));
    transition: transform var(--t-ui) var(--settle);
  }

  .sk-lean {
    transform: rotate(calc(var(--lean, 0) * 1deg));
    transition: transform var(--t-character) var(--spring);
  }

  /* A face change lands with a small squash, so it reads as the
     Skull reacting rather than the drawing being swapped. */
  .sk.has-mood .sk-face--alt.is-on,
  :is(.capability, .service-card, .problem, .skull, .page-head__art):is(:hover, :focus-within)
    .sk--hover:not(.has-mood)
    .sk-face--hover {
    animation: sk-face-in var(--t-micro) var(--snap);
    transform-box: fill-box;
    transform-origin: center;
  }

  /* ---------- Reactions (classes from /js/skull.js) ---------- */

  .sk.is-hop .sk-char {
    animation: sk-hop var(--t-character) var(--settle);
  }

  .sk.is-hop .sk-motion--hop {
    animation: sk-mark-flash var(--t-character) ease-out;
  }

  .sk.is-shake .sk-char {
    animation: sk-shake 0.5s var(--settle);
  }

  .sk.is-wave .sk-char {
    animation: sk-wave 1.1s var(--settle);
  }

  .sk.is-wave .sk-motion--wave {
    animation: sk-mark-flash 1.1s ease-out;
  }

  .sk.is-nod .sk-char {
    animation: sk-nod 0.5s var(--settle);
  }

  .sk.is-glance .sk-glance {
    animation: sk-glance 2.4s var(--settle);
  }

  .sk.is-pop .sk-char {
    animation: sk-pop var(--t-character) var(--spring) both;
  }

  /* ---------- Cards: the Skull does its job on hover ---------- */

  :is(.capability, .service-card, .problem, .split__skull, .page-head__art):hover .sk-char {
    animation: sk-perk var(--t-character) var(--spring);
  }

  :is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--wrench {
    animation: sk-tighten 0.9s var(--settle);
    transform-origin: 20% 80%;
  }

  :is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--laptop .sk-logo {
    animation: sk-bounce 0.5s var(--snap) 0.1s;
  }

  :is(.capability, .service-card):is(:hover, :focus-within) .skull--cap-software .sk-char {
    animation: sk-typing 0.32s steps(2) 4;
  }

  :is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--lock .sk-shackle {
    animation: sk-unlock 0.9s var(--settle);
    transform-origin: 50% 100%;
  }

  :is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--bulb .sk-rays {
    animation: sk-rays 0.6s var(--snap);
  }

  :is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--nodes .sk-node {
    animation: sk-node 0.45s var(--snap) both;
  }

  :is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--nodes .sk-node--2 { animation-delay: 0.12s; }
  :is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--nodes .sk-node--3 { animation-delay: 0.24s; }
  :is(.capability, .service-card, .skull):is(:hover, :focus-within) .sk-prop--nodes .sk-node--4 { animation-delay: 0.36s; }

  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--magnifier {
    animation: sk-inspect 1.3s var(--settle);
  }

  .sk-prop--textbook .sk-pages,
  .sk-prop--textbook .sk-cover {
    transition:
      transform 0.45s var(--spring),
      opacity 0.2s linear;
  }

  :is(.skull):is(:hover, :focus-within) .sk-prop--textbook .sk-pages {
    transition-delay: 0.12s;
  }

  .sk-prop--bulb .sk-glass,
  .sk-prop--nodes .sk-node {
    transition: fill var(--t-ui) var(--settle);
  }

  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--terminalError,
  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--warning .sk-bang {
    animation: sk-shake 0.45s var(--settle);
  }

  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--server .sk-led,
  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--circuit .sk-led {
    animation: sk-led 0.3s steps(1) 4;
  }

  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--gear {
    animation: sk-spin 1.1s var(--settle);
  }

  :is(.service-card, .skull):is(:hover, :focus-within) :is(.sk-prop--check, .sk-prop--board) .sk-tick {
    stroke-dasharray: 40;
    animation: sk-draw 0.6s var(--settle) both;
  }

  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--bug .sk-legs {
    animation: sk-scuttle 0.16s steps(1) 5;
  }

  /* The coffee is hot: once on screen its steam rises in five staggered puffs,
     then the wisps fade back in and rest. Five, not forever: a loop inside an
     SVG repaints the whole drawing every frame. The rest animation is listed
     first so the puffs win while both apply. */
  .sk-prop--coffee .sk-steam,
  .sk-prop--pizza .sk-steam {
    transform-origin: 50% 100%;
    animation:
      sk-steam-rest 0.6s ease-out calc(var(--steam-delay, 0s) + 12s) both,
      sk-steam 2.4s ease-out var(--steam-delay, 0s) 5;
  }

  /* Hovering the card after it rests toggles .sk-steam-again (skull.js). A
     new animation name is what restarts a CSS animation, so the replay runs
     the same keyframes under a second name. */
  .sk-prop--coffee.sk-steam-again .sk-steam,
  .sk-prop--pizza.sk-steam-again .sk-steam {
    animation-name: sk-steam-rest-again, sk-steam-again;
  }

  .sk-prop--coffee .sk-steam--2,
  .sk-prop--pizza .sk-steam--2 { --steam-delay: 0.8s; }
  .sk-prop--coffee .sk-steam--3 { --steam-delay: 1.6s; }

  /* The sprig in the vase sways twice, as if the vase were just set down,
     then rests; hovering the card sways it again. */
  .sk-prop--vase .sk-sway {
    transform-box: fill-box;
    transform-origin: 50% 100%;
    animation: sk-sway 1.6s ease-in-out 0.5s 2;
  }

  .sk-prop--vase.sk-steam-again .sk-sway {
    animation-name: sk-sway-again;
  }

  /* The room key swings from its ring twice, as if just handed over, then
     hangs still; hovering the card swings it again. */
  .sk-prop--roomkey .sk-sway {
    transform-box: fill-box;
    transform-origin: 50% 0;
    animation: sk-sway 1.6s ease-in-out 0.5s 2;
  }

  .sk-prop--roomkey.sk-steam-again .sk-sway {
    animation-name: sk-sway-again;
  }

  /* The camera's flash bursts twice, then rests unlit; hovering the card
     takes the shot again. */
  .sk-prop--camera .sk-flash {
    transform-box: fill-box;
    transform-origin: 100% 100%;
    animation: sk-flash 0.9s ease-out 0.6s 2 both;
  }

  .sk-prop--camera.sk-steam-again .sk-flash {
    animation-name: sk-flash-again;
  }

  /* The slice's cheese stretches down twice as it is lifted, then holds. */
  .sk-prop--pizza .sk-drip {
    transform-box: fill-box;
    transform-origin: 50% 0;
    animation: sk-drip 1.8s ease-in-out 0.4s 2;
  }

  .sk-prop--pizza.sk-steam-again .sk-drip {
    animation-name: sk-drip-again;
  }

  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--phone {
    animation: sk-buzz 0.4s steps(4) 2;
  }

  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--target .sk-crosshair {
    animation: sk-spin 0.9s var(--spring);
  }

  :is(.service-card, .skull):is(:hover, :focus-within) .sk-prop--packets .sk-signal {
    stroke-dasharray: 70;
    animation: sk-draw 0.8s linear both;
  }

  /* ---------- Interface ---------- */

  .btn .icon--external {
    transition: transform var(--t-ui) var(--spring);
  }

  .btn:hover .icon--external,
  .more:hover .icon--external {
    transform: translate(2px, -2px);
  }

  .ask__button {
    transition: box-shadow var(--t-micro) var(--settle), transform var(--t-micro) var(--settle);
  }

  .ask__button:active {
    transform: translate(2px, 2px);
    box-shadow: none;
  }

  /* ---------- The brand mark ----------
     The header's Skull is the rig at 30px (the "brand" role). Each part of
     it moves on its own element so nothing restarts anything else:
     the entrance and the hover wiggle on the svg, the breath on .sk-lean
     (as `translate`, so it composes with the lean's transform), and the
     reactions from skull.js on .sk-char, where motion.css already has them. */
  .site-header .sk {
    transform-origin: 50% 90%;
    animation: sk-enter var(--dur-character) var(--ease-snap) both;
  }

  /* Two slots: the entrance keeps its place in the list, so hovering adds
     the wiggle without replaying the arrival when the pointer leaves. */
  .site-header a.brand:hover .sk {
    animation:
      sk-enter var(--dur-character) var(--ease-snap) both,
      sk-wiggle 0.6s var(--settle);
  }

  .enquiry__link {
    transition: transform var(--t-micro) var(--settle);
  }

  .enquiry__link:active {
    transform: translateY(2px);
  }

  /* 404: looking for the page, left, right, and under the desk. Once,
     on arrival; poke it to look again. */
  .not-found__skull .sk.is-inview .sk-glance {
    animation: sk-search 5.5s var(--settle) 1;
  }
}

/* ═══════════════════════════ KEYFRAMES ═══════════════════════════ */

@keyframes sk-face-in {
  0% { transform: scale(0.86, 1.08); }
  100% { transform: none; }
}

@keyframes sk-pop {
  0% { opacity: 0; transform: translateY(24%) scale(0.6, 0.4); }
  45% { opacity: 1; transform: translateY(-6%) scale(0.94, 1.1); }
  70% { transform: translateY(1%) scale(1.06, 0.94); }
  100% { opacity: 1; transform: none; }
}

@keyframes sk-hop {
  0% { transform: none; }
  18% { transform: scale(1.1, 0.86); }
  45% { transform: translateY(-16%) scale(0.94, 1.08); }
  72% { transform: translateY(0) scale(1.08, 0.9); }
  88% { transform: scale(0.98, 1.03); }
  100% { transform: none; }
}

@keyframes sk-perk {
  0% { transform: none; }
  35% { transform: translateY(-5%) scale(0.97, 1.05); }
  70% { transform: scale(1.03, 0.97); }
  100% { transform: none; }
}

@keyframes sk-shake {
  0%, 100% { transform: none; }
  20% { transform: translateX(-4%) rotate(-5deg); }
  40% { transform: translateX(4%) rotate(4deg); }
  60% { transform: translateX(-3%) rotate(-3deg); }
  80% { transform: translateX(2%) rotate(2deg); }
}

@keyframes sk-wave {
  0%, 100% { transform: none; }
  15% { transform: rotate(9deg); }
  35% { transform: rotate(-6deg); }
  55% { transform: rotate(8deg); }
  75% { transform: rotate(-3deg); }
}

@keyframes sk-nod {
  0%, 100% { transform: none; }
  40% { transform: translateY(4%) scale(1.03, 0.96); }
}

@keyframes sk-glance {
  0%, 100% { transform: none; }
  20%, 42% { transform: translateX(-4px); }
  55%, 80% { transform: translate(4px, -1px); }
}

@keyframes sk-search {
  0%, 12%, 100% { transform: none; }
  22%, 36% { transform: translateX(-5px); }
  48%, 62% { transform: translate(5px, 1px); }
  74%, 86% { transform: translate(0, 3px); }
}

@keyframes sk-mark-flash {
  0% { opacity: 0; transform: translateX(-3px); }
  25%, 70% { opacity: 1; transform: none; }
  100% { opacity: 0; }
}

@keyframes sk-typing {
  50% { transform: translateY(2%); }
}

@keyframes sk-tighten {
  0%, 100% { transform: none; }
  25% { transform: rotate(-24deg); }
  50% { transform: rotate(10deg); }
  75% { transform: rotate(-12deg); }
}

@keyframes sk-bounce {
  40% { transform: translateY(-3px) scale(1.15); }
}

@keyframes sk-unlock {
  0%, 100% { transform: none; }
  30% { transform: translateY(-3px); }
  45% { transform: translateY(-3px) rotate(-8deg); }
  60% { transform: translateY(-1px) rotate(6deg); }
  80% { transform: translateY(0); }
}

@keyframes sk-rays {
  0% { opacity: 0; transform: scale(0.5); }
  60% { opacity: 1; transform: scale(1.2); }
  100% { opacity: 1; transform: none; }
}

@keyframes sk-node {
  0% { transform: scale(0.6); }
  60% { transform: scale(1.3); }
  100% { transform: none; }
}

@keyframes sk-inspect {
  0%, 100% { transform: none; }
  30% { transform: translate(-58px, -46px) scale(1.15); }
  55% { transform: translate(-30px, -48px) scale(1.15); }
  75% { transform: translate(-44px, -40px) scale(1.1); }
}

@keyframes sk-steam {
  0% { opacity: 0; transform: translateY(4px) scaleY(0.6); }
  30% { opacity: 0.6; }
  100% { opacity: 0; transform: translate(1px, -8px) scaleY(1.15); }
}

@keyframes sk-steam-again {
  0% { opacity: 0; transform: translateY(4px) scaleY(0.6); }
  30% { opacity: 0.6; }
  100% { opacity: 0; transform: translate(1px, -8px) scaleY(1.15); }
}

@keyframes sk-sway {
  25% { transform: rotate(-9deg); }
  75% { transform: rotate(7deg); }
}

@keyframes sk-sway-again {
  25% { transform: rotate(-9deg); }
  75% { transform: rotate(7deg); }
}

@keyframes sk-flash {
  0% { opacity: 0; transform: scale(0.6); }
  25% { opacity: 1; transform: scale(1.1); }
  100% { opacity: 0; transform: scale(1); }
}

@keyframes sk-flash-again {
  0% { opacity: 0; transform: scale(0.6); }
  25% { opacity: 1; transform: scale(1.1); }
  100% { opacity: 0; transform: scale(1); }
}

@keyframes sk-drip {
  50% { transform: scaleY(1.35); }
}

@keyframes sk-drip-again {
  50% { transform: scaleY(1.35); }
}

@keyframes sk-steam-rest {
  from { opacity: 0; }
  to { opacity: 0.55; }
}

@keyframes sk-steam-rest-again {
  from { opacity: 0; }
  to { opacity: 0.55; }
}

@keyframes sk-led {
  50% { opacity: 0.2; }
}

@keyframes sk-spin {
  to { transform: rotate(180deg); }
}

@keyframes sk-draw {
  from { stroke-dashoffset: 40; }
  to { stroke-dashoffset: 0; }
}

@keyframes sk-scuttle {
  50% { transform: scaleY(0.8); }
}

@keyframes sk-buzz {
  25% { transform: translateX(-1.5px) rotate(-3deg); }
  75% { transform: translateX(1.5px) rotate(3deg); }
}

@keyframes sk-enter {
  from { opacity: 0; transform: translateY(18px) scale(0.7); }
}

/* Breathing runs on a different period from the blink, so the two drift in
   and out of phase and the idle Skull never reads as a metronome. */
/* `rotate`, not `transform`, so it layers over the entrance's fill. */
@keyframes sk-wiggle {
  0%, 100% { rotate: 0deg; }
  30% { rotate: -5deg; }
  65% { rotate: 4deg; }
}

/* ═══════════════════════════ WORLD LAYOUT ═══════════════════════════
   Placement for the pieces motion drives. Not motion itself, so
   outside the query: with motion off they are still where they
   belong, just still. */

.relative {
  position: relative;
}

/* ═══════════════════════════ THE ESTIMATE ═══════════════════════════

   The builder's only motion is confirmation: the number you just changed
   arrives rather than appearing, and an option you press gives a little under
   the finger. The rows are rebuilt on every change, so the entrance replays
   itself without anything having to toggle a class.

   Nothing here delays reading a price: the longest of it is under 300ms, and
   the figures are legible from the first frame. Outside the no-preference
   query there is no movement at all — the estimate simply updates. */

@media (prefers-reduced-motion: no-preference) {
  .estimate__row {
    animation: estimate-row var(--t-ui) var(--settle) both;
  }

  .estimate__row--total {
    animation: estimate-total var(--t-character) var(--spring) both;
  }

  .estimate__item {
    animation: estimate-row var(--t-micro) var(--settle) both;
  }

  .option__label {
    transition:
      border-color var(--t-micro) var(--settle),
      background-color var(--t-micro) var(--settle),
      transform var(--t-micro) var(--snap);
  }

  .option__label:active {
    transform: scale(0.99);
  }

  .option__input:checked + .option__label::before {
    animation: estimate-tick var(--t-micro) var(--snap);
  }

  .builder__bar {
    animation: estimate-bar var(--t-character) var(--spring) both;
  }
}

@keyframes estimate-row {
  from {
    opacity: 0;
    transform: translateY(3px);
  }
}

@keyframes estimate-total {
  from {
    opacity: 0;
    transform: translateY(6px) scale(0.98);
  }
}

@keyframes estimate-tick {
  from {
    transform: scale(0.7);
  }
}

@keyframes estimate-bar {
  from {
    opacity: 0;
    transform: translateY(12px);
  }
}
