/* Soutik Gangoli portfolio. All sizes are design pixels: the page is designed
   900px tall and #stage scales the whole thing to the window (see script.js). */

@keyframes walk { 100% { background-position: -352px 0; } }
@keyframes luna-walk { 100% { background-position: -864px 0; } }    /* 12 frames: walk, walk, walk with a tail wag */
@keyframes luna-wag { 100% { background-position: -288px 0; } }
@keyframes ball-spin { 100% { background-position: -128px 0; } }
@keyframes splash { 100% { background-position: -700px 0; } }
@keyframes ellipsis { to { width: 1.25em; } }

/* The pixel font is hosted here (fonts/), so the first screen can preload it. It is Pixelify Sans with twelve
   hard-to-read glyphs redrawn (C G E F B H T Z 2 5 7 e): art/patch_font.py. Sharp at multiples of 10px. */
@font-face {
  font-family: "Pixelify Sans";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url(fonts/pixelify-sans-400.woff2) format("woff2");
}

* { box-sizing: border-box; }
.no-js { position: relative; z-index: 20000; background: #0f0f0f; color: #fdfdfd; padding: 20px 24px; font-size: 18px; line-height: 1.5; }
.no-js a { color: #29BA74; text-decoration: underline; }
/* a big background, loaded as strips (art/slices.py): grass below, everything else laid exactly over it */
.bg-strip { position: absolute; left: var(--cx); width: 2400px; max-width: none; height: auto; display: block; pointer-events: none; }

html, h1, h2, h3, h4 { margin: 0; }
/* no ligatures anywhere: the pixel font's fi ligature made "figuring" read "Aguring" */
html { font-variant-ligatures: none; font-feature-settings: "liga" 0, "clig" 0, "dlig" 0, "calt" 0; }
button, input { font-variant-ligatures: none; font-feature-settings: inherit; }

/* Pulling the page past its top (or bottom) must never show black: the page
   does not rubber-band, and what lies behind it is the sky of the first screen. */
html { background: #6eebff; overscroll-behavior: none; }
/* the stop at Work: while it lasts nothing can scroll the page (script.js, holdPage). */
html.held, html.held body { overflow: hidden !important; }
/* No scrollbar at all ("Remove the scrollbar completely"). The page still scrolls by wheel, trackpad, touch, keys and
   the header links. Before this it was the browser's white strip down the right on a Mac with a mouse or on Windows,
   then a dark thin one, which showed sky blue during the stop at Work. With none, there is no gutter to keep either. */
html { scrollbar-width: none; }
html::-webkit-scrollbar { display: none; width: 0; height: 0; }
body {
  background: #6eebff;
  overscroll-behavior: none;
  font-family: "Source Sans 3", "Source Sans Pro", sans-serif;
  overflow-x: hidden;
  margin: 0;
}

a { text-decoration: none; }

/* Everything is pixel art: never smooth it. */
#stage, #stage * { image-rendering: pixelated; }
#stage {
  zoom: var(--stage-scale, 1);
  /* Where the 2400px-wide walk canvas sits. script.js replaces these with values
     snapped to whole device pixels, so the art never lands on a half pixel. */
  --cx: calc(50% - 1200px);        /* left edge of the canvas inside #container */
  --bgx: 50%;                      /* the same thing, as a background-position */
  --centre: 50%;                   /* the road's centre line, across the window */
  --cover-n: 3;                    /* whole-number scale of the 160 x 100 covers */
}

.anchor { padding-top: 48px; }

#container {
  max-width: 2400px;
  margin: auto;
  background-color: #090909;
  position: relative;
  overflow: hidden;
  overflow: clip;                  /* clip, not hidden: a hidden box can still be scrolled by the browser (a focus, a find, scrollIntoView), which slid the whole page up inside it and left a blank stretch after the ending */
}

h2 {
  font-size: 70px;
  font-weight: 700;
  color: #fdfdfd;
  text-align: center;
}
html.phone h2 { font-size: 55px; }

/* ---------------------------------------------------------------- preloader */
.preloader {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 10001;
  background: white;
  transition: opacity 0.2s;
}
.preloader.done { opacity: 0; pointer-events: none; }
.preloader[hidden] { display: none; }      /* disconnected: see index.html */
/* Soutik running on the spot, side on (art/make_runner.py): six frames of 28 x 37 art pixels at 4x */
@keyframes run-side { 100% { background-position: -672px 0; } }
.preloader-run {
  position: fixed;
  left: 50%;
  top: 50%;
  width: 112px;
  height: 148px;
  margin: -150px 0 0 -56px;
  background: url(img/soutik-run-side.png) 0 0 / 672px 148px no-repeat;
  image-rendering: pixelated;
  animation: run-side 0.54s steps(6) infinite;
}
/* speed lines behind his legs: three short dashes that streak back and flick, in whole 4px steps like his pixels */
@keyframes run-lines { 0% { translate: 0 0; opacity: 1; } 50% { translate: -8px 0; opacity: 1; } 75% { translate: -16px 0; opacity: 0.5; } 100% { translate: -20px 0; opacity: 0; } }
.preloader-run::before {
  content: "";
  position: absolute;
  left: -8px;
  top: 96px;
  width: 20px;
  height: 4px;
  background: #0f0f0f;
  box-shadow: -12px 16px 0 #0f0f0f, 4px 32px 0 #0f0f0f;      /* the second and third dash, lower and staggered */
  animation: run-lines 0.36s steps(4) infinite;
}
@media (prefers-reduced-motion: reduce) { .preloader-run, .preloader-run::before { animation: none; } }
.preloader .loading {
  position: fixed;
  left: 0;
  width: 100%;
  top: 50%;
  text-align: center;
  font-family: "Pixelify Sans", monospace;
  font-size: 26px;
  color: #0f0f0f;
}

/* ---------------------------------------------------------------- header */
#header {
  position: fixed;
  z-index: 10000;
  left: 0;
  top: 0;
  width: 100%;
  background: rgba(0, 0, 0, 0.8);
  height: 48px;
  line-height: 48px;
  color: #fdfdfd;
  font-size: 16px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.4s;
}
#header.show { opacity: 1; pointer-events: auto; }


/* the mute button: a pixel speaker at the left of the header. It mutes the jet, the T-Rex and the records. */
#mute { position: absolute; left: 4px; top: 8px; width: 32px; height: 32px; padding: 8px; border: 0; background: none; color: #fdfdfd; opacity: 0.45; cursor: pointer; transition: opacity 0.15s; }
#mute:hover, #mute:focus-visible, #mute.off { opacity: 0.9; }
#mute svg { display: block; width: 16px; height: 16px; }
#mute .cross, #mute.off .waves { display: none; }
#mute.off .cross { display: block; color: #e2452b; }
#mute:focus-visible { outline: 3px solid #fdfdfd; outline-offset: -3px; }

#nav { position: absolute; right: 8px; top: 0; height: 48px; line-height: 48px; }
#nav ul { margin: 0; padding: 0; list-style: none; }
#nav ul li { display: inline-block; margin-left: 8px; font-size: 14.4px; position: relative; bottom: 1px; }
#nav ul li a { display: block; color: inherit; height: 48px; line-height: 48px; padding: 0 8px; outline: 0; }
#nav ul li a:focus-visible { outline: 2px solid #fdfdfd; outline-offset: -4px; }

#menu-icon { display: none; margin-top: 4px; }


/* Narrow screens. html.phone and html.tablet are set by script.js from the
   DESIGN width (the stage is zoomed, so media queries would measure the wrong thing). */
html.phone #menu-icon { display: inline-block; position: absolute; right: 0; }
html.phone #nav { width: 100%; }
html.phone #nav ul { display: none; position: absolute; background: rgba(0, 0, 0, 0.8); right: -8px; width: 100%; top: 48px; }
html.phone #nav ul li { text-align: center; width: 100%; margin: 0; }
html.phone #nav ul li:hover { background: rgba(0, 0, 0, 0.9); }
html.phone #nav.open ul { display: block; }

/* ---------------------------------------------------------------- intro walk */
#section-intro {
  position: relative;
  height: 2891px;
  background: #4a7c27;
}

/* Soutik and Luna. The group is fixed at the road's centre line and the PAIR is
   centred on it: he stands just left of the line, Luna just right, 150px in all.
   Script shifts the group sideways (whole pixels) round the roundabout. */
#walk-group { position: fixed; left: var(--centre); top: 0; width: 0; height: 0; z-index: 2; }
#walk-group[hidden] { display: none; }
/* standing on the stop mark with more page still to come (phones, tablets): part of the page, scrolled by the browser (script.js sets its top) */
#walk-group.attached { position: absolute; left: calc(var(--cx) + 1200px); }
#walker {
  position: absolute;
  left: -75px;
  top: 300px;
  width: 88px;
  height: 144px;
  background: url(img/soutik-walk-forward.png) 0 0 no-repeat;
}
#walker-shadow {
  position: absolute;
  left: -61px;
  top: 426px;
  width: 60px;
  height: 24px;
  background-color: rgba(0, 0, 0, 0.3);
  border-radius: 50%;
  box-shadow: 0 0 10px 7px rgba(0, 0, 0, 0.3);
}
#luna {
  position: absolute;
  left: 3px;                       /* always on his left, the same gap everywhere */
  top: 366px;
  width: 72px;
  height: 76px;
  background: url(img/luna-stand-forward-wag.png) 0 0 no-repeat;
  animation: luna-wag 1.8s steps(4) infinite;      /* standing still: a slow, easy wag */
}
#walk-group.back #walker { background-image: url(img/soutik-walk-backward.png); }
/* the ball is in play, or she has just brought it back: the tail goes fast (script.js, cheer) */
#walk-group.happy:not(.walking):not(.arrived) #luna { animation-duration: 0.28s; }
/* Her tongue, standing: a lick, once for every two full wags of the tail (3.6s). It is its own little piece laid over
   the sprite, so it is not tied to the slow wag frames. It comes down ONE art pixel at most (4px, where the drawn
   tongue-out always reached; two pixels hung too far down), by way of a half step on the way out and on the way back:
   about 0.45s in all. A plain on/off flick of 0.16s looked too quick and mechanical. */
