/*
 * Erasmus.works
 *
 * Fonts are self-hosted so the page makes no third-party requests.
 *   JetBrains Mono  (c) JetBrains  - SIL Open Font License 1.1
 *   IBM Plex Sans   (c) IBM Corp.  - SIL Open Font License 1.1
 * Both files are the Google Fonts latin subset. See OFL.txt.
 */

@font-face {
  font-family: "JetBrains Mono";
  src: url("/JetBrainsMono-Variable.woff2") format("woff2");
  font-weight: 400 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "IBM Plex Sans";
  src: url("/IBMPlexSans-Regular.woff2") format("woff2");
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

:root {
  --ink: #19191c;
  --panel: #27282c;
  --sunk: #1f2023;
  --line: #323236;
  --paper: #f4f4f4;
  --muted: #cdcdcd;
  --dim: #83858f;
  --blue: #167dff;
  --sky: #4ca6ff;
  --green: #00d886;
  --violet: #7256ff;
  --pink: #ff2d90;
  --yellow: #f0eb18;
  --orange: #ff8100;
  --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --sans: "IBM Plex Sans", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  --wrap: 1120px;
}

* { box-sizing: border-box; }

html {
  /* iOS otherwise inflates text on rotation, which reads as an unwanted zoom */
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

/* the masthead is sticky, so anchored sections need to clear it */
[id] { scroll-margin-top: 78px; }

body {
  margin: 0;
  overflow-x: clip;
  overflow-wrap: break-word;
  background: var(--ink);
  color: var(--paper);
  font-family: var(--sans);
  font-size: 17px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: clamp(20px, 5vw, 48px);
}

a { color: var(--sky); text-decoration-color: rgba(76, 166, 255, 0.4); text-underline-offset: 3px; }
/* Touch browsers latch :hover onto whatever was tapped last and hold it until
   something else is tapped, so every hover state here is gated on a real pointer. */
@media (hover: hover) { a:hover { color: var(--paper); text-decoration-color: var(--paper); } }
:focus-visible { outline: 2px solid var(--sky); outline-offset: 3px; }

h1, h2, h3 {
  font-family: var(--mono);
  font-weight: 800;
  letter-spacing: -0.045em;
  /* the mono space keeps a full character cell while the negative tracking
     pulls the letters together, so word gaps end up reading ten times wider
     than letter gaps. pull the spaces back in to match. */
  word-spacing: -0.2em;
  line-height: 0.98;
  text-wrap: balance;
  margin: 0;
}

/* mono sets every glyph on the same advance, so a display-size h1 needs
   tighter tracking than h2/h3 to stop reading as loose. the tighter tracking
   also needs less taken off the word gaps than the -0.2em above. */
h1 { font-size: clamp(2.3rem, 5.6vw, 4rem); letter-spacing: -0.06em; word-spacing: -0.12em; }
h2 { font-size: clamp(1.9rem, 4.2vw, 3.1rem); }
h3 { font-size: 1.05rem; letter-spacing: -0.02em; }

p { margin: 0; }

code { font-family: var(--mono); font-size: 0.92em; overflow-wrap: anywhere; }

.lede { color: var(--muted); max-width: 60ch; font-size: clamp(1rem, 1.4vw, 1.15rem); }

.eyebrow {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--mono);
  font-size: 0.75rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--dim);
}
.eyebrow::before {
  content: "";
  width: 9px;
  height: 9px;
  background: var(--tick, var(--blue));
  flex: none;
}

/* ---------- masthead ---------- */

.masthead {
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: rgba(25, 25, 28, 0.88);
  backdrop-filter: blur(10px);
  z-index: 10;
}
.masthead .wrap {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  height: 62px;
}
.brand {
  display: flex;
  align-items: center;
  gap: 0;
  font-family: var(--mono);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.03em;
  color: var(--paper);
  text-decoration: none;
}
.brand-name span { color: var(--dim); }

.navlinks { display: flex; align-items: stretch; gap: clamp(14px, 3vw, 32px); font-family: var(--mono); font-size: 0.82rem; }
.navlinks a { display: flex; align-items: center; color: var(--muted); text-decoration: none; }
@media (hover: hover) { .navlinks a:hover { color: var(--sky); } }

