/* ============================================================
   Abdelrahman Shaaban — personal site
   ------------------------------------------------------------
   Almost nothing here is filled or boxed. The page is line, type
   and space on ink; the only solid object is the figure on the
   canvas behind it. Every rule that adds a surface has to justify
   itself, and most of them can't.
   ============================================================ */

:root{
  /* Pitch black ground, his call — and the canvas behind uses the exact
     same value, because any difference between the two shows as a seam
     where the WebGL layer ends.
     Contrast re-measured against #000, not guessed:
     amber 10.0:1 · cyan 12.6:1 · text 13.6:1 · muted 6.0:1 */
  --ink:    #000000;
  --ink-2:  #080B14;
  --amber:  #F2A22C;
  --cyan:   #6FD9E8;
  --text:   #C9D1E4;
  --muted:  #7C88A3;

  --hair:   color-mix(in srgb, var(--muted) 22%, transparent);

  --display: 'Michroma', system-ui, sans-serif;
  --body:    'Geist', system-ui, -apple-system, sans-serif;
  /* No monospace anywhere on this site — his call, and a firm one.
     Label and data roles keep their register through size, tracking and
     caps instead of through a typewriter face. */
  --label:   'Geist', system-ui, -apple-system, sans-serif;

  /* One spacing rhythm. Everything is a multiple of it. */
  --u: clamp(1rem, 0.6rem + 1.6vw, 1.75rem);

  --measure: 34rem;
  --gut: clamp(1.25rem, 5vw, 6rem);
}

*, *::before, *::after{ box-sizing: border-box; }

html{
  background: var(--ink);
  /* ABDEL is exactly 100vw wide, which is enough to trip a horizontal
     scrollbar. `clip` trims it without creating a scroll container the
     way `hidden` would. */
  overflow-x: clip;
  /* Never smooth-scroll here — the conductor owns motion, and this
     fights it. Anchor jumps are instant by design. */
  scroll-behavior: auto;
}

body{
  margin: 0;
  /* Clip on html alone still let the page slide 38px sideways — the
     scrolling element needs it too. */
  overflow-x: clip;
  font-family: var(--body);
  font-size: clamp(1rem, 0.95rem + 0.2vw, 1.0625rem);
  line-height: 1.6;
  color: var(--text);
  background: var(--ink);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* ------------------------------------------------------------
   The canvas sits behind everything, always. With JS off it is
   an empty element and the page is simply a document on ink.
   ------------------------------------------------------------ */
#scene{
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  z-index: 1;
  pointer-events: none;
}

#main{ position: relative; z-index: 2; }

/* ------------------------------------------------------------
   THE POOL

   The grey. A spotlight on pure black is invisible: light only shows
   where something catches it, so with nothing in the room the figure
   read as cut out and pasted on. This is that catch — a soft warm-grey
   haze centred where he stands, brightest just behind him and gone by
   the edges of the frame. It sits UNDER the wordmark, so ABDEL reads as
   standing in the light rather than glowing on its own.
   ------------------------------------------------------------ */
.pool{
  position: absolute;
  z-index: -1;
  left: 50%;
  top: 46svh;
  width: min(150vw, 1900px);
  height: min(130vh, 1500px);
  transform: translate(-50%, -50%);
  pointer-events: none;
  background:
    radial-gradient(46% 42% at 50% 44%, rgba(196, 202, 216, 0.085) 0%,
                                        rgba(150, 158, 178, 0.045) 38%,
                                        rgba(90, 96, 112, 0.018) 62%,
                                        transparent 78%),
    radial-gradient(22% 18% at 50% 30%, rgba(255, 236, 205, 0.055) 0%,
                                        transparent 72%);
}

/* ------------------------------------------------------------
   ABDO — his name behind the figure, the way the reference puts a
   ghost of hers there. It is BELOW the canvas in the stack, which
   is only possible because the renderer clears to transparent; the
   figure is drawn over it and the page's black shows through
   around him.
   ------------------------------------------------------------ */