@keyframes tongue-lick { 0% { height: 2px; } 2.5% { height: 4px; } 10% { height: 2px; } 12.5%, 100% { height: 0; } }
#walk-group:not(.walking):not(.back):not(.arrived):not(.luna-away) #luna::before { content: ""; position: absolute; left: 32px; top: 40px; width: 8px; height: 0; background: #ff839a; animation: tongue-lick 3.6s step-end infinite; }
/* no licking while the ball is in play (held, thrown, fetched, and the excited seconds after) or while she is jumping for it */
#walk-group.happy #luna::before, #walk-group #luna.hop::before { display: none; }
@media (prefers-reduced-motion: reduce) { #luna::before { animation: none !important; } }
#walk-group.back #luna { background-image: url(img/luna-stand-backward-wag.png); }
#walk-group.walking #luna { background-image: url(img/luna-walk-forward-3.png); }
#walk-group.walking.back #luna { background-image: url(img/luna-walk-backward-3.png); }
/* while they walk, script.js sets the frame from how far the road has moved: no timed animation */
#walk-group.walking #walker { animation: none; }
/* three of his cycles: two plain, then one where her tail swings */
#walk-group.walking #luna { animation: none; }

/* ---------------------------------------------------------------- speech bubbles */
.bubble {
  background-color: #fdfdfd;
  color: #0f0f0f;
  /* The outline is the reference site's: a 10px border drawn by a nine-slice picture (img/bubble-border.png,
     art/make_bubble.py), a thin 4px black line with stepped pixel corners and white inside it. The same on every screen. */
  border: 10px solid transparent;
  border-image: url(img/bubble-border.png) 10 stretch;
  image-rendering: pixelated;
  background-clip: padding-box;
  font-family: "Pixelify Sans", monospace;
}
/* Typing: the full text is there from the start, in the bubble's own colour.
   Typed letters turn dark one at a time. */
.js .type { color: #fdfdfd; }
.js .type .typed, .js .type.all-typed { color: #0f0f0f; }

/* Clips every intro bubble to the intro section, whatever the scroll speed: a
   fixed child of a clipped box cannot paint outside it. */
#intro-bubbles { position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 7; clip-path: inset(0); pointer-events: none; }

#intro {
  position: fixed;
  z-index: 7;
  top: 56px;
  width: 80%;
  left: 50%;
  margin-left: -40%;
  text-align: center;
  font-size: 40px;
  line-height: 1.25;
  padding: 6px 0 10px;
}
#intro #intro-line-2 { font-size: 120px; font-weight: 400; line-height: 1; }
#intro::before {
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  right: 50%;
  margin-right: -120px;
  top: calc(100% + 6px);            /* its top edge sits on the top of the bubble's black line, so no corner pokes above the base */
  border: 20px solid;
  border-color: #0f0f0f transparent transparent #0f0f0f;
}
#intro::after {
  content: "";
  position: absolute;
  width: 0;
  height: 0;
  right: 50%;
  margin-right: -115px;
  top: calc(100% + 4px);
  border: 15px solid;
  border-color: #fdfdfd transparent transparent #fdfdfd;
}

.speech-container { width: 1200px; position: fixed; left: var(--centre); margin-left: -600px; top: 0; z-index: 7; pointer-events: none; }
.speech {
  position: absolute;
  width: 560px;
  font-size: 30px;                 /* Pixelify Sans is drawn on a grid of a tenth of its size: only multiples of 10 are sharp */
  line-height: 1.3;
  padding: 6px 12px 8px;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.2s, visibility 0.2s;
}
.speech.show { opacity: 1; visibility: visible; }
.speech[hidden] { display: none; }
.speech::before, .speech::after { content: ""; position: absolute; width: 0; height: 0; border-style: solid; }