/* The slot holds the mark's space open from the start, so the name sits where
   it will always sit and never moves. The mark then fades up in that space in
   step with the scroll: --markfade tracks how much of the hero lockup has gone
   behind the bar, so the two trade places gradually. Opacity only - no motion,
   no layout, and no threshold to flicker on. */
.brand-slot {
  display: block;
  flex: none;
  width: 62px;
}
.brand-mark {
  display: block;
  height: 34px;
  width: auto;
  margin-right: 10px;
  opacity: var(--markfade, 0);
}
/* the menu button and its scrim are mobile only - see the 880px breakpoint */
.navtoggle { display: none; }
.navscrim { display: none; }

/* ---------- hero ---------- */

.hero { padding-block: clamp(48px, 8vw, 96px) clamp(56px, 8vw, 104px); }
.hero-grid {
  display: grid;
  justify-items: center;
  text-align: center;
}
.hero h1 { margin-top: 22px; }
.hero .lede { margin-top: 22px; }
.logo {
  width: 100%;
  max-width: 340px;
  height: auto;
  display: block;
  margin-bottom: clamp(28px, 4vw, 44px);
}

/* ---------- sections ---------- */

.sec { border-top: 1px solid var(--line); padding-block: clamp(64px, 9vw, 120px); }
.sec-head {
  display: grid;
  justify-items: center;
  text-align: center;
}
.sec-head h2 { margin-top: 18px; }
.sec-head .lede { margin-top: 20px; }

/* ---------- logo tiles ---------- */

.tiles { list-style: none; margin: 0; padding: 0; }

/* labelled wall */
.tiles-wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(152px, 1fr));
  gap: clamp(22px, 2.8vw, 32px) 16px;
  margin-top: clamp(28px, 4vw, 40px);
}

.tile { display: grid; gap: 2px; justify-items: start; align-content: start; }

.tile-mark {
  position: relative;
  width: 46px;
  height: 46px;
  display: grid;
  place-items: center;
}
.tiles-wall .tile-mark { margin-bottom: 14px; }

/* the section's own mark: half the hero logo's width at every size.
   the hero logo is min(100%, 340px), so min(50%, 170px) tracks it exactly.
   must sit after .tile-mark to override its 46px box. */
.sec-mark {
  width: min(50%, 170px);
  height: auto;
  margin-bottom: clamp(24px, 3vw, 34px);
}
.tile-mark::after {
  content: "";
  position: absolute;
  inset: 0;
  background-position: center;
  background-repeat: no-repeat;
  background-size: contain;
}
.tile-name {
  font-family: var(--mono);
  font-size: 0.84rem;
  font-weight: 700;
  letter-spacing: -0.025em;
  color: var(--paper);
}
.tile-role { font-family: var(--mono); font-size: 0.72rem; line-height: 1.45; color: var(--dim); }

.wall-head { margin-top: clamp(48px, 6vw, 76px); font-size: 1.3rem; text-align: center; }

/* ---------- carousel ---------- */

/* Modelled on the Junie carousel: slides narrower than the track and snapped to
   its centre, so the neighbours peek in at both edges, and the live card stands
   a little larger than the two beside it. Touch swipe, momentum and snapping are
   the browser's; the JS adds mouse dragging and drives the arrows. */

/* the carousel breaks out of the page column and runs the full width of the
   viewport, so the cards beside the live one bleed off both edges */
.carousel {
  margin-top: clamp(30px, 4vw, 48px);
  margin-inline: calc(50% - 50vw);
}

.carousel-track {
  display: flex;
  align-items: stretch;
  gap: clamp(14px, 2vw, 32px);
  /* the same gutter the page column uses, so the first and last card come to
     rest in line with the heading above them */
  padding-inline: clamp(20px, 5vw, 48px);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  cursor: grab;
  /* the cards scale up to full size, which would otherwise clip at the edges */
  padding-block: 26px;
}
.carousel-track::-webkit-scrollbar { display: none; }
.carousel-track.is-dragging { cursor: grabbing; user-select: none; }
.carousel-track.is-dragging,
.carousel-track.is-settling { scroll-snap-type: none; }
/* the 700ms settle is right when a card arrives, but during a drag it lags the
   pointer, which reads as the track sticking */
.carousel-track.is-dragging .carousel-slide { transition: none; }