.ghost{
  /* His bronze render, background matted out to real alpha so only the
     letters land on the page. Dropping the whole photo in was wrong:
     its studio backdrop became a grey slab across the frame and the
     letters, at photographic scale, swallowed the figure.

     So it is sized and dimmed to sit BEHIND him — the grey contrast he
     wanted comes from .pool, which is light in the room, not from a
     rectangle baked into an image. */
  position: absolute;
  z-index: 0;
  left: 50%;
  top: 52svh;
  transform: translate(-50%, -50%);
  width: 100vw;
  height: auto;
  margin: 0;
  opacity: 0.55;
  pointer-events: none;
  user-select: none;
}



/* ------------------------------------------------------------
   THE INTRO.

   There is no curtain element: the page is already black, so the
   opening is simply everything else holding still while the figure
   draws itself in. That also means the DEFAULT state — no attribute,
   which is what you get with JavaScript off — is fully visible. A
   loading overlay that needs JS to remove itself is a page that
   never loads for anyone whose JS fails.
   ------------------------------------------------------------ */
/* Black until he is off the ground. The page arrives at the top of the
   backflip — draw, hold, fade and the flight itself are all him alone. */
body[data-intro="draw"] #main,
body[data-intro="hold"] #main,
body[data-intro="fade"] #main,
body[data-intro="flight"] #main,
body[data-intro="draw"] .ghost,
body[data-intro="hold"] .ghost,
body[data-intro="fade"] .ghost,
body[data-intro="flight"] .ghost{
  opacity: 0;
}
body[data-intro="midair"] #main,
body[data-intro="midair"] .ghost,
body[data-intro="done"] #main,
body[data-intro="done"] .ghost{
  opacity: 1;
  transition: opacity 520ms ease;
}

/* ------------------------------------------------------------
   Skip link — visible the moment it is focused, never before.
   ------------------------------------------------------------ */
.skip{
  /* Clipped rather than parked at left:-9999px — a far-offscreen element
     widens the document and trips a horizontal scrollbar. */
  position: absolute;
  clip-path: inset(50%);
  left: 0;
  top: 0;
  z-index: 10;
  padding: 0.75em 1.25em;
  background: var(--ink-2);
  color: var(--cyan);
}
.skip:focus{ clip-path: none; }

/* ------------------------------------------------------------
   Sections. Each is a camera station AND a front door — someone
   linked to #record must land somewhere that reads on its own.
   ------------------------------------------------------------ */
.ch{
  position: relative;
  min-height: 100svh;
  padding: calc(var(--u) * 4) var(--gut);
  display: flex;
  flex-direction: column;
  justify-content: center;
  max-width: 78rem;
  margin-inline: auto;
}
/* Extra scroll length = more camera travel for that leg. The number
   is a duration, not a layout value. */
.ch--tall{ min-height: 190svh; justify-content: flex-start; }
.ch--aside{ min-height: 70svh; }

/* The content of a tall section holds still while the camera travels
   the runway beneath it. Two reasons this is sticky rather than
   centred: a deep link to #record has to LAND on the content (centred
   in a 190svh box puts it a screenful below the anchor, so you arrive
   at empty space), and the words should stay put while the shot moves
   — that is the difference between a camera move and a scroll. */
.hold{
  position: sticky;
  top: 0;
  min-height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: calc(var(--u) * 2) 0;
}
@media (prefers-reduced-motion: reduce){
  .hold{ position: static; min-height: 0; }
}

/* ------------------------------------------------------------
   Type
   ------------------------------------------------------------ */
/* The hero is a frame, not a column: name top left, CV top right,
   the line along the bottom, and the figure owning the middle. */
#hero{
  max-width: none;
  width: 100%;
  min-height: 100svh;
  display: block;
  padding: clamp(1.25rem, 3.5vw, 2.75rem) var(--gut);
}

/* Michroma is wide and monoweight, so ABDELRAHMAN is set small and
   tracked rather than huge — the size in this hero belongs to ABDO
   behind the figure, not to the full name in front of it. */