/* bubble 2 sits beside him, below the jet's flight path, tail on its right */
:is(#about-speech-1, #about-speech-1b) { right: 734px; top: 376px; width: min(560px, calc(var(--half, 800px) - 148px)); }   /* narrower on a small laptop, so it never leaves the screen */
:is(#about-speech-1, #about-speech-1b)::before { right: -46px; top: calc(16px + var(--tail, 0px)); border-width: 20px; border-color: transparent transparent transparent #0f0f0f; }
:is(#about-speech-1, #about-speech-1b)::after { right: -35px; top: calc(21px + var(--tail, 0px)); border-width: 15px; border-color: transparent transparent transparent #fdfdfd; }

/* bubble 3 sits upper right, clear of the buildings, tail on its left */
/* script.js sets --b2-top and --b2-tail from the box's height: a short box hangs with its foot at his head,
   a tall one starts 30px from the top, and either way the tail stays level with his head. */
:is(#about-speech-2, #about-speech-2b) { left: 752px; top: var(--b2-top, 30px); width: min(560px, calc(var(--half, 800px) - 176px)); }
:is(#about-speech-2, #about-speech-2b)::before { left: -46px; top: var(--b2-tail, 286px); border-width: 20px; border-color: transparent #0f0f0f transparent transparent; }
:is(#about-speech-2, #about-speech-2b)::after { left: -35px; top: calc(var(--b2-tail, 286px) + 5px); border-width: 15px; border-color: transparent #fdfdfd transparent transparent; }
/* walking back up they take the island's other side, so the bubble swaps sides too and never covers them */
:is(#about-speech-2, #about-speech-2b).flip { left: auto; right: 752px; }
:is(#about-speech-2, #about-speech-2b).flip::before { left: auto; right: -46px; border-color: transparent transparent transparent #0f0f0f; }
:is(#about-speech-2, #about-speech-2b).flip::after { left: auto; right: -35px; border-color: transparent transparent transparent #fdfdfd; }

html.tablet #intro { width: 92%; margin-left: -46%; }
html.tablet #intro #intro-line-2 { font-size: 100px; }
html.phone #intro { width: 96%; margin-left: -48%; font-size: 30px; }
html.phone #intro #intro-line-2 { font-size: 60px; line-height: 0.95; padding-bottom: 6px; }

/* Phones and tablets have no room beside him: bubbles 2 and 3 sit under the
   pair, tail pointing up at him. Text never drops below 16 real pixels. */
html:is(.phone, .tablet) .speech-container { width: 100%; left: 0; margin-left: 0; }
html:is(.phone, .tablet) :is(:is(#about-speech-1, #about-speech-1b), :is(#about-speech-2, #about-speech-2b), :is(#about-speech-2, #about-speech-2b).flip, #about-speech-3) {
  left: 6px;
  right: 6px;
  top: 468px;
  width: auto;
  font-size: var(--bubble-font, 20px);
  line-height: 1.25;
  padding: 5px 10px 7px;
}
html.tablet :is(:is(#about-speech-1, #about-speech-1b), :is(#about-speech-2, #about-speech-2b), :is(#about-speech-2, #about-speech-2b).flip, #about-speech-3) { left: 50%; right: auto; width: 640px; margin-left: -320px; font-size: var(--bubble-font, 20px); }
html:is(.phone, .tablet) :is(:is(#about-speech-1, #about-speech-1b), :is(#about-speech-2, #about-speech-2b), :is(#about-speech-2, #about-speech-2b).flip, #about-speech-3)::before { left: 50%; right: auto; top: -34px; margin-left: -52px; border-width: 14px; border-color: transparent transparent #0f0f0f transparent; }
html:is(.phone, .tablet) :is(:is(#about-speech-1, #about-speech-1b), :is(#about-speech-2, #about-speech-2b), :is(#about-speech-2, #about-speech-2b).flip, #about-speech-3)::after { left: 50%; right: auto; top: -24px; margin-left: -50px; border-width: 12px; border-color: transparent transparent #fdfdfd transparent; }
.speech { font-size: max(30px, var(--px16, 16px)); }
/* the line break in the second bubble's text is kept, so its last sentence stands on its own line */
:is(#about-speech-2, #about-speech-2b) .type { white-space: pre-line; }
/* Bubble 4: one line, said as they walk under India Gate. To his right at head height on a laptop; under the pair,
   only as wide as its words, on phones and tablets. */
#about-speech-3 { left: 752px; top: 318px; width: max-content; white-space: nowrap; }
#about-speech-3::before { left: -46px; top: 4px; border-width: 20px; border-color: transparent #0f0f0f transparent transparent; }
#about-speech-3::after { left: -35px; top: 9px; border-width: 15px; border-color: transparent #fdfdfd transparent transparent; }
html.phone #about-speech-3.speech.bubble { left: 50%; right: auto; width: max-content; translate: -50% 0; }      /* centred under the road, where they are by then */
html.tablet #about-speech-3.speech.bubble { width: max-content; margin-left: 0; translate: -50% 0; }

/* ---------------------------------------------------------------- Su-30MKI fly-past
   One fixed layer, 118 x 53 art pixels at 3x. No transitions and no animated
   transforms: frames, throttle and position all change instantly, on whole pixels. */
.su30, .su30 * { transition: none !important; animation: none !important; }
.su30 { --j: calc(1px * var(--jet, 3)); position: fixed; left: 0; top: 0; z-index: 5; width: calc(118 * var(--j)); height: calc(53 * var(--j)); pointer-events: none; }
.su30[hidden], .su30 [hidden] { display: none; }
.su30-roll { position: absolute; left: 0; top: 0; width: calc(118 * var(--j)); height: calc(53 * var(--j)); }
.su30 img, .su30-flame { position: absolute; left: 0; top: 0; height: calc(53 * var(--j)); }
.su30 img { width: calc(84 * var(--j)); }
.su30-flame { width: calc(118 * var(--j)); background-repeat: no-repeat; background-size: 200% 100%; background-position: 0 0; }
.su30.flick .su30-flame { background-position: 100% 0; }
.su30-roll.squash img, .su30-roll.squash .su30-flame { top: calc(10 * var(--j)); height: calc(33 * var(--j)); }

/* ---------------------------------------------------------------- scenery on the background */
#fountain {
  position: absolute;
  left: calc(var(--cx) + 1113px);
  top: 2044px;
  width: 175px;
  height: 168px;
  background: url(img/fountain.png) 0 0 no-repeat;
  animation: splash 0.72s steps(4) infinite;
}
.lamp-on { position: absolute; width: 76px; height: 132px; background: url(img/lamp-on.png) 0 0 no-repeat; }
.lamp-on[hidden] { display: none; }

#india-gate { position: absolute; left: calc(var(--cx) + 1005px); top: 2462px; z-index: 4; display: block; }

/* the hedge top, cut from the same background image, drawn over Soutik and Luna */
#hedge {
  position: absolute;
  left: 0;
  top: 2828px;
  width: 100%;
  height: 63px;
  z-index: 6;
  background: url(img/hedge.png?v=20749) var(--bgx) 0 no-repeat;
}

/* ---------------------------------------------------------------- scroll-in animations
   .animation-element gets .in-view from script.js while any part of it is on screen. */
.js .animation-element.slide-left { opacity: 0; transition: all 1s ease; transform: translate3d(-100px, 0, 0); }
.js .animation-element.slide-right { opacity: 0; transition: all 1s ease; transform: translate3d(100px, 0, 0); }
.js .animation-element.bounce-up { opacity: 0; transition: all 700ms ease-out; transform: translate3d(0, 200px, 0); }
.js .animation-element.slide-left.in-view,
.js .animation-element.slide-right.in-view,
.js .animation-element.bounce-up.in-view { opacity: 1; transform: none; }

/* ---------------------------------------------------------------- Work */
#section-work {
  position: relative;
  z-index: 6;
  background: #197A56;
  padding-bottom: 30px;
  min-height: var(--vh, 900px);    /* exactly one screen: script.js sizes the covers so all eight fit in it */
  display: flex;                   /* the grid sits in the middle of what is left under the heading */
  flex-direction: column;
}
#section-work #container-work { margin-top: auto; margin-bottom: auto; }
#section-work h2 { position: relative; top: 0; padding-top: 30px; }
#section-work { overflow: hidden; }

/* Both grids are placed by script.js on whole design pixels (--grid-w, --grid-left),
   so every cover lines up exactly with the pixel-assembly canvas. */
#container-work, #container-decks {
  position: relative;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;         /* a short last row sits centred */
  width: var(--grid-w, auto);
  margin: 0 0 0 var(--grid-left, auto);
  padding: 0;
}
#container-decks { width: var(--deck-w, auto); margin: 60px 0 0 var(--deck-left, auto); --cover-n: var(--deck-n, 3); --cover-gap: var(--deck-gap, 24px); }
.section-heading { height: 130px; }
.section-heading h2 { line-height: 90px; }

/* Break / Rebuild: a pixel switch in the section's top-right corner */
.pixel-toggle {
  position: absolute;
  top: 48px;
  right: 40px;
  z-index: 40;
  display: flex;
  align-items: center;
  gap: 12px;
  background: none;
  border: 0;
  padding: 6px;
  cursor: pointer;
  font-family: "Pixelify Sans", monospace;
  font-size: 24px;
  line-height: 1;
  color: #fdfdfd;
}
.pixel-toggle:focus-visible { outline: 4px solid #ffbc00; outline-offset: 4px; }
.pixel-toggle-track { position: relative; width: 56px; height: 28px; background: #0E4D33; border: 4px solid #0f0f0f; box-shadow: 4px 4px 0 #0f0f0f; }
.pixel-toggle-knob { position: absolute; top: 0; left: 0; width: 20px; height: 20px; background: #ffbc00; box-shadow: inset -4px -4px 0 #c98a12, inset 4px 4px 0 #ffe08a; }
/* "Build" is on the left and is where the switch starts (cards built: aria-checked true); "Build fast", on the right, scatters them */
.pixel-toggle[aria-checked="false"] .pixel-toggle-knob { left: 28px; }
.pixel-toggle-label { opacity: 0.55; }
/* phones: the switch sits under the heading, centred */
html.phone .pixel-toggle { position: relative; top: auto; right: auto; margin: -34px auto 6px; }
html.phone .section-heading { height: 124px; }
/* phones: less air between stacked cards, a lower sunset, a shorter dawn */
html.phone .deck .img { margin-top: 6px; margin-bottom: 6px; }
html.phone #section-work { padding-bottom: 30px; }
html.phone #container-decks { margin-top: 20px; }
html.phone #sunset { height: 270px; }
html.phone #dawn { height: 170px; }
/* Touch: a quick second tap on a control is a click, not a double-tap zoom (a fast Break / Rebuild / Break lost taps to it);
   and a long press on a control never selects text or opens the callout, which cancels the press. */
button, a, .pixel-toggle, .fetch-ball { touch-action: manipulation; }
.fetch-ball { touch-action: none; }
button, .pixel-toggle, .fetch-ball, #heart { -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
/* touch screens: every control is at least 44 real pixels tall */
@media (pointer: coarse) {
  #mute { width: max(44px, var(--px44, 44px)); height: max(44px, var(--px44, 44px)); left: 0; top: max(0px, calc((48px - max(44px, var(--px44, 44px))) / 2)); padding: calc((max(44px, var(--px44, 44px)) - 16px) / 2); }      /* a 44px target; the speaker itself stays 16px */
  .pixel-toggle { min-height: var(--px44, 44px); }
  .pixel-button { min-height: var(--px44, 44px); display: inline-flex; align-items: center; }
  a.close-overlay { min-width: var(--px44, 44px); min-height: var(--px44, 44px); }
  .detail .close-detail { width: max(56px, var(--px44, 44px)); height: max(56px, var(--px44, 44px)); line-height: max(52px, var(--px44, 44px)); font-size: 30px; }
}
.pixel-toggle[aria-checked="false"] .pixel-toggle-label[data-side="break"],
.pixel-toggle[aria-checked="true"] .pixel-toggle-label[data-side="rebuild"] { opacity: 1; }
/* Covers are 160 x 100 art pixels, always shown at a whole-number scale
   (--cover-n, chosen in script.js to fit the window) and never resampled. */
.card, .deck { position: relative; width: calc(160px * var(--cover-n) + var(--cover-gap, 24px)); }
img.cover {
  display: block;
  width: calc(160px * var(--cover-n));
  height: calc(100px * var(--cover-n));
  max-width: none;
  image-rendering: pixelated;
}

/* Cards build from pixels (script.js draws that on a canvas laid over the grid).
   A card stays hidden until its own pixels have locked in. */
.js .card { visibility: hidden; }
.js .card.built { visibility: visible; }
canvas.pixel-build { position: absolute; left: 0; top: 0; z-index: 30; pointer-events: none; image-rendering: pixelated; }
canvas.pixel-build[hidden] { display: none; }

.img { position: relative; overflow: hidden; cursor: pointer; }
/* a dark pixel frame and a hard drop shadow: each card reads larger and sits off the green */
.card .img { box-shadow: 0 0 0 4px #0f0f0f, 10px 10px 0 4px rgba(0, 0, 0, 0.28); }
html.phone .card .img { box-shadow: 0 0 0 3px #0f0f0f, 6px 6px 0 3px rgba(0, 0, 0, 0.28); }
.card .img { margin: var(--card-air, 12px) calc(var(--cover-gap, 24px) / 2); }
.deck .img { margin: 24px calc(var(--cover-gap, 24px) / 2); }
.img img { display: block; padding: 0; max-width: 100%; height: auto; margin: 0 auto; }

.badge {
  position: absolute;
  top: calc(var(--by, 4) * 1px * var(--cover-n, 2));          /* --by and --bx: art pixels from the top and right edges */
  right: calc(var(--bx, 4) * 1px * var(--cover-n, 2));
  z-index: 31;                     /* over the pixel canvas: it pops while the last blocks are still landing */
  font-family: "Pixelify Sans", monospace;
  font-size: max(calc(7.5px * var(--cover-n, 2)), var(--badge-min, 0px));   /* grows and shrinks with the cover, so it always covers the same part of the art */
  line-height: 1;
  color: #0f0f0f;
  background: #ffbc00;
  border: 3px solid #0f0f0f;
  box-shadow: 3px 3px 0 #0f0f0f;
  padding: 4px 7px 5px;
}

/* the reference's "effect-6" hover overlay */
.overlay {
  display: block;
  position: absolute;
  z-index: 20;
  background: rgba(0, 0, 0, 0.7);
  overflow: hidden;
  transition: all 0.5s;
  margin: auto;
}
.effect-6 .overlay { top: 0; bottom: 0; left: 0; right: 0; opacity: 0; }
.effect-6 .overlay a.expand { left: 0; right: 0; top: 0; margin: 0 auto; opacity: 0; transition: all 0.5s; }
.effect-6 .img.hover .overlay, .effect-6 .img:focus-within .overlay { opacity: 1; }
.effect-6 .img.hover .overlay a.expand, .effect-6 .img:focus-within .overlay a.expand { top: 50%; margin-top: -30px; opacity: 1; }

/* A badge shows once its card is all but assembled (script.js adds .tagged, and
   .pop for the two-step pixel pop), and goes when the card breaks. */
@keyframes badge-pop { 0% { transform: scale(0); } 34% { transform: scale(1.1); } 67%, 100% { transform: scale(1); } }
/* the font's $ carries a wide gap after it: pull the amount back against it */
.badge .amount { margin-left: -0.08em; }
.js .card:not(.built):not(.tagged) .badge { visibility: hidden; }
.js .card.tagged .badge { visibility: visible; }
.card.pop .badge { animation: badge-pop 150ms steps(1, end); }

a.expand {
  display: block;
  position: absolute;
  z-index: 100;
  text-align: center;
  color: #fff;
  line-height: 50px;
  font-weight: 300;
  font-size: 20px;
  letter-spacing: 0.5px;
}
h3.expand {
  position: absolute;
  bottom: 44px;
  padding: 0 20px;
  font-size: 15px;
  color: #fff;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 1px;
  line-height: 1.5em;
}
.overlay h4 {
  position: absolute;
  top: calc(100% - 42px);
  left: 20px;
  color: #fff;
  font-size: 12px;
  font-weight: 100;
  line-height: 1.25;
  padding-right: 14px;
}
.deck h3.expand { bottom: 20px; }

a.close-overlay {
  display: block;
  position: absolute;
  top: 0;
  right: 0;
  z-index: 100;
  width: 45px;
  height: 45px;
  font-size: 20px;
  font-weight: 700;
  color: #fff;
  line-height: 45px;
  text-align: center;
  background-color: #000;
  cursor: pointer;
}
a.close-overlay.hidden { display: none; }

/* ---------------------------------------------------------------- full-screen card detail
   .detail is the dimmed backdrop; .detail-box is the card. A click on the backdrop closes it. */
.detail {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 10002;
  overflow: hidden;                /* the backdrop never scrolls; only the box does */
  overscroll-behavior: contain;
  touch-action: none;
  background: rgba(9, 9, 9, 0.6);
  padding: 70px 24px 60px;
  cursor: pointer;
}
.detail[hidden] { display: none; }
.detail-box {
  position: relative;
  max-width: 1080px;               /* a large card */
  /* Never taller than the screen less the backdrop's margins. The card is a column: the name, dates and role
     stay at the top, the buttons stay at the bottom, and only the text between them (.detail-scroll) scrolls. */
  max-height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  margin: 0 auto;
  background: #0f0f0f;                     /* fully opaque: nothing behind the card shows through it */
  border: 4px solid #fdfdfd;
  color: #fdfdfd;
  padding: 56px 60px 52px;
  font-size: 23px;
  line-height: 1.6;
  cursor: auto;
}
.detail-box > * { flex: none; }
.detail-scroll { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y; padding-right: 8px; outline: 0; }
.detail-scroll > :last-child { margin-bottom: 0; }
.detail-scroll:focus-visible { outline: 2px solid #29BA74; outline-offset: 4px; }
/* on a short screen the card gives up some of its own padding and the backdrop some of its margin, so more text shows */
@media (max-height: 820px) {
  .detail { padding-top: 40px; padding-bottom: 28px; }
  .detail-box { padding-top: 40px; padding-bottom: 32px; }
  .detail .detail-sub { margin-bottom: 18px; }
  .sold-stamp { top: 128px; width: 110px; height: auto; }      /* smaller and higher, so it stays clear of the text that has moved up */
}
.detail p { margin: 0 0 16px; }
/* like a CV: the company on the left, the dates on the far right of the same line */
.detail-head { display: flex; align-items: baseline; justify-content: space-between; gap: 6px 24px; flex-wrap: wrap; }
.detail .detail-dates { margin: 0 0 6px; font-size: 24px; font-weight: 300; white-space: nowrap; text-align: right; }
.detail p.tight { margin-bottom: 2px; }
.detail p.tight + .detail-buttons, .detail p.tight:last-child { margin-top: 0; }
/* a company logo in place of its name: Soutik's file, light on the black card */
/* his logo files are dark marks on white, so each sits on a small white plate */
.detail-logo { display: block; height: 76px; width: auto; max-width: 420px; image-rendering: auto; margin: 4px 0 12px; border-radius: 6px; }
.detail-title:has(.detail-logo) { display: flex; align-items: center; gap: 18px; }
.detail-title:has(.detail-logo) span { margin-bottom: 8px; }
html.phone .detail-logo { height: 52px; max-width: 200px; }
/* Ollvy's file is small (48px tall once trimmed): shown no larger than it is, so it stays sharp */
.detail-logo.logo-ollvy, html.phone .detail-logo.logo-ollvy { height: 44px; }
/* these three are cut out and sit straight on the black card: no plate, so no rounded corners */
.detail-logo.logo-bcg, .detail-logo.logo-pwc, .detail-logo.logo-unacademy { border-radius: 0; }
.detail .detail-title { font-size: 68px; font-weight: 700; line-height: 1.1; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 1px; }
.detail .detail-sub { font-weight: 300; font-size: 27px; margin-bottom: 30px; }
.detail .detail-foot { font-family: "Pixelify Sans", monospace; font-size: 20px; color: #29BA74; margin-top: 22px; }
.detail-buttons { display: flex; flex-wrap: wrap; gap: 14px; margin-top: 22px !important; margin-bottom: 0 !important; padding-bottom: 6px; }      /* pinned under the scrolling text; the padding leaves room for the buttons' hard shadow */
.pixel-button {
  font-family: "Pixelify Sans", monospace;
  font-size: 22px;
  line-height: 1;
  color: #0f0f0f;
  background: #fdfdfd;
  border: 4px solid #0f0f0f;
  box-shadow: 4px 4px 0 #29BA74;
  padding: 9px 14px 11px;
}
.pixel-button:hover, .pixel-button:focus-visible { background: #ffbc00; outline: 0; }
.sold-stamp { position: absolute; right: 60px; top: 156px; }      /* under the dates, beside the role and the flow */
.detail:has(.sold-stamp) .detail-sub, .detail:has(.sold-stamp) .flow { padding-right: 170px; }
html.phone .detail:has(.sold-stamp) .detail-sub, html.phone .detail:has(.sold-stamp) .flow { padding-right: 0; }
.flow { list-style: none; margin: 0 0 28px; padding: 0; display: flex; flex-wrap: wrap; gap: 10px 34px; font-family: "Pixelify Sans", monospace; font-size: 20px; line-height: 1; }
.flow li { position: relative; color: #0f0f0f; background: #fdfdfd; border: 4px solid #29BA74; padding: 8px 10px 10px; }
.flow li + li::before {                /* a pixel arrow between the boxes */
  content: "";
  position: absolute;
  left: -30px;
  top: 50%;
  width: 14px;
  height: 4px;
  margin-top: -2px;
  background: #29BA74;
  box-shadow: 4px -4px 0 #29BA74, 4px 4px 0 #29BA74, 8px 0 0 #29BA74;
}
/* phones: the card is the whole screen, with a large close button that stays in the corner */
html.phone .detail { padding: 0; }
html.phone .detail-box { max-width: none; width: 100%; height: 100%; border: 0; padding: 64px 20px 40px; font-size: max(18px, var(--px16, 16px)); }
html.phone .detail .close-detail { position: fixed; top: 0; right: 0; width: 56px; height: 56px; line-height: 52px; font-size: 30px; background: #fdfdfd; color: #0f0f0f; border: 4px solid #0f0f0f; }
html.phone .detail .detail-title { font-size: 40px; }
html.phone .detail .detail-sub { font-size: 20px; }
html.phone .detail .detail-dates { font-size: 17px; }
html.phone .sold-stamp { position: static; display: block; width: 100px; height: auto; margin: 0 0 10px; }
/* phones: the flow boxes stack, with the arrow pointing down between them */
/* phones: the boxes stay in one row, left to right. Smaller text (--flow-font, a sharp size set by script.js),
   tighter boxes, and the longest label is allowed to break onto two lines. */
html.phone .flow { flex-wrap: nowrap; align-items: stretch; gap: 0 16px; font-size: var(--flow-font, 13px); }
html.phone .flow li { display: flex; align-items: center; text-align: center; border-width: 3px; padding: 5px 5px 6px; }
html.phone .flow li + li::before { left: -14px; scale: 0.6; transform-origin: 0 50%; }
html.detail-open { overflow: hidden; }
/* nothing behind an open card moves */
html.detail-open #container *, html.detail-open #container *::before, html.detail-open #container *::after { animation-play-state: paused !important; }

/* "BROKEN!", across Work while the switch is on "Build fast" and the cards are in pieces: a smashed pixel title
   (art/make_work.py, broken_title; it began as a copy of the SOLD stamp, which he took back). It slams down half a
   second after the pixels fly, and goes the moment "Build" is chosen. */
@keyframes stamp-slam { 0% { opacity: 0; scale: 2.2; } 60% { opacity: 1; scale: 0.92; } 80% { scale: 1.06; } 100% { opacity: 1; scale: 1; } }
#broken-stamp { display: none; position: absolute; z-index: 31; left: 50%; top: 50%; width: 576px; height: 204px; margin: -102px 0 0 -288px; image-rendering: pixelated; pointer-events: none; }
#break-toggle[aria-checked="false"] ~ #broken-stamp { display: block; animation: stamp-slam 0.32s 0.5s ease-out both; }
html.phone #broken-stamp { width: 384px; height: 136px; margin: -68px 0 0 -192px; }
@media (prefers-reduced-motion: reduce) { #break-toggle[aria-checked="false"] ~ #broken-stamp { animation: none; } }

/* ---------------------------------------------------------------- sunset: the end of the green
   The green stops on a hard stepped edge. Below it the sunset sky, and along
   its foot a dark tree line that the sun sets behind. */
#sunset {
  position: relative;
  z-index: 6;
  height: 430px;
  overflow: hidden;
  background: linear-gradient(#ff412e, #ff9a83);
}
#sunset::before {                      /* stepped edge of the green, like the hedge */
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 14px;
  z-index: 3;
  background:
    linear-gradient(#197A56, #197A56) 0 0 / 100% 7px no-repeat,
    repeating-linear-gradient(90deg, #197A56 0 42px, transparent 42px 84px) 0 7px / 100% 7px no-repeat;
}
#sunset .design-heading { position: relative; top: 60px; z-index: 4; }
/* The sun sits still in the middle, half behind the trees. Only its glow moves: it swells and fades, like a sunset. */
@keyframes sun-glow {
  from { background-color: #ffb400; box-shadow: 0 0 60px 10px #ffb400, 0 0 140px 30px rgba(255, 170, 0, 0.75), 0 0 260px 70px rgba(255, 150, 0, 0.45); }
  to { background-color: #ff5a1f; box-shadow: 0 0 90px 30px #ff5a1f, 0 0 240px 90px rgba(255, 70, 20, 0.8), 0 0 520px 200px rgba(255, 60, 20, 0.5); }
}
/* the sky behind it warms and cools with the sun */
@keyframes sky-glow { from { opacity: 0; } to { opacity: 1; } }
#sunset::after { content: ""; position: absolute; left: 0; top: 0; width: 100%; height: 100%; z-index: 0; background: radial-gradient(ellipse 60% 90% at 50% 100%, rgba(255, 60, 10, 0.55), rgba(255, 60, 10, 0) 70%); animation: sky-glow 3.2s ease-in-out infinite alternate; pointer-events: none; }
#sun {
  height: 100px;
  width: 200px;
  background: #ffbc00;
  position: absolute;
  z-index: 1;
  border-radius: 100px 100px 0 0;
  bottom: 72px;                    /* its lower part is hidden behind the trees */
  left: 50%;
  margin-left: -100px;
  animation: sun-glow 3.2s ease-in-out infinite alternate;
}
#tree-line {
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 112px;
  z-index: 2;
  background: url(img/tree-line.png) 50% 100% repeat-x;
}

/* ---------------------------------------------------------------- Decks */
#section-decks {
  position: relative;
  z-index: 6;
  padding: 40px 0 480px;           /* the night city (480px) sits right under the last row of decks, so the Skills heading is about 200px below it */
  overflow: hidden;
  background: linear-gradient(#03398e 30%, #0f0f0f 80%, #51503f);
}

/* ---------------------------------------------------------------- the bookshelf
   Under the decks: a pixel shelf of seven spines (a button). Opened, the seven covers stand round a ring seen from a
   little above: the front one large and low, the others smaller and higher behind it. script.js places them and
   turns the ring. A cover is 48 x 72 art pixels, shown at a whole multiple (--book-n), like every other sprite. */
#books { position: relative; z-index: 2; margin: 70px auto 0; width: min(1240px, 100% - 20px); display: grid; justify-items: center; align-items: center; --book-n: 3; }
html.phone #books { margin-top: 40px; --book-n: 4; }      /* on a phone the front book is the picture: 192 x 288, the middle of the screen; the others peek out behind it */
/* the shelf and the ring share one cell: the ring opens right on top of the shelf, and the shelf goes dark behind it */
#bookshelf, #book-ring { grid-area: 1 / 1; }
#bookshelf { display: block; padding: 0; border: 0; background: none; cursor: pointer; transition: filter 0.2s; -webkit-tap-highlight-color: transparent; }
#bookshelf img { display: block; width: 448px; height: 312px; image-rendering: pixelated; }
html.phone #bookshelf img { width: 336px; height: 234px; }
#bookshelf .shelf-label { display: block; margin-top: 10px; font-family: "Pixelify Sans", monospace; font-size: 30px; line-height: 1; color: #fdfdfd; }
html.phone #bookshelf .shelf-label { font-size: 20px; }
#bookshelf:hover img, #bookshelf:focus-visible img { translate: 0 -4px; }
#bookshelf:focus-visible { outline: 4px solid #ffbc00; outline-offset: 6px; }
#books.open #bookshelf { filter: brightness(0.25); }
/* the framed picture's button: laid exactly over the frame drawn on the shelf (art pixels 79 to 102 across, 44 to 67 down, of 112; --shelf-n is the shelf's scale) */
#books { --shelf-n: 4; }
html.phone #books { --shelf-n: 3; }
#shelf-photo { grid-area: 1 / 1; align-self: start; justify-self: center; z-index: 1; width: calc(24px * var(--shelf-n)); height: calc(24px * var(--shelf-n)); margin: calc(44px * var(--shelf-n)) 0 0 calc(69px * var(--shelf-n)); padding: 0; border: 0; background: none; cursor: zoom-in; }
#shelf-photo:hover, #shelf-photo:focus-visible { outline: 4px solid #ffbc00; outline-offset: 2px; }
#books.open #shelf-photo { display: none; }
.detail-photo .detail-box { max-width: 720px; padding: 16px; background: rgba(15, 15, 15, 0.45); }      /* the photograph's card is see-through, so the shelf shows round the picture (the work cards stay solid) */
.detail-photo .detail-box img { display: block; flex: 1 1 auto; min-height: 0; width: 100%; height: auto; max-height: 100%; object-fit: contain; image-rendering: auto; }
html.phone .detail-photo .detail-box { padding: 60px 8px 8px; justify-content: center; }
#books.open #bookshelf:hover img { translate: 0 0; }
#book-ring { position: relative; z-index: 2; width: 100%; height: calc(72px * var(--book-n) + 110px); touch-action: pan-y; cursor: grab; -webkit-user-select: none; user-select: none; }
#book-ring.dragging { cursor: grabbing; }
#book-ring[hidden] { display: none; }
.book { position: absolute; left: 50%; top: 0; width: calc(48px * var(--book-n)); height: calc(72px * var(--book-n)); margin-left: calc(-24px * var(--book-n)); padding: 0; border: 0; background: #0f0f0f; cursor: pointer; transform-origin: 50% 50%; will-change: transform, opacity; -webkit-backface-visibility: hidden; backface-visibility: hidden; -webkit-tap-highlight-color: transparent; }
.book img { display: block; width: 100%; height: 100%; image-rendering: auto;      /* photographs of the covers, not pixel art */ border: 4px solid #0f0f0f; box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.45); box-sizing: border-box; pointer-events: none; -webkit-user-drag: none; }
.book.front img { border-color: #fdfdfd; }
.book:focus-visible { outline: 4px solid #ffbc00; outline-offset: 4px; }
.ring-turn { position: absolute; top: 40%; z-index: 20; width: 48px; height: 48px; padding: 0; border: 4px solid #0f0f0f; background: #fdfdfd; color: #0f0f0f; font-family: "Pixelify Sans", monospace; font-size: 30px; line-height: 1; cursor: pointer; box-shadow: 4px 4px 0 #29BA74; touch-action: manipulation; }
.ring-turn:hover, .ring-turn:focus-visible { background: #ffbc00; outline: 0; }
#ring-prev { left: 0; }
#ring-next { right: 0; }
#ring-close { position: absolute; right: 0; top: 0; z-index: 20; width: 44px; height: 44px; padding: 0; border: 4px solid #0f0f0f; background: #fdfdfd; color: #0f0f0f; font-family: "Pixelify Sans", monospace; font-size: 20px; line-height: 1; cursor: pointer; touch-action: manipulation; }
#ring-close:hover, #ring-close:focus-visible { background: #ffbc00; outline: 0; }
/* a book's card: the work popups' card, with its cover beside the text */
.detail-book .book-cover { float: left; width: 200px; height: 300px; margin: 4px 28px 12px 0; image-rendering: auto; border: 4px solid #fdfdfd; }
html.phone .detail-book .book-cover { width: 120px; height: 180px; margin-right: 18px; }
.detail-book .book-note { color: #b9b9b9; font-style: italic; }

/* ---------------------------------------------------------------- Skills
   The night city sits at the foot of the Decks section, as in the reference,
   with the heading over it. Its four frames change every half second. */
@keyframes city { 100% { background-position: 0 -1920px; } }
#cityscape {
  position: absolute;
  left: var(--cx);
  bottom: 0;
  width: 2400px;
  height: 480px;
  background: url(img/skyline.png) 0 0 no-repeat;
  animation: city 2s steps(4) infinite;
}
#skills-heading { position: absolute; bottom: 190px; left: 0; width: 100%; z-index: 1; }

/* black from edge to edge, carrying on from the night above it, and never more than two thirds of the screen tall */
#section-skills { position: relative; z-index: 6; background: #0f0f0f; padding: 18px 0 26px; --chart-h: clamp(220px, calc(var(--vh, 900px) * 0.66 - 200px), 500px); }
/* the reference's vertical bar chart, drawn as a BCG chart on a white panel */
.chart-panel { width: 80%; max-width: 1180px; margin: 0 auto; background: #0f0f0f; padding: 40px 40px 84px; }
#chart-vertical { width: 100%; height: var(--chart-h); display: block; color: #D0D0D0; }
#chart-vertical::after { content: ""; display: block; clear: both; }
#chart-vertical .numbers { width: 12%; height: 100%; margin: 0; padding: 0; display: inline-block; float: left; }
#chart-vertical .numbers li { text-align: right; padding-right: 1em; list-style: none; height: calc(var(--chart-h) * 0.18); position: relative; bottom: calc(var(--chart-h) * 0.04); }
#chart-vertical .numbers li span { font-size: 16px; line-height: 1.15; color: #D0D0D0; position: absolute; top: calc(var(--chart-h) * 0.132); left: -35px; right: 20px; transform: translateY(-25%); }
#chart-vertical .numbers li::after { content: " "; width: 14px; position: absolute; height: 1px; background: #6a6a6a; top: calc(var(--chart-h) * 0.144); right: 0; }
#chart-vertical .bars { display: inline-block; width: 88%; height: 100%; padding: 0; margin: 0; border-bottom: 1px solid #6a6a6a; border-left: 1px solid #6a6a6a; position: relative; }
#chart-vertical .bars li { display: inline-block; width: 15.5%;   /* six bars now (was seven at 13%) */ height: var(--chart-h); margin: 0; text-align: center; position: relative; bottom: 1px; }
#chart-vertical .bars li .bar { display: block; width: 70px; margin-left: 15px; position: absolute; bottom: 0; height: 0; transition: height 1.5s cubic-bezier(0.37, 0, 0.63, 1); }
/* each bar takes the green of the level it reaches (its data-percentage), darker low, lighter high; heights are untouched */
#chart-vertical .bars li .bar { background: #22C55E; }
#chart-vertical .bars li .bar[data-percentage="17.6"] { background: #0F9D58; }      /* Ehh... */
#chart-vertical .bars li .bar[data-percentage="35.6"] { background: #16B364; }      /* Beginner but hopeful */
#chart-vertical .bars li .bar[data-percentage="53.6"] { background: #22C55E; }      /* Moderate and improving */
#chart-vertical .bars li .bar[data-percentage="71.6"] { background: #4ADE80; }      /* Pretty darn good */
#chart-vertical .bars li .bar[data-percentage="89.6"] { background: #86EFAC; }      /* Grandmaster */
#chart-vertical .bars li .bar[data-percentage="100"] { background: #BBF7D0; }       /* Making the 30-slide deck (was Dad jokes): off the top */
/* pixel-art dress for the chart, settled over several rounds with him. The one he liked: FLAT solid bars in their own
   green with the top corners stepped in pixels (two 4px steps on a laptop). Tried and refused, do not bring back: bars
   built of blocks, pixel shading down the sides, a ragged pixel top edge. On phones the bars are narrow and two steps
   read as rounded ("pixelated but square"), so there it is a single 3px pixel off each corner. The axes are 4px thick,
   the ticks square, and each level has a dotted pixel line. Heights, colours, labels and fonts are untouched. */
#chart-vertical .bars li .bar { width: 64px; margin-left: 18px; clip-path: polygon(8px 0, calc(100% - 8px) 0, calc(100% - 8px) 4px, calc(100% - 4px) 4px, calc(100% - 4px) 8px, 100% 8px, 100% 100%, 0 100%, 0 8px, 4px 8px, 4px 4px, 8px 4px); }
/* "Pixel shading on the chart" (asked for again after the flat version): hard pixel shading down the sides of each
   bar, laid see-through over its own green: a lit left edge, a shaded right edge and one column of dither where the
   shade begins. --u is one art pixel (4px, 3px on phones). No blocks, no seams across the bar. */
#chart-vertical .bars li .bar[data-percentage] { --u: 4px; background-repeat: no-repeat;
  background-image: linear-gradient(rgba(255, 255, 255, 0.4) 0 0), linear-gradient(rgba(0, 0, 0, 0.45) 0 0), linear-gradient(rgba(0, 0, 0, 0.27) 0 0), repeating-linear-gradient(0deg, rgba(0, 0, 0, 0.27) 0 var(--u), transparent var(--u) calc(var(--u) * 2));
  background-size: var(--u) 100%, var(--u) 100%, var(--u) 100%, var(--u) 100%;
  background-position: 0 0, 100% 0, calc(100% - var(--u)) 0, calc(100% - var(--u) * 2) 100%; }
html.phone #chart-vertical .bars li .bar[data-percentage] { --u: 3px; }
#chart-vertical .bars { box-shadow: -3px 0 0 #6a6a6a, 0 3px 0 #6a6a6a, -3px 3px 0 #6a6a6a; --dots: repeating-linear-gradient(90deg, #333 0 4px, transparent 4px 16px);
  background: var(--dots) 8px calc(var(--chart-h) * 0.104 - 2px) / 100% 4px no-repeat, var(--dots) 8px calc(var(--chart-h) * 0.284 - 2px) / 100% 4px no-repeat, var(--dots) 8px calc(var(--chart-h) * 0.464 - 2px) / 100% 4px no-repeat, var(--dots) 8px calc(var(--chart-h) * 0.644 - 2px) / 100% 4px no-repeat, var(--dots) 8px calc(var(--chart-h) * 0.824 - 2px) / 100% 4px no-repeat; }
#chart-vertical .numbers li::after { width: 12px; height: 4px; margin-top: -2px; right: 3px; }
html.phone #chart-vertical .bars li .bar { clip-path: polygon(3px 0, calc(100% - 3px) 0, calc(100% - 3px) 3px, 100% 3px, 100% 100%, 0 100%, 0 3px, 3px 3px); }
#chart-vertical.grown .bars li .bar { height: var(--bar); }
/* After the bars have risen together, the 100 bar ("Making the 30-slide deck", once Dad jokes; no longer the last
   bar, so it is picked by its level) alone keeps creeping up for three more seconds, at a steady crawl, to
   half way between the top of the chart and the foot of the buildings above (--over, measured by script.js). */
#chart-vertical.grown.over .bars li .bar[data-percentage="100"] { height: calc(100% + var(--over, 28px)); transition: height 3s linear; }
#chart-vertical.reset .bars li .bar { transition: none; }
#chart-vertical .bars li span { color: #D0D0D0; font-size: 16px; line-height: 18px; width: 100px; position: absolute; top: calc(100% + 12px); left: 0; text-align: center; margin-left: 0; }   /* 100 wide (was 70, same centre) for the two long deck labels */

/* Narrow screens: the same chart, sized to fit. Bars share the width evenly and
   the labels under them are turned to read upwards. */
/* "Reduce the blackspace from the chart to cybershub on mobile": the panel ends just under the slanted labels (112,
   was 140), the section's own foot is 6 (was 26) and the night strip above the gate is 170 (was 220): about 100 less. */
html.phone .chart-panel { width: calc(100% - 20px); padding: 20px 10px 112px 6px; }
html.phone #section-skills { padding-bottom: 6px; }
html.phone #section-skills { --chart-h: 320px; }
html.phone #chart-vertical .numbers { width: 96px; }
html.phone #chart-vertical .numbers li { padding-right: 0; }
/* phones: no tick dashes; each label sits centred on its own level, clear of the axis */
html.phone #chart-vertical .numbers li span { left: 0; right: 10px; top: calc(var(--chart-h) * 0.144); transform: translateY(-50%); font-size: max(13px, calc(var(--px16, 16px) * 0.8)); }
html.phone #chart-vertical .numbers li::after { display: none; }
html.phone #chart-vertical .bars { width: calc(100% - 96px); display: inline-flex; }
html.phone #chart-vertical .bars li { flex: 1; width: auto; height: var(--chart-h); }
html.phone #chart-vertical .bars li .bar { width: 70%; left: 15%; margin-left: 0; }
html.phone #chart-vertical .bars li span { width: 132px; left: 50%; margin-left: 0; top: calc(100% + 8px); text-align: right; font-size: max(14px, calc(var(--px16, 16px) * 0.875)); line-height: 1.05; transform-origin: 100% 0; transform: translate(-100%, 0) rotate(-55deg); }
html.tablet .chart-panel { width: calc(100% - 40px); padding: 48px 24px 96px; }
html.tablet #chart-vertical .bars li .bar, html.tablet #chart-vertical .bars li span { width: 64px; margin-left: calc(50% - 32px); }

/* One boat on the Yamuna, just below the tree line. script.js runs the loop:
   fish, bubbles, lift the rod (nothing), paddle across, fish, bubbles, lift (a
   fish!), paddle back. The frame is a class; the crossing is a straight slide. */
@keyframes bubbles { 100% { background-position: -144px 0; } }
@keyframes ripples { 100% { background-position: -96px 0; } }
@keyframes surface { from { transform: translateY(100%); } to { transform: translateY(0); } }
#fisher { position: absolute; z-index: 3; top: 14px; left: var(--spot-a); width: 90px; height: 66px; background: url(img/fisher.png?v=2) 0 0 / 540px 66px no-repeat; transition: left var(--trip, 3.4s) linear;
  --spot-a: calc(50% - min(420px, var(--half, 700px) - 60px)); --spot-b: calc(50% - 45px); --spot-c: calc(50% + min(330px, var(--half, 700px) - 150px)); }
#fisher.at-b { left: var(--spot-b); }
#fisher.at-c { left: var(--spot-c); }
#fisher.pose-empty { background-position: -90px 0; }
#fisher.pose-catch { background-position: -180px 0; }
#fisher.pose-paddle { background-position: -270px 0; }
#fisher.pose-paddle.stroke { background-position: -360px 0; }
/* He has pulled a man out of the river. A startled frame (arms up, hat in the air, rod let go), and the boat
   shakes under him for a second and a half. Soutik's head and the "!" get the opposite movement, so they hold still. */
#fisher.pose-shock { background-position: -450px 0; }
@keyframes fisher-shake { 0%, 100% { translate: 0 0; } 15% { translate: -3px -3px; } 30% { translate: 3px 0; } 45% { translate: -3px -1px; } 60% { translate: 3px -2px; } 80% { translate: -2px 0; } }
@keyframes fisher-still { 0%, 100% { translate: 0 0; } 15% { translate: 3px 3px; } 30% { translate: -3px 0; } 45% { translate: 3px 1px; } 60% { translate: -3px 2px; } 80% { translate: 2px 0; } }
#fisher.surprise { animation: fisher-shake 0.3s steps(1) 5; }
#fisher.surprise #fisher-surprise, #fisher.surprise #fisher-shock { animation: fisher-still 0.3s steps(1) 5; }
@media (prefers-reduced-motion: reduce) { #fisher.surprise, #fisher.surprise #fisher-surprise, #fisher.surprise #fisher-shock { animation: none; } }
#fisher.back { transform: scaleX(-1); }
/* bubbles round the float while something is biting: big, and plain to see */
#fisher-bubbles { display: none; position: absolute; left: 50px; top: 34px; width: 48px; height: 36px; background: url(img/bubbles.png) 0 0 / 144px 36px no-repeat; animation: bubbles 0.5s steps(3) infinite; }
#fisher.bite #fisher-bubbles { display: block; }
/* ripples on the water behind the boat while he paddles */
#fisher-ripples { display: none; position: absolute; left: -52px; top: 52px; width: 48px; height: 15px; background: url(img/ripples.png) 0 0 / 96px 15px no-repeat; animation: ripples 0.5s steps(2) infinite; }
#fisher.pose-paddle #fisher-ripples { display: block; }
/* the third spot: what comes up on the line is Soutik's head, wide-eyed */
#fisher-surprise { display: none; position: absolute; left: 56px; top: 25px; width: 44px; height: 31px; overflow: hidden; border-bottom: 3px solid #e9eef2; }
#fisher-surprise span { display: block; width: 44px; height: 34px; background: url(img/soutik-shocked.png) 0 0 / 44px 34px no-repeat; animation: surface 0.5s steps(5) both; }
#fisher.surprise #fisher-surprise { display: block; }
#fisher-shock { display: none; position: absolute; left: 4px; top: -14px; font-family: "Pixelify Sans", monospace; font-size: 30px; line-height: 1; color: #ffbc00; text-shadow: 2px 2px 0 #0f0f0f; }
#fisher.surprise #fisher-shock { display: block; }
html.phone #section-decks { padding-top: 92px; }

/* ---------------------------------------------------------------- the ending: dawn
   A 600px strip of sky between Skills and Cyber Hub. script.js steps its five
   flat bands from night to day as it scrolls by, puts the stars out and
   switches the city's windows off. It is drawn over Soutik and Luna, who come
   out from under its foot through the gate. */
@keyframes wave { 100% { background-position: -416px 0; } }
@keyframes reel { 100% { background-position: -72px 0; } }
#section-end { position: relative; background: #03398e; }
#dawn { position: relative; height: 240px; overflow: hidden; }
/* The night above Skills, the other way up: its two colours in one even run from the very top to the very
   bottom (no flat black first, so no sudden start), ending exactly where the Cyber Hub ground starts. In the blue, the same pixel
   stars as up there (img/night-stars.png: the star rows of the skyline art, its four twinkling frames), and
   no skyline. Nothing in it changes with the scroll. */
@keyframes night-stars { 100% { background-position: 0 -720px; } }
#dawn-stage { background: linear-gradient(#0f0f0f, #03398e) !important; }
#dawn-city { display: none !important; }
#dawn-stars { display: block !important; opacity: 1 !important; top: auto; bottom: 0; height: 180px; background: url(img/night-stars.png) 0 0 / 2400px 720px no-repeat; image-rendering: pixelated; animation: night-stars 2s steps(4) infinite; }
@media (prefers-reduced-motion: reduce) { #dawn-stars { animation: none; } }

/* two dry-country trees on the sand, behind the towers */
.arid-tree { position: absolute; z-index: 1; display: block; }

/* Gurugram's dry grass, a row of clumps to a band (art/make_ending.py). Most of the time it stands still.
   Every eight seconds a gust comes through: each row leans over, shivers and stands up again, the far rows first. */
@keyframes gust { 0%, 58%, 100% { transform: skewX(0deg); } 66% { transform: skewX(-13deg); } 73% { transform: skewX(-5deg); } 80% { transform: skewX(-11deg); } 90% { transform: skewX(2deg); } }
.scrub-row { position: absolute; left: var(--cx); width: 2400px; height: 72px; background-repeat: no-repeat; background-size: 2400px auto; transform-origin: 50% 60px; animation: gust 8s ease-in-out infinite; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .scrub-row { animation: none; } }
#dawn-stage {
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  z-index: 4;
  overflow: hidden;
  --s0: #090909; --s1: #0b0d18; --s2: #0f1226; --s3: #131a36; --s4: #182246;
  background: linear-gradient(var(--s0) 0 30%, var(--s1) 30% 52%, var(--s2) 52% 70%, var(--s3) 70% 85%, var(--s4) 85% 100%);
}
#dawn-stars { position: absolute; left: var(--cx); width: 2400px; }
#dawn-city { position: absolute; left: var(--cx); bottom: 0; width: 2400px; height: 384px; display: block; }

/* ---------------------------------------------------------------- the ending: Cyber Hub */
#end-ground {
  position: relative;
  /* just enough plaza for the pair and their bubbles to sit in the middle of the last screen */
  /* the pair a little below the middle, so the whole viaduct shows at the top (456px = stop mark to viaduct top, plus the header) */
  height: calc(var(--stop) + min(var(--vh, 900px) / 2 - 100px, var(--vh, 900px) - 456px));
  background: #beb4a0;
}
#one-horizon, #gateway-tower { position: absolute; z-index: 5; display: block; }      /* in front of the dawn sky: their tops stand against it */
#cyberhub-gate { position: absolute; z-index: 5; display: block; }
/* The viaduct is Soutik's tile, repeated. Over the road one pillar is left out
   (the span there shows only the tile's deck), so nothing stands on the tarmac. */
#viaduct { position: absolute; left: var(--cx); width: 2400px; height: 160px; z-index: 5; pointer-events: none; }
.viaduct-run { position: absolute; top: 0; width: 1104px; height: 160px; background: url(img/cyberhub-metro-viaduct.png) 0 0 / 192px 160px repeat-x; }
.viaduct-left { left: 0; background-position: 144px 0; }
.viaduct-right { left: 1296px; }
.viaduct-span { position: absolute; left: 1104px; top: 0; width: 192px; height: 52px; overflow: hidden; display: flex; }
.viaduct-span span { flex: none; width: 60px; height: 52px; background: url(img/cyberhub-metro-viaduct.png) 0 0 / 192px 160px no-repeat; }
#metro-train { position: absolute; left: 0; top: -40px; width: 452px; height: 56px; background: url(img/metro-train-3.png) 0 0 / 452px 56px no-repeat; }      /* three cars, the driving car in front (art/make_arid.py) */

/* "On repeat": two records, each half out of its sleeve, with the song and artist
   beside it. A third the size of the old cassettes. script.js picks where they go:
   a column right of the social bubble (html.tapes-col), or a row under it (html.tapes-under). */
@keyframes vinyl-spin { 100% { background-position: -192px 0; } }
#on-repeat { position: absolute; z-index: 1; display: flex; flex-direction: column; gap: 10px; left: calc(var(--cx) + 1806px); top: calc(var(--stop) - 72px - 70px); }
/* three records in one row where the screen has the room; on a narrower laptop the third drops to a second row */
html.tapes-under #on-repeat { flex-direction: row; flex-wrap: wrap; gap: 10px 14px; width: min(520px, calc(var(--half, 720px) - 200px)); left: calc(var(--cx) + 1390px); top: calc(var(--stop) - 170px + var(--social-h, 180px) + 16px); }
#on-repeat h3 { flex: 0 0 100%; font-family: "Pixelify Sans", monospace; font-weight: 400; font-size: 20px; line-height: 1; color: #0f0f0f; }
.record { display: flex; align-items: center; gap: 10px; color: #0f0f0f; outline: 0; min-height: max(48px, var(--px44, 44px)); }
.record:focus-visible { outline: 3px solid #0f0f0f; outline-offset: 3px; }
.record-art { position: relative; flex: none; width: 72px; height: 48px; }
.sleeve { position: absolute; left: 0; top: 0; width: 48px; height: 48px; border: 2px solid #0f0f0f; background: #e2452b center / cover no-repeat; image-rendering: auto; }
.record-505 .sleeve { background-color: #ffbc00; }
/* the disc: the record sprite over a round label cut from the same album art */
.disc { position: absolute; left: 22px; top: 0; width: 48px; height: 48px; border-radius: 50%; background: url(img/vinyl.png) 0 0 / 192px 48px no-repeat, radial-gradient(#fdfdfd 0 2px, transparent 2px 10px, transparent 10px), url(img/sleeve-selfless.jpg) center / 22px 22px no-repeat, #0f0f0f; transition: left 0.15s steps(3); }
.record-505 .disc { background: url(img/vinyl.png) 0 0 / 192px 48px no-repeat, radial-gradient(#fdfdfd 0 2px, transparent 2px 10px, transparent 10px), url(img/sleeve-505.jpg) center / 22px 22px no-repeat, #0f0f0f; }
.record-runaway .disc { background: url(img/vinyl.png) 0 0 / 192px 48px no-repeat, radial-gradient(#fdfdfd 0 2px, transparent 2px 10px, transparent 10px), url(img/sleeve-runaway.jpg) center / 22px 22px no-repeat, #0f0f0f; }
/* The heading over the records is just "Music I love". No "Play us", and no nudge: the disc-peek hint (each disc
   slipping out of its sleeve and spinning every few seconds) is gone at his word. The disc still slides out and
   spins on hover and while its song plays. */
.record:hover .disc, .record:focus-visible .disc, .record.playing .disc { left: 28px; animation: vinyl-spin 0.5s steps(4) infinite; }
.record-text { display: flex; flex-direction: column; line-height: 1.1; white-space: nowrap; }
.record-text b { font-family: "Source Sans 3", "Source Sans Pro", sans-serif; font-weight: 700; font-size: max(16px, var(--px16, 16px)); }
.record-text i { font-family: "Source Sans 3", "Source Sans Pro", sans-serif; font-style: normal; font-size: max(14px, calc(var(--px16, 16px) * 0.875)); }      /* never under 14 real pixels; the song above it never under 16 */

/* the final screen: the two bubbles of the reference's contact section, either side of him */
/* the header's Contact link lands here: exactly one screen above the end of the page */
#section-contact { position: absolute; left: 0; top: calc(100% - var(--vh, 900px)); width: 1px; height: 1px; }
#contact-bubbles.speech-container { position: absolute; left: calc(var(--cx) + 600px); top: 0; margin-left: 0; pointer-events: auto; }
#contact-bubbles .speech { width: 400px; font-size: 30px; line-height: 1.25; padding: 8px 12px 10px; }
#contact-bubbles #email { font-size: 20px; }
html:not(.js) #contact-bubbles .speech { opacity: 1; visibility: visible; }
#contact-info { left: 10px; top: calc(var(--stop) - 210px); }      /* 50px further out than the reference, to leave room for the ball, the heart and the small bubbles */
#contact-info::before { right: -46px; top: 0; bottom: 0; margin: auto; border-width: 20px; border-color: transparent transparent transparent #0f0f0f; }
#contact-info::after { right: -35px; top: 0; bottom: 0; margin: auto; border-width: 15px; border-color: transparent transparent transparent #fdfdfd; }
#social-media { right: 10px; top: calc(var(--stop) - 170px); text-align: center; }
#social-media::before { left: -46px; top: 0; bottom: 0; margin: auto; border-width: 20px; border-color: transparent #0f0f0f transparent transparent; }
#social-media::after { left: -35px; top: 0; bottom: 0; margin: auto; border-width: 15px; border-color: transparent #fdfdfd transparent transparent; }
/* The email is a button like the ones in the work popups (.pixel-button: white, black border, green hard shadow,
   yellow under the pointer). Pressed, it sinks onto its shadow. */
#email { display: inline-flex; align-items: center; margin-top: 14px; text-decoration: none; max-width: 100%; overflow-wrap: anywhere; }
#email:hover, #email:focus-visible, #email:active { translate: 2px 2px; box-shadow: 2px 2px 0 #29BA74; }
.js #email { opacity: 0; transition: opacity 0.2s, translate 0.06s, box-shadow 0.06s; }
.js #email.show { opacity: 1; }
/* the three marks: the companies' own shapes and colours, each with half its width clear all round */
#social-media-blocks { list-style: none; margin: 18px 0 6px; padding: 0; display: flex; justify-content: center; }
.social-media-block { width: 33%; display: flex; justify-content: center; }
.js .social-media-block { opacity: 0; transition: opacity 0.6s; }
.js .social-media-block.show { opacity: 1; }
.social-media-block a { display: flex; align-items: center; justify-content: center; width: max(76px, var(--px44, 44px)); height: max(60px, var(--px44, 44px)); outline: 0; }
.social-media-block a:focus-visible { outline: 4px solid #0f0f0f; }
.social-media-block a:hover { opacity: 0.8; }
.social-media-block .logo { display: block; width: 38px; height: 38px; }
.social-media-block a[data-brand="linkedin"] { color: #0A66C2; }
.social-media-block a[data-brand="github"] { color: #181717; }
.social-media-block a[data-brand="x"] { color: #000000; }
#copyright { position: absolute; left: 8px; bottom: 4px; z-index: 1; color: #0f0f0f; font-size: max(14px, calc(var(--px16, 16px) * 0.8)); }

/* ---------------------------------------------------------------- the final pose
   At the stop mark the walk sprites give way to the ending sheets. His frames
   are 32 art pixels wide (5 more each side than the walk), so the box moves 20px
   left. script.js sets one pose class at a time on #walk-group. */
@keyframes sit-wag { 100% { background-position: -352px 0; } }      /* four frames: tail right, tucked, left, tucked */
@keyframes wave-swing { from { transform: rotate(-4deg); } to { transform: rotate(34deg); } }
@keyframes head-tilt { 0%, 52%, 96%, 100% { transform: rotate(0deg); } 62%, 86% { transform: rotate(20deg); } }
@keyframes ball-toss { 0%, 70%, 100% { transform: translateY(0); } 78% { transform: translateY(-28px); } 85% { transform: translateY(-36px); } 92% { transform: translateY(-28px); } }
@keyframes luna-hop { 0%, 100% { translate: 0 0; } 40% { translate: 0 -28px; } }
.fetch-luna.hop { animation: luna-hop 0.42s ease-out; }      /* the ball has been held too long: she jumps for it (script.js) */
/* !important: the hop has to win over the happy-tail and calm-tail rules, which set the same property */
#walk-group #luna.hop { animation: luna-wag 0.28s steps(4) infinite, luna-hop 0.42s ease-out !important; }
#walk-group.arrived #luna.hop { animation: sit-wag 0.36s steps(4) infinite, luna-hop 0.42s ease-out !important; }
#walk-group.arrived #walker { left: -95px; width: 128px; background: url(img/soutik-end.png?v=6) 0 0 / 1024px 144px no-repeat; animation: none; }
#walk-group.pose-wave #walker { background-position: -128px 0; }
#walk-group.pose-pocket #walker { background-position: -256px 0; }
#walk-group.pose-wait #walker { background-position: -384px 0; }
#walk-group.pose-rest #walker { background-position: -512px 0; }
#walk-group.pose-bend #walker { background-position: -640px 0; }
/* His waving forearm is one rigid piece, turned about the elbow. Nothing in it is redrawn. */
#forearm { display: none; position: absolute; left: 92px; top: 16px; width: 16px; height: 52px; background: url(img/soutik-forearm.png) 0 0 / 16px 52px no-repeat; transform-origin: 50% 100%; }
#walk-group.pose-wave #forearm { display: block; animation: wave-swing 0.32s ease-in-out infinite alternate; }
#walk-group.pose-rest #forearm { display: block; animation: wave-swing 0.8s ease-in-out infinite alternate; }
#walk-group.pose-rest.greeting #forearm { animation-duration: 0.32s; }      /* the goodbye wave: quick for the first two seconds only */
/* Luna sits: her body wags, and her head is one rigid piece that tilts about the neck. */
#walk-group.arrived #luna { left: -1px; top: 362px; width: 88px; height: 80px; background: url(img/luna-sit-body.png?v=2) 0 0 / 352px 80px no-repeat; animation: sit-wag 1.6s steps(4) infinite; }
#walk-group.arrived.calm #luna { animation: none; background-position: -88px 0 !important; }      /* tail tucked. !important: a leftover walk frame set by the script must never show here */
#walk-group.arrived #luna.startled { animation: sit-wag 0.5s steps(4) infinite, luna-hop 0.42s ease-out; }
#walk-group.arrived.happy #luna { animation: sit-wag 0.36s steps(4) infinite; }      /* thrilled: even if her tail had gone still */
#luna-head { display: none; position: absolute; left: 4px; top: 8px; width: 72px; height: 40px; background: url(img/luna-head.png) 0 0 / 72px 40px no-repeat; transform-origin: 50% 100%; }
#walk-group.arrived #luna-head { display: block; }
#walk-group.pose-rest #luna-head { animation: head-tilt 5s ease-in-out infinite; }
/* She blinks: for a moment her two eyes (one art pixel each, 4px here) take the colour of her coat. Part of the head, so it turns with it. */
@keyframes luna-blink { 0%, 96.5% { opacity: 0; } 97%, 100% { opacity: 1; } }
/* She blinks on the walk too, whenever she is standing still and facing us: the same blink, on the standing sprite (its eyes are one row lower). */
#walk-group:not(.walking):not(.back):not(.arrived):not(.luna-away) #luna::after { content: ""; position: absolute; left: 24px; top: 20px; width: 4px; height: 4px; background: #2e2e2e; box-shadow: 20px 0 #2e2e2e; opacity: 0; animation: luna-blink 4s steps(1) infinite; }
@media (prefers-reduced-motion: reduce) { #luna::after { animation: none !important; } }
#luna-head::after { content: ""; position: absolute; left: 24px; top: 16px; width: 4px; height: 4px; background: #2e2e2e; box-shadow: 20px 0 #2e2e2e; opacity: 0; animation: luna-blink 4s steps(1) infinite; }
@media (prefers-reduced-motion: reduce) { #luna-head::after { animation: none; } }
#walk-group.luna-away #luna { visibility: hidden; }

.mini-bubble { display: none; position: absolute; z-index: 3; white-space: nowrap; background: #fdfdfd; color: #0f0f0f; border: 4px solid #0f0f0f; border-radius: 10px; padding: 1px 8px 3px; font-family: "Pixelify Sans", monospace; font-size: 20px; line-height: 1.2; }
#walk-group.pose-rest #throw-bubble { display: block; right: 70px; top: 290px; }
#walk-group.pose-rest #hi-bubble { display: block; left: 22px; top: 270px; }
#heart { display: none; position: absolute; left: -114px; top: 406px; width: max(44px, var(--px44, 44px)); height: max(44px, var(--px44, 44px)); padding: 0; border: 0; background: none; cursor: pointer; outline: 0; }
#walk-group.pose-rest #heart, #walk-group.pose-wait #heart, #walk-group.pose-bend #heart { display: block; }
.heart-art { position: absolute; left: 50%; top: 50%; width: 36px; height: 32px; margin: -16px 0 0 -18px; background: url(img/heart.png) 0 0 / 36px 32px no-repeat; }
/* a very quiet hint that the heart can be pressed: a small double beat (2px) every three seconds or so */
@keyframes heart-beat { 0%, 60%, 100% { translate: 0 0; scale: 1; } 66% { translate: 0 -5px; scale: 1.15; } 74% { translate: 0 0; scale: 1; } 82% { translate: 0 -5px; scale: 1.15; } 90% { translate: 0 0; scale: 1; } }      /* a real heartbeat: it jumps 6px and swells by a quarter, twice, every two seconds (2px, then 4px, were too faint for him) */
.heart-art { animation: heart-beat 2.3s steps(1) infinite; image-rendering: pixelated; }
#heart:hover .heart-art, #heart:focus-visible .heart-art, #heart[aria-disabled="true"] .heart-art { animation: none; }
@media (prefers-reduced-motion: reduce) { .heart-art { animation: none; } }
#heart:hover .heart-art, #heart:focus-visible .heart-art { top: calc(50% - 4px); }
#heart:focus-visible { outline: 4px solid #fdfdfd; outline-offset: 2px; }
#heart-tip { left: 50%; top: 100%; transform: translateX(-50%); }
#heart:hover #heart-tip, #heart:focus-visible #heart-tip { display: block; }
#heart[aria-disabled="true"] { cursor: default; }
#heart[aria-disabled="true"] .heart-art { opacity: 0.5; top: 50%; }

/* ---------------------------------------------------------------- fetch
   The ball, its shadow, and Luna while she is off fetching. They sit under the
   speech bubbles (z-index 7) and only the ball itself takes a press. */
.fetch-ball { position: absolute; left: 0; top: 0; z-index: 3; width: 32px; height: 32px; padding: 0; border: 0; background: none; cursor: grab; touch-action: none; outline: 0; -webkit-tap-highlight-color: transparent; }
.fetch-ball[hidden] { display: none; }
.fetch-ball::before { content: ""; position: absolute; left: 50%; top: 50%; width: max(44px, var(--px44, 44px)); height: max(44px, var(--px44, 44px)); transform: translate(-50%, -50%); }   /* a 44px target round a 32px ball */
.fetch-ball.held { cursor: grabbing; z-index: 8; }
.fetch-ball:focus-visible .ball-art { outline: 4px solid #fdfdfd; outline-offset: 2px; border-radius: 50%; }
.ball-art { position: absolute; left: 0; top: 0; width: 32px; height: 32px; background: url(img/tennis-ball.png) 0 0 / 32px 32px no-repeat; }
.fetch-ball.in-hand .ball-art { animation: ball-toss 2.5s steps(12) infinite; }
/* The ball in his hand has to look like something to pick up: a white ring opens out from it again and again, and a
   yellow one sits round it under the pointer. Both ride with the ball as he tosses it. The ring is a hint, so it shows only until the ball has been thrown once
   (html.thrown-once, until the page is loaded again), and the yellow outline under the pointer goes with it. */
@keyframes ball-ping { 0% { transform: scale(0.9); opacity: 0.95; } 70% { transform: scale(1.7); opacity: 0; } 100% { transform: scale(1.7); opacity: 0; } }
html:not(.thrown-once) .fetch-ball.in-hand .ball-art::after { content: ""; position: absolute; left: -4px; top: -4px; width: 40px; height: 40px; box-sizing: border-box; border: 4px solid #fdfdfd; border-radius: 50%; animation: ball-ping 1.4s steps(7) infinite; pointer-events: none; }
html:not(.thrown-once) .fetch-ball.in-hand:hover .ball-art { outline: 4px solid #ffbc00; outline-offset: 2px; border-radius: 50%; }
@media (prefers-reduced-motion: reduce) { .fetch-ball.in-hand .ball-art::after { animation: none; opacity: 0.8; } }
.fetch-shadow { position: absolute; left: 0; top: 0; z-index: 2; width: 24px; height: 8px; border-radius: 50%; background: rgba(0, 0, 0, 0.3); pointer-events: none; }
.fetch-luna { position: absolute; left: 0; top: 0; z-index: 3; background-repeat: no-repeat; pointer-events: none; }
.fetch-shadow[hidden], .fetch-luna[hidden] { display: none; }

/* the T-Rex, and the tiny shake as each foot comes down */
#trex { position: absolute; left: 0; top: 0; z-index: 1; width: 336px; height: 288px; background-repeat: no-repeat; pointer-events: none; transform-origin: 0 0; }
#trex[hidden] { display: none; }
canvas.trex-dust { position: absolute; left: 0; top: 0; z-index: 1; pointer-events: none; }
/* the first two seconds after arriving: the same resting pose with an open, toothy smile (script.js adds .grin) */
#walk-group.grin #walker { background-position: -896px 0; }
/* The T-Rex is here (script.js adds .scared for as long as it is on screen): he stares wide-eyed with his mouth open,
   the wave and the little bubbles stop, Luna's eyes go wide, her tongue goes in and her tail tucks, and both of them tremble. */
@keyframes tremble { 0%, 100% { translate: 0 0; } 50% { translate: 2px 0; } }
#walk-group.arrived.scared #walker { background-position: -768px 0; animation: tremble 0.12s steps(1) infinite; }
#walk-group.arrived.scared #forearm, #walk-group.arrived.scared #throw-bubble, #walk-group.arrived.scared #hi-bubble { display: none; }
#walk-group.arrived.scared #heart { display: none; }      /* the heart and its "See what I like" label go while the T-Rex is here, and come back when it has gone */
#walk-group.arrived.scared #luna { background-position: -88px 0 !important; animation: tremble 0.1s steps(1) infinite; }
#walk-group.arrived.scared #luna.startled { animation: tremble 0.1s steps(1) infinite, luna-hop 0.42s ease-out; }
#walk-group.arrived.scared #luna-head { background-image: url(img/luna-head-scared.png?v=2); animation: none; }
#walk-group.arrived.scared #luna-head::after { display: none; }
@media (prefers-reduced-motion: reduce) { #walk-group.arrived.scared #walker, #walk-group.arrived.scared #luna { animation: none; } }
/* the T-Rex shakes the ground: script.js (shake) writes --shx and --shy; the ground and the pair move together */
/* moved with left/top, NOT translate: a translate makes the ground its own layer, which put the whole of Cyber Hub
   (gate, viaduct, train) underneath the pair as they walked in */
#end-ground { left: var(--shx, 0px); top: var(--shy, 0px); box-shadow: 0 40px 0 #beb4a0, 40px 0 0 #beb4a0, -40px 0 0 #beb4a0; }      /* paving colour past the edges, so a jolt never shows the sky behind the page */
#walk-group { translate: var(--shx, 0px) var(--shy, 0px); }

/* phones and tablets: no room beside him, so both bubbles sit under the pair, and the cassettes stack */
html:is(.phone, .tablet) #contact-bubbles.speech-container { left: calc(var(--cx) + 1200px - var(--half)); width: calc(var(--half) * 2); }
html:is(.phone, .tablet) #contact-bubbles .speech { position: absolute; left: 0; right: 0; margin: 0 auto; width: min(400px, calc(100% - 20px)); font-size: 20px; }
html:is(.phone, .tablet) #contact-info { top: calc(var(--stop) + 44px); }
html:is(.phone, .tablet) #social-media { top: calc(var(--stop) + 44px + var(--info-h, 250px) + 16px); }
html:is(.phone, .tablet) #contact-info::before { right: 0; left: 0; top: -46px; bottom: auto; border-color: transparent transparent #0f0f0f transparent; }
html:is(.phone, .tablet) #contact-info::after { right: 0; left: 0; top: -28px; bottom: auto; border-color: transparent transparent #fdfdfd transparent; }
html:is(.phone, .tablet) #social-media::before, html:is(.phone, .tablet) #social-media::after { display: none; }
/* phones and tablets: the records sit under the social bubble */
html:is(.phone, .tablet) #end-ground { height: calc(var(--stop) + 44px + var(--info-h, 250px) + 16px + var(--social-h, 150px) + 16px + 208px + 14px); }
/* A wide, short tablet (1024 x 768): stacked, the two bubbles and the records are taller than the screen and push
   the pair up under the header. There the bubbles sit side by side under him, and the records in one row below. */
html.tablet.wide #contact-bubbles .speech { margin: 0; width: min(400px, calc(50% - 18px)); }
html.tablet.wide #contact-info { left: auto; right: calc(50% + 8px); }
html.tablet.wide #social-media { left: calc(50% + 8px); right: auto; top: calc(var(--stop) + 44px); }
html.tablet.wide #contact-info::before { left: auto; right: 24px; }
html.tablet.wide #contact-info::after { left: auto; right: 29px; }
html.tablet.wide #end-ground { height: calc(var(--stop) + 44px + max(var(--info-h, 250px), var(--social-h, 150px)) + 16px + 96px + 30px); }
html.tablet.wide #on-repeat { flex-direction: row; flex-wrap: wrap; gap: 10px 14px; width: 520px; left: calc(var(--cx) + 1200px - 260px); top: calc(var(--stop) + 44px + max(var(--info-h, 250px), var(--social-h, 150px)) + 16px); }
html:is(.phone, .tablet) #on-repeat { left: calc(var(--cx) + 1200px - 100px); top: calc(var(--stop) + 44px + var(--info-h, 250px) + 16px + var(--social-h, 150px) + 16px); }

/* ---------------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
  /* the cards do not build from pixels: a plain fade */
  .js .card { visibility: visible; opacity: 0; transition: opacity 0.6s !important; }
  .js .card.built { opacity: 1; }
}


/* ===========================================================================
   The fork at the chowraha (2026-10-09)
   A sign hanging above the ring's east arm, plus a prompt that is NOT
   inside the scaled stage, so it reads the same on a 390px phone where the
   sign itself is off screen. Right goes to journey.html, the Career Road.
   The board is drawn at the Scene B station boards' size (259x63 at 7x, the
   same letter height as AMBALA and PUNE) in its own dark colour, and its
   posts land on row 2051, the top edge of the cross road: it reads over the
   road you turn onto instead of standing in it, as it did at first.
   =========================================================================== */
.anchor-mark { position: absolute; left: 0; top: 1740px; width: 1px; height: 1px; }

#signpost {
  position: absolute;
  left: calc(var(--cx) + 1617px);   /* 1617 + 185/2 = 1709, the post on the same spot as before */
  top: 1916px;                      /* foot on 2016: 35px of grass above the cross road's top edge, 2051 */
  width: 185px;
  height: 100px;
  background: url(img/signpost.png) 0 0 / 185px 100px no-repeat;
  image-rendering: pixelated;
  z-index: 3;
}

#fork {
  position: fixed;
  left: 50%;
  bottom: calc(18px + env(safe-area-inset-bottom));
  transform: translate(-50%, 24px);
  z-index: 40;
  width: min(560px, calc(100vw - 24px));
  padding: 14px 16px 16px;
  background: #f2f2f2;
  color: #0f0f0f;
  border: 10px solid transparent;
  border-image: url(img/bubble-border.png) 10 stretch;
  background-clip: padding-box;
  image-rendering: pixelated;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.25s ease, transform 0.25s ease;
}
/* turning right at the roundabout (script.js, turnRight): the pair go side on and run off along the cross road before
   the town opens. .turn-in is the way back: the same run, mirrored, in from the right. */
@keyframes luna-side { 100% { background-position: -320px 0; } }
#walk-group.turn-right #walker { left: -87px; top: 296px; width: 112px; height: 148px; background: url(img/soutik-run-side.png) 0 0 / 672px 148px no-repeat !important; animation: run-side 0.54s steps(6) infinite !important; }
#walk-group.turn-right #luna { left: 34px; top: 374px; width: 80px; height: 68px; background: url(img/luna-walk-side.png) 0 0 / 320px 68px no-repeat !important; animation: luna-side 0.4s steps(4) infinite !important; }
#walk-group.turn-in #walker { scale: -1 1; }
#walk-group.turn-in #luna { scale: -1 1; left: -150px; }
/* walking the road to the town a step at a time (script.js, tripFrame): the same side-on pictures, but the frame is set
   by the ground covered, so the feet move only when they do */
#walk-group.side-walk #walker { background-image: url(img/soutik-walk-side.png?v=1) !important; background-size: 352px 144px !important; background-repeat: no-repeat !important; animation: none !important; }
#walk-group.side-walk #luna { left: 8px; top: 374px; width: 80px; height: 68px; background-image: url(img/luna-walk-side.png) !important; background-size: 320px 68px !important; background-repeat: no-repeat !important; animation: none !important; }
#walk-group.side-walk.side-left #walker { scale: -1 1; }
#walk-group.side-walk.side-left #luna { scale: -1 1; left: -150px; }
#walk-group.side-walk #forearm, #walk-group.side-walk #luna-head { display: none !important; }
#fork[hidden] { display: none; }
#fork.show { opacity: 1; visibility: visible; transform: translate(-50%, 0); }

#fork-ask {
  font-variant-ligatures: none;
  font-feature-settings: "liga" 0, "clig" 0;
  margin: 0 0 12px;
  font-family: 'Pixelify Sans', monospace;
  font-size: 19px;
  text-align: center;
}
#fork-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
.fork-btn {
  display: grid; gap: 2px; justify-items: center; text-align: center;
  padding: 11px 8px 10px;
  text-decoration: none;
  color: #f2f2f2;
  background: #3b3b3b;
  border: 3px solid #0f0f0f;
  box-shadow: 0 4px 0 #0f0f0f;
  image-rendering: auto;
}
.fork-btn b { font-family: 'Pixelify Sans', monospace; font-weight: 400; font-size: 17px; }
.fork-btn span { font-size: 13px; opacity: 0.85; }
.fork-btn.right { background: #197a56; }
.fork-btn:hover { filter: brightness(1.18); }
.fork-btn:active { transform: translateY(4px); box-shadow: none; }
.fork-btn:focus-visible { outline: 3px solid #d4a017; outline-offset: 3px; }

#fork-dismiss {
  position: absolute; top: -6px; right: -6px;
  width: 30px; height: 30px;
  font-size: 18px; line-height: 1;
  color: #f2f2f2; background: #0f0f0f;
  border: 3px solid #f2f2f2; cursor: pointer;
}

@media (max-width: 460px) {
  #fork-ask { font-size: 16px; }
  .fork-btn b { font-size: 15px; }
  .fork-btn span { font-size: 12px; }
}
@media (prefers-reduced-motion: reduce) { #fork { transition: none; } }