.carousel-slide {
  flex: 0 0 42%;
  min-width: 0;
  scroll-snap-align: center;
  --rest: 0.87;
  transform: scale(var(--rest));
  transform-origin: center;
  transition: transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
/* With the script running the scale follows the scroll position frame by frame,
   so there is nothing left to transition - and a transition here would only lag
   the pointer during a drag. Without it the two-size rules below still stand. */
.carousel-track.is-live .carousel-slide { transition: none; }
.carousel-slide.is-current { transform: none; }
.carousel-slide:not(.is-current) { cursor: pointer; }

.carousel-card {
  display: flex;
  flex-direction: column;
  height: 100%;
  padding: clamp(20px, 2.6vw, 48px);
  border-radius: clamp(24px, 3.4vw, 48px);
  background: var(--panel);
  text-align: center;
}

.carousel-head { display: flex; flex-direction: column; }
.carousel-head h3 {
  font-size: clamp(1.5rem, 2.9vw, 2.2rem);
  letter-spacing: -0.04em;
  line-height: 1.05;
}
.carousel-head p {
  margin-top: 16px;
  margin-inline: auto;
  max-width: 44ch;
  color: var(--muted);
  font-size: clamp(0.9rem, 1.1vw, 1rem);
  line-height: 1.5;
}
.carousel-head a { color: var(--paper); text-decoration-color: rgba(244, 244, 244, 0.4); text-underline-offset: 4px; }
@media (hover: hover) { .carousel-head a:hover { text-decoration-color: var(--paper); } }

/* one panel of the same proportion on every card, pushed to the bottom so the
   three line up however long the description runs */
.carousel-media {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: auto;
  padding-top: clamp(20px, 3vw, 32px);
}
.carousel-media > * {
  aspect-ratio: 508 / 330;
  width: 100%;
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: clamp(12px, 1.4vw, 16px);
  background: #1e2022;
  overflow: hidden;
}
/* a single mark over its role, for a card that is about one app */
.carousel-kind {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}
/* the role sits above the name, so the panel below is free for a screenshot */
.carousel-head .carousel-kind { margin-bottom: 12px; }

/* every card wears its own mark, whether or not it has a screenshot below.
   Height is fixed and width follows each logo's proportions, so a lockup
   carrying a wordmark gets the room it needs instead of being letterboxed. */
.carousel-icon {
  align-self: center;
  width: auto;
  height: clamp(34px, 3.4vw, 46px);
  max-width: 70%;
  margin-bottom: clamp(14px, 1.6vw, 20px);
}

/* a screenshot fills the panel edge to edge */
.carousel-shot {
  display: block;
  -webkit-user-drag: none;
  user-select: none;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top center;
}


.carousel-nav {
  display: flex;
  justify-content: center;
  gap: 12px;
  margin-top: clamp(20px, 3vw, 34px);
}
.carousel-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: none;
  color: var(--accent, var(--paper));
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, opacity 0.15s ease;
}
.carousel-arrow svg { width: 22px; height: 22px; }
@media (hover: hover) { .carousel-arrow:hover:not(:disabled) { border-color: currentColor; } }

/* ---------- rack ---------- */

/* The photo and the list are one control: hovering a row dims the photo down to
   that band, so "what is in it" is answered on the picture rather than beside it.
   The band's position comes from --top/--h on the row, which the script copies
   onto the band. */

.rack {
  display: grid;
  grid-template-columns: minmax(260px, 400px) minmax(0, 1fr);
  gap: clamp(24px, 4vw, 52px);
  align-items: start;
  margin-top: clamp(36px, 5vw, 56px);
}

.rack-photo {
  margin: 0;
  padding: 12px;
  background: var(--sunk);
  border: 1px solid var(--line);
  border-radius: 4px;
  position: sticky;
  top: 96px;
}
.rack-frame { position: relative; display: block; overflow: hidden; border-radius: 3px; }
.rack-frame img { width: 100%; height: auto; display: block; }
.rack-photo figcaption {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--dim);
  text-align: center;
}

.rack-band {
  position: absolute;
  left: 0;
  right: 0;
  top: var(--top, 0);
  height: var(--h, 0);
  pointer-events: none;
  opacity: 0;
  border-block: 1px solid var(--accent);
  background: rgba(255, 129, 0, 0.12);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  /* the spread is what dims everything outside the band */
  box-shadow: 0 0 0 2000px rgba(25, 25, 28, 0.66);
  transition: opacity 0.18s ease,
              top 0.28s cubic-bezier(0.16, 1, 0.3, 1),
              height 0.28s cubic-bezier(0.16, 1, 0.3, 1);
}
.rack.is-lit .rack-band { opacity: 1; }