.wordmark{
  font-family: var(--display);
  font-size: clamp(1.4rem, 5vw, 4rem);
  line-height: 1;
  letter-spacing: 0.01em;
  margin: 0;
  color: #FFFFFF;
  text-transform: uppercase;
  white-space: nowrap;
}
/* SHAABAN starts where ABDELRAHMAN ends — it hangs off the end of the
   first name rather than sitting under its start.

   The indent is in the CHILD's em, so it has to be converted: Michroma
   sets wider than the 0.96em/character estimate once tracking is in,
   so the first pass overlapped by 39px. Measured off the rendered text
   instead: 13em of the CHILD's size lands SHAABAN just past the N.
   Both scale from the same clamp, so the join holds at every viewport
   rather than drifting. */
.surname{
  display: block;
  margin-top: 0.1em;
  margin-left: 13em;
  font-size: 0.9em;
  letter-spacing: 0.01em;
  color: #FFFFFF;
}

/* Slides in from the left, fast, once the figure is there. */
body[data-intro="midair"] .wordmark{
  animation: slide-in 420ms cubic-bezier(.16,.9,.3,1) both;
}
@keyframes slide-in{
  from{ opacity: 0; transform: translateX(-2.5em); }
  to  { opacity: 1; transform: none; }
}

/* Top right, and deliberately quiet — it is a utility, not a call to
   action competing with the figure. */
/* ------------------------------------------------------------
   Top right: the three places he actually is, then the CV.

   Monochrome on purpose. Three brand colours up there would be the
   loudest thing on a page whose whole point is one lit figure on
   black — so they sit as grey glyphs and only pick up a sheen when
   you reach for one.
   ------------------------------------------------------------ */
.top{
  position: absolute;
  top: clamp(1.25rem, 3.5vw, 2.75rem);
  right: var(--gut);
  display: flex;
  align-items: center;
  gap: clamp(0.9rem, 1.6vw, 1.4rem);
}

.top a{ display: inline-flex; align-items: center; }

.defs{ position: absolute; width: 0; height: 0; overflow: hidden; }

.top svg{
  width: 19px;
  height: 19px;
  display: block;
  /* Brushed steel rather than flat grey: a bright top face falling to a
     dark base, with a second lift near the bottom so it reads as a
     rounded metal surface catching the same light as everything else.
     Flat grey at this size just looks switched off. */
  fill: url(#steel);
  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.75));
  transition: filter 200ms ease, transform 200ms ease, opacity 200ms ease;
  opacity: 0.92;
}
.top a:hover svg{
  opacity: 1;
  filter: drop-shadow(0 0 9px rgba(200, 220, 255, 0.55))
          drop-shadow(0 1px 1px rgba(0, 0, 0, 0.7));
  transform: translateY(-1px) scale(1.06);
}
.top a:focus-visible svg{ fill: var(--cyan); }

.cv{
  font-family: var(--label);
  font-size: 0.6875rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  margin-left: 0.4rem;
  white-space: nowrap;

  /* Same brushed steel as the glyphs beside it — a bright top face
     falling to a dark base — painted through the text itself so the
     whole cluster reads as one material rather than icons plus a
     grey label. */
  background-image: linear-gradient(
    180deg,
    #EDF2FB 0%,
    #AEBACE 34%,
    #7C88A3 52%,
    #96A3BC 72%,
    #5A6478 100%
  );
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* The glyphs get a drop-shadow; clipped text needs its own, and it
     has to sit UNDER the fill, which is what text-shadow does. */
  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.75);
  opacity: 0.92;
  transition: opacity 200ms ease, filter 200ms ease, transform 200ms ease;
}
.cv:hover{
  text-decoration: none;
  opacity: 1;
  transform: translateY(-1px);
  filter: drop-shadow(0 0 9px rgba(200, 220, 255, 0.5));
}
/* Focus must stay visible, and a transparent-filled element shows no
   outline colour of its own — so the ring is drawn explicitly. */
.cv:focus-visible{ outline: 2px solid var(--cyan); outline-offset: 4px; }


/* ------------------------------------------------------------
   Bottom left, and it waits: it appears only once he has landed
   the flip, so the opening is the figure alone and the facts
   arrive after. Default (no JS, no WebGL) is visible — nothing
   here may depend on an animation completing.
   ------------------------------------------------------------ */