.rack-head { margin: 0 0 18px; font-size: 1.3rem; }

.rack-map { list-style: none; margin: 0; padding: 0; }
.rack-row {
  display: grid;
  grid-template-columns: 52px minmax(0, 1fr);
  gap: 2px 14px;
  padding: 13px 12px;
  border-top: 1px solid var(--line);
  border-left: 2px solid transparent;
  transition: background 0.15s ease, border-left-color 0.15s ease;
}
.rack-row:last-child { border-bottom: 1px solid var(--line); }
.rack-row.is-lit { background: var(--sunk); border-left-color: var(--accent); }

.rack-u {
  font-family: var(--mono);
  font-size: 0.72rem;
  line-height: 1.7;
  color: var(--dim);
  transition: color 0.15s ease;
}
.rack-row.is-lit .rack-u { color: var(--accent); }
.rack-what { font-weight: 600; letter-spacing: -0.01em; }
.rack-note {
  grid-column: 2;
  font-family: var(--mono);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--dim);
}

/* The same two columns as the block above, mirrored: the rack photo is shot from
   the front, so putting the back of it on the other side keeps the pair reading
   as one object turned round. No band here - there is nothing to point at. */
.power {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(260px, 400px);
  gap: clamp(24px, 4vw, 52px);
  align-items: start;
  margin-top: clamp(36px, 5vw, 56px);
}

.power-photo {
  margin: 0;
  padding: 12px;
  background: var(--sunk);
  border: 1px solid var(--line);
  border-radius: 4px;
}
.power-frame { display: block; overflow: hidden; border-radius: 3px; }
.power-frame img { width: 100%; height: auto; display: block; }
.power-photo figcaption {
  margin-top: 12px;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  color: var(--dim);
  text-align: center;
}

.power-lede { margin: 0 0 20px; color: var(--muted); max-width: 54ch; }

.power-facts { list-style: none; margin: 0; padding: 0; }
.power-fact {
  padding: 13px 12px;
  border-top: 1px solid var(--line);
  border-left: 2px solid transparent;
}
.power-fact:last-child { border-bottom: 1px solid var(--line); }
.power-what { display: block; font-weight: 600; letter-spacing: -0.01em; }
.power-note {
  display: block;
  margin-top: 2px;
  font-family: var(--mono);
  font-size: 0.72rem;
  line-height: 1.5;
  color: var(--dim);
}

.power-go { margin-top: 20px; font-family: var(--mono); font-size: 0.8rem; }
.power-go a { color: var(--paper); text-decoration-color: rgba(244, 244, 244, 0.35); text-underline-offset: 4px; }
@media (hover: hover) { .power-go a:hover { color: var(--accent); text-decoration-color: currentColor; } }

.rack-specs {
  list-style: none;
  margin: clamp(30px, 4vw, 44px) 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 0;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--dim);
}
.rack-specs li { padding-inline: clamp(10px, 1.6vw, 18px); border-right: 1px solid var(--line); }
.rack-specs li:last-child { border-right: 0; }

/* the two Star buttons in the panels are the target; this is the nudge towards
   them, and not a link itself - there are two repositories, so a single href
   would pick a winner. A rule rather than a box: the page draws every division
   with a 1px line, so a floating container would be the only one. Both closing
   lines sit under that one rule, stacked at every width. The rule is top-only,
   the way sections are - a closing rule too would fence off the section's
   bottom padding as an empty box above the footer's own border. */
.source-close {
  display: grid;
  justify-items: center;
  gap: 12px;
  margin-top: clamp(30px, 4vw, 44px);
  padding-block: clamp(48px, 6vw, 84px);
  border-top: 1px solid var(--line);
}
/* the band supplies its own closing space, so the section must not add a
   second helping under it - that is what left the lines hugging the rule */
#source { padding-bottom: 0; }
.source-ask {
  display: flex;
  align-items: center;
  gap: 12px;
  font-family: var(--mono);
  font-size: 1rem;
  letter-spacing: -0.01em;
  color: var(--paper);
}
/* the colour is the star alone, the way the repo Star buttons carry it */
.star-glyph {
  width: 24px;
  height: 24px;
  flex: none;
  color: var(--yellow);
  animation: starpulse 3.2s ease-in-out infinite;
}
@keyframes starpulse {
  50% { transform: scale(1.09); }
}

.rack-cta { margin-top: 20px; text-align: center; font-family: var(--mono); font-size: 0.85rem; }
.rack-cta a {
  display: inline-block;
  padding-block: 4px;
  color: var(--paper);
  text-decoration-color: rgba(244, 244, 244, 0.4);
  text-underline-offset: 4px;
}
@media (hover: hover) { .rack-cta a:hover { color: var(--orange); text-decoration-color: currentColor; } }

/* ---------- sources ---------- */

/* Two repos will not fill a carousel - that pattern wants three or more before
   the peeking neighbours mean anything. This is the TeamCity feature-row layout
   instead: one full-width panel per repo, text held left, the screenshot running
   off the right edge so the panel reads as a window onto the page rather than a
   framed thumbnail. */

.panels { list-style: none; margin: clamp(36px, 5vw, 56px) 0 0; padding: 0; display: grid; gap: clamp(14px, 1.8vw, 22px); }

.panel {
  display: grid;
  grid-template-columns: minmax(0, 0.82fr) minmax(0, 1.18fr);
  align-items: center;
  gap: clamp(24px, 3vw, 44px);
  padding: clamp(28px, 3.6vw, 52px);
  /* no padding on the right: the screenshot is meant to reach the edge */
  padding-right: 0;
  border-radius: clamp(22px, 2.6vw, 32px);
  background: var(--panel);
  overflow: hidden;
}

.panel-kind {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--accent);
}
.panel-mark { width: 18px; height: 18px; flex: none; color: var(--paper); }
.panel-mark svg { width: 100%; height: 100%; display: block; }

.panel h3 { font-size: clamp(1.5rem, 2.9vw, 2.2rem); letter-spacing: -0.04em; line-height: 1.05; }
.panel-name { color: inherit; text-decoration: none; }
@media (hover: hover) { .panel-name:hover { color: var(--accent); } }
.panel-what { margin-top: 16px; max-width: 44ch; color: var(--muted); font-size: clamp(0.9rem, 1.1vw, 1rem); line-height: 1.55; }

.panel-go { margin-top: 20px; font-family: var(--mono); font-size: 0.8rem; }
.panel-go a { color: var(--paper); text-decoration-color: rgba(244, 244, 244, 0.35); text-underline-offset: 4px; }
@media (hover: hover) { .panel-go a:hover { color: var(--accent); text-decoration-color: currentColor; } }

/* The shot is wider than the space it is given and anchored left, so its right
   edge runs past the panel and gets clipped. Only the far edge of the GitHub
   sidebar is lost, and the panel stops looking like a framed thumbnail. */
.panel-shot {
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-right: 0;
  border-radius: clamp(10px, 1.2vw, 14px) 0 0 clamp(10px, 1.2vw, 14px);
  background: #1e2022;
  overflow: hidden;
}
.panel-shotlink { display: block; }
.panel-shot img { display: block; width: 112%; max-width: none; height: auto; transition: opacity 0.15s ease; }
@media (hover: hover) { .panel-shotlink:hover img { opacity: 0.82; } }

.source-facts {
  list-style: none;
  margin: 22px 0 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-family: var(--mono);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--dim);
}
.source-facts > li { padding: 4px 9px; border: 1px solid var(--line); border-radius: 2px; }
/* the star sits in the chip row but is a link, so it carries its own box */
.source-starwrap { padding: 0 !important; border: 0 !important; }

.source-star {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: 2px;
  color: var(--dim);
  text-decoration: none;
  transition: color 0.15s ease, border-color 0.15s ease;
}
.source-star-mark { width: 12px; height: 12px; flex: none; }
.source-star-count { padding-left: 8px; border-left: 1px solid var(--line); }
@media (hover: hover) {
  .source-star:hover { color: var(--yellow); border-color: var(--yellow); }
  .source-star:hover .source-star-mark { fill: var(--yellow); }
  .source-star:hover .source-star-count { border-left-color: var(--yellow); }
}
/* the flex above would otherwise beat [hidden] and leave an empty divider */
.source-star-count[hidden] { display: none; }