.creds{
  position: absolute;
  left: var(--gut);
  bottom: clamp(1.5rem, 5vw, 3rem);
  z-index: 3;
  margin: 0;
  padding: 0;
  list-style: none;
  font-family: var(--body);
  font-size: clamp(0.875rem, 1.05vw, 1rem);
  font-weight: 400;
  line-height: 1.75;
  letter-spacing: 0.005em;
  color: #FFFFFF;
  max-width: 34rem;
}
.creds li{ list-style: none; }

/* The one line that is a request rather than a fact gets the underline. */
.creds li:last-child{
  text-decoration: underline;
  text-decoration-thickness: 1px;
  text-underline-offset: 0.28em;
  text-decoration-color: rgba(255, 255, 255, 0.42);
}

body[data-intro="draw"] .creds,
body[data-intro="hold"] .creds,
body[data-intro="fade"] .creds,
body[data-intro="flight"] .creds,
body[data-intro="midair"] .creds,
body[data-world="on"][data-intro="done"]:not([data-stage="settled"]) .creds{
  opacity: 0;
}
body[data-stage="settled"] .creds{
  animation: creds-in 620ms cubic-bezier(.16,.9,.3,1) both;
}
@keyframes creds-in{
  from{ opacity: 0; transform: translateY(0.7em); }
  to  { opacity: 1; transform: none; }
}

/* The line sits along the bottom, clear of the figure. */
.hero-foot{
  position: absolute;
  left: var(--gut);
  right: var(--gut);
  bottom: clamp(1.5rem, 5vw, 3.5rem);
}



.lede{
  font-size: clamp(1.15rem, 0.9rem + 1vw, 1.6rem);
  line-height: 1.45;
  max-width: var(--measure);
  margin: calc(var(--u) * 2) 0 0;
  color: var(--text);
}

.meta{
  font-family: var(--label);
  font-size: 0.8125rem;
  letter-spacing: 0.04em;
  color: var(--muted);
  margin: var(--u) 0 0;
}

.eyebrow{
  font-family: var(--label);
  font-size: 0.75rem;
  font-weight: 400;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0 0 calc(var(--u) * 1.5);
}

.verb{
  font-family: var(--body);
  font-weight: 700;
  font-size: clamp(2.25rem, 8vw, 5.5rem);
  line-height: 0.9;
  letter-spacing: -0.015em;
  text-transform: uppercase;
  margin: 0;
  color: var(--text);
}
.verb-sub{
  max-width: var(--measure);
  color: var(--muted);
  margin: var(--u) 0 0;
}

/* The numeral. It is not a label beside the content — the content
   shows through it. Until the WebGL layer lands it is a quiet
   outline, which is already the right amount of presence. */
.numeral{
  font-family: var(--display);
  font-size: clamp(6rem, 26vw, 20rem);
  line-height: 0.75;
  margin: 0 0 calc(var(--u) * -1.5);
  color: transparent;
  -webkit-text-stroke: 1px color-mix(in srgb, var(--amber) 45%, transparent);
  user-select: none;
}

/* ------------------------------------------------------------
   The scroll-lit statement. The UNLIT state must be legible on
   its own — someone landing mid-sentence, or with JS off, sees
   only this. That is why --muted is 5.6:1 and not dimmer.
   ------------------------------------------------------------ */
.statement{
  /* Geist, not the Michroma wordmark face. Michroma is drawn for four
     words at a time; a thirteen-word block set in it stops being
     readable, which is the one thing this sentence has to be. */
  font-family: var(--body);
  font-weight: 800;
  font-size: clamp(2.1rem, 7.2vw, 5.75rem);
  /* Set solid. The reference stacks the lines almost touching, which
     is what turns a sentence into a block the light can climb; at 1.15
     it reads as a paragraph and the sweep has nothing to travel up. */
  line-height: 0.98;
  letter-spacing: -0.02em;
  text-transform: uppercase;
  max-width: 15ch;
  margin-inline: auto;
  text-align: center;
  color: var(--muted);
}
/* The light rises through each word rather than switching it on. A
   binary toggle makes the sweep step in word-sized jumps, which reads
   as the page stuttering; mixing on a 0..1 per word makes the same
   scroll distance carry a continuous glare. --l is set by site.js and
   substitutes as a plain token, so no @property is needed. */
.statement .w{
  color: color-mix(in srgb, var(--amber) calc(var(--l, 0) * 100%), var(--muted));
}
.statement .w.lit{ color: var(--amber); }
/* With JS off no words are split, so the whole line reads at 5.6:1. */

.shout{
  font-family: var(--display);
  font-size: clamp(2.5rem, 14vw, 12rem);
  line-height: 0.85;
  letter-spacing: -0.03em;
  text-transform: uppercase;
  margin: 0 0 calc(var(--u) * 2.5);
  color: var(--amber);
}

/* ------------------------------------------------------------
   Lists. Hairlines, not cards. No box, no shadow, no hover lift —
   that combination is the 2026 house style of AI-generated sites
   and it reads as exactly that.
   ------------------------------------------------------------ */
.items, .proof, .posts, .timeline{
  list-style: none;
  margin: calc(var(--u) * 1.5) 0 0;
  padding: 0;
}

.items > li, .proof > li{
  padding: calc(var(--u) * 1.25) 0;
  border-top: 1px solid var(--hair);
  max-width: 52rem;
}
.items > li:last-child, .proof > li:last-child{
  border-bottom: 1px solid var(--hair);
}

.items h3, .proof h3{
  font-family: var(--body);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  margin: 0;
  color: var(--text);
}
.items p, .proof p{
  margin: 0.5em 0 0;
  color: var(--muted);
  max-width: var(--measure);
}
.items strong{ color: var(--text); font-weight: 600; }

.links{ font-family: var(--label); font-size: 0.8125rem; }
.links a + a{ margin-left: 1.25em; }

/* Timeline — a real line, because the line is this site's motif. */
.timeline{
  border-left: 1px solid var(--hair);
  padding-left: calc(var(--u) * 1.5);
}
.timeline > li{
  position: relative;
  padding: calc(var(--u) * 0.85) 0;
  display: grid;
  grid-template-columns: 8.5rem 1fr;
  gap: var(--u);
  align-items: baseline;
}
.timeline > li::before{
  content: '';
  position: absolute;
  left: calc(var(--u) * -1.5 - 3px);
  top: calc(var(--u) * 0.85 + 0.55em);
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--amber);
}
.when{
  font-family: var(--label);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--muted);
  white-space: nowrap;
}
.what{ color: var(--text); }
.what strong{ color: var(--amber); font-weight: 500; }

.posts > li{
  display: grid;
  grid-template-columns: 8.5rem 1fr;
  gap: var(--u);
  padding: calc(var(--u) * 0.6) 0;
  border-top: 1px solid var(--hair);
}

/* ------------------------------------------------------------
   Latest run
   ------------------------------------------------------------ */
.run-card{
  border-top: 1px solid var(--hair);
  border-bottom: 1px solid var(--hair);
  padding: calc(var(--u) * 1.25) 0;
  max-width: 40rem;
}
.run-empty{ color: var(--muted); margin: 0; font-family: var(--label); font-size: 0.8125rem; }
.credit{
  font-family: var(--label);
  font-size: 0.6875rem;
  letter-spacing: 0.06em;
  color: var(--muted);
  margin: var(--u) 0 0;
}

/* ------------------------------------------------------------
   Links, buttons, focus
   ------------------------------------------------------------ */
a{ color: var(--cyan); text-decoration: none; }
a:hover{ text-decoration: underline; text-underline-offset: 0.25em; }

:focus-visible{
  outline: 2px solid var(--cyan);
  outline-offset: 3px;
  border-radius: 1px;
}