.repo-cta { text-align: center; font-family: var(--mono); font-size: 0.85rem; }
.repo-cta a {
  display: inline-block;
  padding-block: 4px;
  color: var(--paper);
  text-decoration-color: rgba(244, 244, 244, 0.4);
  text-underline-offset: 4px;
}
@media (hover: hover) { .repo-cta a:hover { color: var(--violet); text-decoration-color: currentColor; } }

footer { border-top: 1px solid var(--line); padding-block: clamp(48px, 6vw, 72px); }
.foot-grid { display: grid; justify-items: center; text-align: center; gap: 30px; }
.foot-name span { color: var(--dim); }
.foot-name { font-family: var(--mono); font-weight: 800; font-size: 1.15rem; letter-spacing: -0.04em; }
.foot-bio { margin-top: 10px; color: var(--dim); max-width: 44ch; font-size: 0.92rem; }
.foot-links { list-style: none; margin: 0; padding: 0; display: grid; justify-items: center; gap: 10px; font-family: var(--mono); font-size: 0.85rem; }
.foot-links a { display: inline-block; padding-block: 4px; text-decoration: none; }
@media (hover: hover) { .foot-links a:hover { text-decoration: underline; } }

/* ---------- motion ---------- */

.rise { opacity: 0; transform: translateY(14px); animation: rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) forwards; }
.rise:nth-child(2) { animation-delay: 0.06s; }
.rise:nth-child(3) { animation-delay: 0.12s; }
.rise:nth-child(4) { animation-delay: 0.18s; }
@keyframes rise { to { opacity: 1; transform: none; } }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .rise { animation: none; opacity: 1; transform: none; }
  .star-glyph { animation: none; }
  * { transition-duration: 0.01ms !important; }
}

/* ---------- responsive ---------- */

@media (max-width: 880px) {
  /* the sticky bar keeps a solid ground here: backdrop-filter would make it a
     containing block and the fixed drawer would resolve against it, not the viewport */
  .masthead { backdrop-filter: none; background: var(--ink); }

  /* A held-open 62px reads as a gap on a bar this narrow - it looks like an
     image that failed to load. So on a phone the space is not reserved: the
     slot widens over the first half of the fade and the mark appears in it
     over the second. At the top there is no gap, the mark is never caught
     half-clipped, and both halves are tied to the scroll rather than animated,
     so the name drifts by about a tenth of a pixel per pixel scrolled. */
  .brand-slot { width: calc(62px * clamp(0, calc(var(--markfade, 0) * 2), 1)); }
  .brand-mark { opacity: clamp(0, calc((var(--markfade, 0) - 0.5) * 2), 1); }

  /* A phone screen is too short to give the bar 62px permanently, so it moves
     with the scroll: off on the way down, back on the way up, always in step
     with the finger rather than animating on a threshold. The offset is set
     inline by the script - no transition here, it would lag the scroll, and no
     will-change: transform, which would make the bar a containing block for
     the fixed drawer exactly as backdrop-filter would.

     The drawer is the reason for the !important: while it is open the bar must
     carry no transform at all, and this has to beat the inline one. */
  body.nav-open .masthead { transform: none !important; }

  .navtoggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    position: relative;
    z-index: 30;
    width: 44px;
    height: 44px;
    margin-right: -12px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--paper);
    cursor: pointer;
  }
  .navtoggle-box, .navtoggle-box::before, .navtoggle-box::after {
    display: block;
    width: 20px;
    height: 1.5px;
    background: currentColor;
    transition: transform 0.2s ease, background-color 0.2s ease, top 0.2s ease;
  }
  .navtoggle-box { position: relative; }
  .navtoggle-box::before, .navtoggle-box::after { content: ""; position: absolute; left: 0; }
  .navtoggle-box::before { top: -6px; }
  .navtoggle-box::after { top: 6px; }
  /* the button stays above the scrim and becomes the close control */
  body.nav-open .navtoggle-box { background: transparent; }
  body.nav-open .navtoggle-box::before { top: 0; transform: rotate(45deg); }
  body.nav-open .navtoggle-box::after { top: 0; transform: rotate(-45deg); }

  .navlinks {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    width: min(66vw, 280px);
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 68px 24px 24px;
    background: var(--panel);
    border-left: 1px solid var(--line);
    box-shadow: 0 6px 32px rgba(0, 0, 0, 0.55);
    font-size: 0.82rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    transform: translateX(100%);
    visibility: hidden;
    transition: transform 0.2s ease, visibility 0.2s ease;
  }
  body.nav-open .navlinks { transform: none; visibility: visible; }
  .navlinks a { min-height: 48px; border-bottom: 1px solid var(--line); }
  .navlinks a:last-child { border-bottom: 0; }

  .navscrim { display: block; position: fixed; inset: 0; z-index: 10; background: rgba(0, 0, 0, 0.5); opacity: 0; visibility: hidden; transition: opacity 0.2s ease, visibility 0.2s ease; }
  body.nav-open .navscrim { opacity: 1; visibility: visible; }
  body.nav-open { overflow: hidden; }


  .panel { grid-template-columns: minmax(0, 1fr); padding-right: clamp(28px, 3.6vw, 52px); gap: clamp(22px, 3vw, 30px); }
  .panel-shot { border-right: 1px solid rgba(255, 255, 255, 0.1); border-radius: clamp(10px, 1.2vw, 14px); }
  .panel-shot img { width: 100%; }

  /* the card stacks and the peek narrows so the text still gets a full column */
  /* the slide widens as the viewport narrows, so one stays readable while the
     neighbours still show at the edges */
  .carousel-slide { flex-basis: 62%; }

  /* stacked, the photo and the list are no longer side by side, so the band
     would light up off screen - the list carries itself from here down */
  .rack { grid-template-columns: minmax(0, 1fr); justify-items: center; }
  .rack-photo { position: static; max-width: 320px; }
  .rack-side { width: 100%; }
  .rack-band { display: none; }

  /* stacked, the mirror is gone, so the photo goes back on top the way the
     block above it stacks */
  .power { grid-template-columns: minmax(0, 1fr); justify-items: center; }
  .power-photo { max-width: 320px; order: -1; }
  .power-side { width: 100%; }
}