.btn{
  font-family: var(--label);
  font-size: 0.8125rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--amber);
  border: 0;
  padding: 0.9em 1.6em;
  cursor: pointer;
}
.btn:hover{ text-decoration: none; background: color-mix(in srgb, var(--amber) 85%, white); }
.btn--quiet{
  background: transparent;
  color: var(--text);
  box-shadow: inset 0 0 0 1px var(--hair);
}
.btn--quiet:hover{ background: transparent; color: var(--amber); box-shadow: inset 0 0 0 1px var(--amber); }

/* The social rail. On desktop it runs up the right edge; on a
   phone it becomes an ordinary row, because a vertical rail on a
   375px screen is just a thing in the way. */


/* ------------------------------------------------------------
   Contact form. The wiggling underline lives on :hover only —
   a focused field needs a stable, obvious indicator, and an
   animating one reads as an error state.
   ------------------------------------------------------------ */
.contact{ max-width: 34rem; }
.field{ margin: 0 0 calc(var(--u) * 1.5); }
.field label{
  display: block;
  font-family: var(--label);
  font-size: 0.75rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.6em;
}
.field input, .field textarea{
  width: 100%;
  font: inherit;
  color: var(--text);
  background: transparent;
  border: 0;
  border-bottom: 1px solid var(--hair);
  padding: 0.5em 0;
  resize: vertical;
}
.field input:focus, .field textarea:focus{
  outline: none;
  border-bottom-color: var(--cyan);
}
.or{ color: var(--muted); max-width: var(--measure); margin-top: calc(var(--u) * 2.5); }

footer{
  position: relative;
  z-index: 1;
  padding: calc(var(--u) * 2) var(--gut);
  font-family: var(--label);
  font-size: 0.75rem;
  color: var(--muted);
  max-width: 78rem;
  margin-inline: auto;
}

/* ------------------------------------------------------------
   Reduced motion. Not a courtesy — the research on this was the
   most vocal of anything we read. Everything settles instantly
   and the page becomes a plain document.
   ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce){
  *, *::before, *::after{
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
  .statement{ color: var(--amber); }
  .statement .w{ color: var(--amber); }
  .ch--tall{ min-height: 100svh; }
}

/* ------------------------------------------------------------
   Latest activity. Laid out like the reference card he sent —
   label and date on one line, the activity name, a rule, then the
   three numbers — but on black, and with the route drawn in
   Strava's own orange, which is the thing everyone recognises.
   ------------------------------------------------------------ */
:root{ --track: #FC4C02; }

/* This one is a card label people actually read, not a quiet section
   marker, so it is sized up from the standard eyebrow. */
.eyebrow--strava{
  color: var(--track);
  font-weight: 600;
  font-size: clamp(0.9rem, 1.2vw, 1.15rem);
  letter-spacing: 0.13em;
}
.run-head{
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--u);
  flex-wrap: wrap;
}
.run-head h3{
  font-family: var(--body);
  font-size: clamp(1.35rem, 2vw, 1.75rem);
  font-weight: 600;
  margin: 0;
  color: var(--text);
}
.run-head time{
  font-family: var(--label);
  font-size: 0.75rem;
  letter-spacing: 0.06em;
  color: var(--muted);
}