@media (max-width: 620px) {
  .hero { padding-block: 34px 44px; }
  .hero .lede { margin-top: 16px; }
  /* the lockup has to share the first screen with the headline here */
  .logo { max-width: 230px; margin-bottom: clamp(20px, 5vw, 28px); }
  .lede { font-size: 1rem; }

  .sec { padding-block: 54px; }

  .carousel-slide { flex-basis: 84%; --rest: 0.92; }
  .carousel-track { padding-block: 16px; }
  .carousel-arrow { width: 46px; height: 46px; }

  /* stacked tiles cost ~120px a row, and 18 of them make the wall three phone
     screens tall. The row form keeps every name and role, at about half that. */
  .tiles-wall { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 14px 10px; }
  .tiles-wall .tile {
    grid-template-columns: 26px minmax(0, 1fr);
    column-gap: 8px;
    align-items: start;
  }
  .tiles-wall .tile-mark { width: 26px; height: 26px; margin-bottom: 0; grid-row: span 2; }
  .tile-name { font-size: 0.78rem; }
  .tile-role { font-size: 0.66rem; line-height: 1.35; }

  .source-ask { gap: 10px; font-size: 0.85rem; }
  .star-glyph { width: 20px; height: 20px; }

  /* the full repo URL is one character too wide to hold a single line here */
  .panel-go { font-size: 0.72rem; }
  .panel { padding: clamp(22px, 5vw, 30px); }

  .rack-photo { padding: 10px; max-width: 260px; }
  /* the same squeeze as the tile wall: a narrower U cell, less padding and a
     tighter note, so eight rows read as a list rather than a second screen */
  .rack-head { margin-bottom: 12px; }
  .rack-row {
    grid-template-columns: 38px minmax(0, 1fr);
    gap: 1px 10px;
    padding: 9px 6px;
  }
  .rack-u { font-size: 0.68rem; line-height: 1.5; }
  .rack-what { font-size: 0.94rem; line-height: 1.3; }
  .rack-note { font-size: 0.66rem; line-height: 1.35; }

  .power-photo { padding: 10px; max-width: 260px; }
  .power-fact { padding: 9px 6px; }
  .power-what { font-size: 0.94rem; line-height: 1.3; }
  .power-note { font-size: 0.66rem; line-height: 1.35; }
  .power-go { font-size: 0.72rem; }

  .foot-grid { gap: 26px; }
}

/* small phones: keep everything inside the viewport without shrinking to fit */
@media (max-width: 400px) {
  .tiles-wall { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .carousel-slide { flex-basis: 88%; }
}