.run-stats{
  display: flex;
  gap: calc(var(--u) * 2.5);
  flex-wrap: wrap;
  margin: calc(var(--u) * 1.25) 0 0;
}
.run-stats dt{
  font-family: var(--label);
  font-size: 0.6875rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
.run-stats dd{
  margin: 0.3em 0 0;
  font-family: var(--body);
  font-weight: 600;
  font-size: clamp(1.5rem, 2.4vw, 2.1rem);
  line-height: 1;
  color: #FFFFFF;
  font-variant-numeric: tabular-nums;
}
.run-stats dd span{
  font-family: var(--label);
  font-size: 0.8125rem;
  font-weight: 400;
  color: var(--muted);
  margin-left: 0.3em;
}

/* The route. One amber stroke — the same line as the preloader
   figure and the timeline spine. It is his actual GPS trace,
   decoded from Strava's polyline and baked to a path. */
.route{
  display: block;
  width: 100%;
  max-width: 24rem;
  height: auto;
  margin-top: calc(var(--u) * 1.6);
  /* Strava orange, because that is what a route trace reads as. */
  color: var(--track);
  overflow: visible;
  filter: drop-shadow(0 0 14px rgba(252, 76, 2, 0.35));
}

/* ------------------------------------------------------------
   The whip underline. The SVG IS the line — the input keeps no
   border of its own, so there is only ever one stroke there.
   ------------------------------------------------------------ */
.field{ position: relative; }

.field input, .field textarea{
  border-bottom: 0;
  padding-bottom: 0.65em;
}

.whip{
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 14px;
  display: block;
  color: var(--hair);
  pointer-events: none;
  overflow: visible;
  transition: color 160ms ease;
}
/* Focus is a colour change, never a movement: a field that wobbles
   while you are typing in it reads as an error. */
.field:focus-within .whip{ color: var(--cyan); }
.field:hover .whip{ color: color-mix(in srgb, var(--amber) 70%, transparent); }

@media (prefers-reduced-motion: reduce){
  .whip{ transition: none; }
}


/* ------------------------------------------------------------
   THE SHELF — what he is taking in.

   Two tiles at deliberately different shapes: a 16:9 podcast still
   and a portrait book cover, because that IS what those two things
   look like. Forcing them to a shared aspect ratio would make both
   look like neither.
   ------------------------------------------------------------ */
.shelf{
  display: grid;
  grid-template-columns: minmax(0, 1.85fr) minmax(0, 1fr);
  gap: clamp(1.25rem, 3vw, 2.75rem);
  align-items: start;
  max-width: 66rem;
  margin-top: calc(var(--u) * 1.5);
}

/* The tiles keep their FULL size and are pushed to the right of the
   station instead — shrinking them to make room was the wrong trade,
   since they are the content here. On a screen too narrow to fit both
   them and him, they simply reach him and he peeks out from behind,
   which is the better accident anyway. */
/* THE SECTION HAD TO GO FULL-BLEED FIRST.
   Every .ch is capped at 78rem and centred, so on a wide screen there
   is dead space either side that the shelf could never reach — it was
   right-aligning inside a 1248px box, not inside the window. He was
   standing in that dead margin, which is exactly where the thumbnail
   kept landing. Releasing the cap here is what actually moves the
   tiles off him. */
@media (min-width: 62rem){
  #inputs{
    max-width: none;
    width: 100%;
  }
  #inputs .shelf,
  #inputs > .eyebrow{
    margin-left: auto;
    margin-right: 0;
  }
}
@media (max-width: 46rem){
  .shelf{ grid-template-columns: 1fr; max-width: 26rem; }
}

.tile{ margin: 0; }

.tile-media{
  display: block;
  position: relative;
  overflow: hidden;
  background: var(--ink-2);
  box-shadow: inset 0 0 0 1px var(--hair);
}
.tile-media img{
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 700ms cubic-bezier(.2,.8,.2,1),
              filter 400ms ease;
}
.tile--wide .tile-media{ aspect-ratio: 16 / 9; }
.tile--tall .tile-media{ aspect-ratio: 2 / 3; }

/* The water is a real WebGL layer lifted from DUAT (js/ripple.js); it
   mounts its own canvas over the still. All this needs to do is host it
   and keep the still underneath as the accessible, always-correct copy. */
.tile-media canvas{
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

.tile figcaption{
  display: block;
  margin-top: calc(var(--u) * 0.85);
}
.tile-kind{
  display: block;
  font-family: var(--label);
  font-size: clamp(0.9rem, 1.2vw, 1.15rem);
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Announced and linkable, never seen. clip-path rather than
   display:none, which would take it out of the accessibility tree. */
.visually-hidden{
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden;
  white-space: nowrap;
}
.tile-title{
  display: block;
  margin-top: 0.35em;
  font-size: clamp(1.05rem, 1.4vw, 1.3rem);
  font-weight: 500;
  letter-spacing: 0.005em;
  color: #FFFFFF;
}
a.tile-media:hover{ text-decoration: none; }

/* Reduced motion never attaches the canvas at all, so the still is
   simply the whole of it. */
