/* ═══════════════════════════════════════════════════════════
   mahfuzmaruf.me — LAYOUT & COMPONENTS
   Tokens live in tokens.css and must be loaded first.
   Do not hardcode a colour, size or duration here; if a value is
   needed twice, add it to tokens.css instead.
   ═══════════════════════════════════════════════════════════ */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  min-height: 100vh;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }

.sr-only {
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* ═══ HEADER ═══════════════════════════════════════════════ */

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  height: var(--header-h);
  padding-inline: var(--gutter);
  background: var(--bg);
}

.brand {
  font-size: var(--fs-title);
  font-weight: 700;
  letter-spacing: -.01em;
  line-height: 1;
}

.header-nav {
  display: flex;
  gap: var(--s4);
  font-size: var(--fs-nav);
  font-weight: 500;
}

.header-nav a { color: var(--ink-mute); transition: color .18s ease; }
.header-nav a:hover { color: var(--ink); }

/* ═══ SHELL ════════════════════════════════════════════════
   Main : rail  =  3 : 1   (75% / 25%)                       */

.container {
  display: grid;
  grid-template-columns: var(--col-main) 1fr;
  gap: 0;
  min-height: calc(100vh - var(--header-h));
  padding-right: var(--s2);
}

.main {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Same ceiling as the rail. Without a definite height a panel that
     scrolls internally (see .main-skills) has nothing to measure
     against and grows the page instead. */
  max-height: calc(100dvh - var(--header-h));
  min-height: 0;
  padding: var(--s4) var(--s4) var(--s3);
  background: var(--surface);
  border-radius: var(--radius);
}

.rail {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  /* A definite ceiling. Without it `flex: 1` on .messages has nothing
     to measure against, so a growing thread stretches the rail and
     pushes the page instead of scrolling inside it. min-height:0 lets
     the flex children actually shrink to fit. */
  max-height: calc(100dvh - var(--header-h));
  min-height: 0;
  /* Lets .thought-grid respond to the rail's own width rather than
     the viewport's — the rail is ~400px on a 13" laptop and ~600px
     on a large display, which is what actually decides the layout. */
  container-type: inline-size;
}

/* One inset governs the hero, and .thinking repeats it. Everything
   in the rail therefore starts on the same left edge. */
.rail-body {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: var(--s5) var(--pad-section) 0;
}

/* ═══ CARDS ════════════════════════════════════════════════
   Screen aspect = 1 : √φ. Both cards share one grid track,
   so their captions land on the same baseline.              */

.cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s4);
  width: 100%;
  /* Was 950px — the Figma's 458×2 + gap. That froze the cards at the
     mock's size and left the panel half empty on a larger screen.
     The height budget below is the real governor; this is just a
     ceiling so the cards never get absurd on an ultrawide. */
  max-width: 1240px;
  /* auto on every side: centres in the leftover space without
     taking the dock out of flow, so the two can never overlap. */
  margin: auto;
}

.card { margin: 0; }

.screen {
  position: relative;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  aspect-ratio: 1 / var(--phi-half);
  /* Sized by whichever axis is scarcer: the grid track, or the
     height left after --card-chrome. Both caps are widths, so
     aspect-ratio keeps the shape — the card never distorts, and
     the two-column view never needs a scrollbar.
     100% resolves to 458px once .cards hits its 950px max.
     The * .855 pulls both cards in — 10% then a further 5%
     (.9 * .95) — keeping them centred in their tracks
     (margin-inline: auto) rather than left-anchored. */
  width: calc(min(100%, (100vh  - var(--card-chrome)) / var(--phi-half)) * .855);
  width: calc(min(100%, (100dvh - var(--card-chrome)) / var(--phi-half)) * .855);
  margin-inline: auto;
  padding: var(--s3);
  background: var(--screen);
  /* Rounded to match the dock's 15px; overflow: hidden clips the
     map tiles to the same corner. */
  border-radius: 15px;
}

/* ── Map ──
   Leaflet fills the screen and sits behind the nav bar. Configure
   coordinates and zoom in js/maps.js, not here. */

.map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  /* shown while tiles load, and if the network is unavailable */
  background: var(--screen);
  z-index: 0;
}

/* Leaflet ships its own font stack and link colours — bring both
   back in line with the rest of the page. */
.map.leaflet-container {
  font-family: var(--font-sans);
  background: var(--screen);
}

.map .leaflet-control-attribution {
  padding: 2px 6px;
  background: rgba(255, 255, 255, .8);
  font-size: 10px;
  line-height: 1.4;
  color: var(--ink-mute);
  /* clears the floating nav bar, which is inset --s3 from the bottom */
  margin-bottom: 0;
}
.map .leaflet-control-attribution a { color: var(--ink-mute); text-decoration: underline; }

/* ── Country names ──
   Drawn by us, because no raster variant shows countries while
   hiding divisions and cities. Both classes are Leaflet divIcons,
   so they sit at real coordinates and move with the map. */

.map-country,
.map-neighbour {
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  line-height: 1.2;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
  pointer-events: none;
  /* the tiles beneath are pale, so a soft halo keeps the text legible
     without needing a box around it */
  text-shadow:
     0 0 3px var(--surface), 0 0 3px var(--surface),
     0 0 6px var(--surface);
}

.map-country {
  font-size: var(--fs-nav);
  font-weight: 700;
  color: var(--ink);
}

.map-neighbour {
  font-size: var(--fs-micro);
  font-weight: 500;
  color: var(--ink-mute);
}

/* ── Place markers ──
   Every place on a card renders a marker. The circular pin styling
   sits on the inner .pin, never on the marker root: Leaflet positions
   markers with an inline transform, so transforming the root would
   fight it and send the marker to the wrong coordinates. */

.place-marker { cursor: pointer; }

.place-marker .pin {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  background: var(--accent);
  border: 2.5px solid #fff;
  border-radius: 50%;
  box-shadow: var(--shadow-md);
  transition: transform .18s cubic-bezier(.34, 1.4, .64, 1);
}

.place-marker .pin svg { width: 15px; height: 15px; fill: #fff; }

.place-marker:hover .pin,
.place-marker:focus-visible .pin { transform: scale(1.14); }

.place-marker.is-selected .pin {
  transform: scale(1.2);
  box-shadow: 0 2px 9px rgba(11, 27, 58, .5);
}

.place-marker:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 50%;
}

/* Fallback pulsing dot, for a place with no icon set. */
.pin-dot { background: none !important; border: 0 !important; box-shadow: none !important; }
.pin-dot .core,
.pin-dot .pulse { position: absolute; inset: 0; border-radius: 50%; }
.pin-dot .core {
  inset: 8px;
  background: var(--accent);
  border: 2.5px solid #fff;
  box-shadow: 0 1px 4px rgba(11, 27, 58, .45);
}
.pin-dot .pulse {
  inset: 4px;
  background: var(--accent);
  opacity: .3;
  animation: dot-pulse 2.4s ease-out infinite;
}

@keyframes dot-pulse {
  0%   { transform: scale(.5); opacity: .45; }
  70%  { transform: scale(2.1); opacity: 0; }
  100% { transform: scale(2.1); opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .pin-dot .pulse { animation: none; opacity: .22; }
  .place-marker .pin { transition: none; }
}

/* ── Place label ── */

.map .map-label,
.map .map-label.leaflet-tooltip {
  padding: 3px 8px;
  background: var(--surface);
  border: 0;
  border-radius: 6px;
  box-shadow: 0 1px 4px rgba(11, 27, 58, .22);
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
  white-space: nowrap;
}

/* Leaflet draws a callout arrow on tooltips; this label is a plain pill. */
.map .map-label::before { display: none; }

.screen-nav {
  position: relative;
  z-index: 1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: center;
  width: 100%;
  padding: var(--s4) var(--s2);
  background: var(--surface);
  border-radius: var(--radius-sm);
}

.screen-nav-item {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  /* hug the content so the underline matches the label's width
     rather than stretching across the whole grid third */
  justify-self: center;
  gap: 6px;
  /* items are a mix of <a> and <button> — normalise the button */
  padding: 0;
  background: none;
  border: 0;
  font-family: inherit;
  cursor: pointer;
  /* fixed row height: an item with an icon must not make its nav bar
     taller than the icon-less one in the neighbouring card */
  min-height: 14px;
  font-size: var(--fs-nav);
  font-weight: 500;
  line-height: 1;
  color: var(--ink);
  transition: opacity .18s ease;
}

.screen-nav-item:hover { opacity: .55; }

/* Selected: label, icon (stroke="currentColor") and underline all
   take the accent. The rule sits in the nav bar's existing bottom
   padding, so showing it never changes the bar's height. */
.screen-nav-item.is-active { color: var(--accent); opacity: 1; }

.screen-nav-item::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -10px;
  height: 3px;
  background: currentColor;
  border-radius: 2px;
  transform: scaleX(0);
  transform-origin: center;
  opacity: 0;
  transition: transform .42s cubic-bezier(.22, .9, .3, 1),
              opacity   .28s ease;
}

.screen-nav-item.is-active::after {
  transform: scaleX(1);
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .screen-nav-item::after { transition: none; }
}
.screen-nav-item .icon { width: 14px; height: 14px; flex: none; }

.card-caption {
  margin: 0;
  padding-top: var(--s3);
  text-align: center;
}

/* ── Flag bar ──
   Colour abstraction, not the actual flag artwork — Bangladesh's is a
   red disc on green, not a split. Segments are flex:1 so each band is
   equal and the strip is centred on the title regardless of how many
   colours a flag has (2 for BD, 3 for DE). */

.flag {
  display: flex;
  width: 132px;
  height: 12px;
  margin: 0 auto var(--s2);
  overflow: hidden;
  border-radius: 2px;
}

.flag i { flex: 1; }

.flag-bd i:nth-child(1) { background: var(--flag-bd-green); }  /* bottle green */
.flag-bd i:nth-child(2) { background: var(--flag-bd-red); }  /* red          */

.flag-de i:nth-child(1) { background: var(--flag-de-black); }
.flag-de i:nth-child(2) { background: var(--flag-de-red); }
.flag-de i:nth-child(3) { background: var(--flag-de-gold); }

.card-title {
  margin: 0;
  font-size: var(--fs-title);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -.01em;
}

.card-meta {
  margin: var(--s1) 0 0;
  font-size: var(--fs-body);
  font-weight: 300;
  line-height: 1.2;
}

/* ═══ DOCK ═════════════════════════════════════════════════ */

.dock {
  flex: none;
  margin-top: var(--s3);
  display: flex;
  /* Every tile now carries a label and a dot row, so they are all the
     same height and align on their own. */
  align-items: flex-start;
  /* Breathing room: --s2 between tiles, and the bar's own inset one
     Fibonacci step up from that (--s3) so the end tiles are not
     tighter to the edge than they are to each other. */
  gap: var(--s2);
  padding: var(--s2) var(--s3);
  /* A solid light card lifted off the page by a real drop shadow —
     no glass tint. The card is the same value as the page (--bg), so
     the shadow, not a fill contrast, is what separates it. */
  background: var(--bg);
  border-radius: 15px;
  box-shadow: 0 4px 9.4px rgba(0, 0, 0, .25);
}

/* State lives here, as one colour per item. The glyph paints with
   currentColor and the label reads it too, so flipping this single
   property recolours the whole tile. Idle tiles recede to grey; the
   active tile is the only accent in the bar. */
.dock-item {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 2px;
  background: none;
  border: 0;
  cursor: pointer;
  color: var(--dock-idle-icon);
}

.dock-item.is-active { color: var(--accent); }

/* One resting size for every tile — the Figma had tile 1 frozen
   mid-magnify. Enlarging is a hover state, not a layout offset. */
.tile {
  width: 40px;
  height: 40px;
  border-radius: 9px;
  /* No shadow here — a bare glyph must not cast one. The carded
     tiles (.tile-grid, .tile-mono) bring their own box-shadow. */
  transition: transform .18s cubic-bezier(.34, 1.56, .64, 1);
  transform-origin: bottom center;
}

.dock-item:hover .tile { transform: scale(1.2); }

/* Placeholder app tiles — swap these for real icons. */
.tile-mail {
  background: #fff;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .06);
  overflow: hidden;
}
.tile-mail svg { width: 100%; height: 100%; display: block; }

.tile-photos { background: conic-gradient(from 210deg, #ea4335, #fbbc04, #34a853, #4285f4, #ea4335); }
.tile-drive  { background: linear-gradient(145deg, #ffd04b, #34a853 55%, #4285f4); }

.tile-calendar {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  background: linear-gradient(180deg, #ea4335 0 26%, #fff 26%);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .08);
}
.tile-calendar b {
  font-size: 17px;
  font-weight: 500;
  line-height: 1;
  color: #ea4335;
  padding-bottom: 6px;
}

/* Launcher tile. Rows and columns are both declared explicitly, so
   the 3x3 is exact rather than however many rows the children
   happen to make: 3x7 + 2x4.5 = 30, which is the same 30.4px the
   line glyphs occupy (76% of the 40px tile). The launcher reads as
   one of the set, not a control bolted onto the end — so it carries
   no card and no shadow, exactly like its neighbours. */
.tile-grid {
  display: grid;
  grid-template-columns: repeat(3, 7px);
  grid-template-rows: repeat(3, 7px);
  gap: 4.5px;
  place-content: center;
  background: none;
}

.tile-grid i {
  width: 7px;
  height: 7px;
  background: currentColor;
  border-radius: 2px;
}

/* currentColor above lets the launcher's dots recede to the same idle
   grey as its neighbours' glyphs — no per-tile colour override needed. */

/* The running-page dot. It carries the same meaning as the active
   tile's ink, so it takes the same blue. 5px because 4px of a
   mid-value colour reads as a smudge rather than a mark. */
.dock-dot {
  width: 5px;
  height: 5px;
  background: var(--accent);
  border-radius: 50%;
}

.badge {
  position: absolute;
  top: -4px;
  /* right-anchored, so a wide label ("New") grows away from the
     neighbouring tile instead of colliding with its badge */
  right: -6px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding-inline: 5px;
  background: var(--danger);
  border: 2px solid rgba(245, 245, 247, .9);
  border-radius: 9px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, .2);
  font-family: var(--font-badge);
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: .0117em;
  color: #fff;
}

/* ═══ DOCK — LABELS & LAUNCHER ═════════════════════════════
   Markup comes from js/dock.js; these are the styles for it.
   ═══════════════════════════════════════════════════════════ */

.dock-wrap { position: relative; display: flex; flex-direction: column; align-items: center; }

.dock-label {
  font-size: 14px;
  font-weight: 500;
  line-height: 1.2;
  /* Idle label grey — one shade darker than the icon so text stays
     legible at its smaller size. The active tile overrides to accent. */
  color: var(--dock-idle-label);
  white-space: nowrap;
}

.dock-item.is-active .dock-label { color: var(--accent); }

/* Bare line-glyph, no card. Colour comes from the item's currentColor
   (idle grey, or accent when active), so the icon tracks its label. */
.tile-app {
  display: flex;
  align-items: center;
  justify-content: center;
  background: none;
}

.tile-app svg { width: 80%; height: 80%; fill: currentColor; }

/* The active dot sits under the label, so reserve its row on every
   item or the tiles lose their shared baseline. */
.dock-item .dock-dot { opacity: 0; }
.dock-item.is-active .dock-dot { opacity: 1; }

/* ── Launcher panel ── */

.launcher {
  position: absolute;
  bottom: calc(100% + var(--s3));
  left: 50%;
  translate: -50% 0;
  z-index: 2;
  padding: var(--s4);
  background: rgba(230, 231, 235, .92);
  backdrop-filter: blur(24px);
  -webkit-backdrop-filter: blur(24px);
  border-radius: var(--radius);
  box-shadow: 0 0 0 .5px var(--hairline), 0 12px 32px rgba(11, 27, 58, .18);
  animation: launcher-in var(--dur-mid) var(--ease-spring) both;
}

.launcher[hidden] { display: none; }

@keyframes launcher-in {
  from { opacity: 0; translate: -50% 8px; scale: .96; }
  to   { opacity: 1; translate: -50% 0;   scale: 1; }
}

/* Row gap is one Fibonacci step above the column gap (34 / 21 = φ),
   so the grid breathes vertically without the columns drifting apart
   — labels sit under their own tile, not between two. */
.launcher-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--s4) var(--s3);
}

.launcher-item {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s1);
  width: 68px;
}

.tile-mono {
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--surface);
  box-shadow: inset 0 0 0 1px var(--hairline), var(--shadow-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent);
}

.launcher-item:hover .tile-mono { transform: scale(1.08); }
.tile-mono { transition: transform var(--dur-fast) var(--ease-spring); }

@media (prefers-reduced-motion: reduce) {
  .launcher { animation: none; }
  .tile-mono { transition: none; }
}

/* ═══ RAIL — HERO ══════════════════════════════════════════ */

.hero { padding-bottom: var(--s4); }

/* Display type is capped at the scale value but shrinks with the
   rail once it is too narrow to hold it — 1cqi is 1% of the rail's
   width. At a 20% rail (~290px on a 13" laptop) the 50px headline
   would otherwise wrap mid-phrase or overflow. */
.hero-title {
  margin: 0;
  font-size: var(--fs-hero-fluid);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -.02em;
  text-align: center;
}

/* Bar-chart glyph closing the headline. Three equal bars on equal
   gaps, ascending — the first is the sentence's full stop, kept in
   the text colour so it still reads as punctuation. Sized in em so
   it scales with the type; bars grow from the baseline, as a chart
   sits on its axis. */
.spark {
  display: inline-block;
  width: .65em;
  height: .48em;
  margin-left: .09em;
  vertical-align: baseline;
}

.spark svg { width: 100%; height: 100%; display: block; fill: var(--accent); }
.spark .spark-stop { fill: currentColor; }

/* ═══ RAIL — CHAT ══════════════════════════════════════════
   The thread. Markup and behaviour live in js/chat.js; this is its
   appearance. Bubbles stack from the bottom and the area scrolls
   once it outgrows the rail's slack.
   ═══════════════════════════════════════════════════════════ */

.messages {
  flex: 1;
  min-height: 0;              /* lets the flex child actually shrink */
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  overscroll-behavior: contain;   /* don't hand scrolling to the page */
  padding-bottom: var(--s4);
  scrollbar-width: thin;
  scrollbar-color: rgba(0, 0, 0, .18) transparent;
}

.messages::-webkit-scrollbar { width: 6px; }
.messages::-webkit-scrollbar-thumb {
  background: rgba(0, 0, 0, .18);
  border-radius: 3px;
}
.messages::-webkit-scrollbar-track { background: transparent; }

/* Fades the top edge so a scrolled-away message looks deliberately
   cut rather than chopped. Only once there is a thread — while the
   hint is centred there is nothing to cut. */
.messages.has-message {
  mask-image: linear-gradient(to bottom, transparent 0, #000 var(--chat-fade), #000 100%);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--chat-fade), #000 100%);
}

/* margin-top:auto rather than justify-content:flex-end — with
   flex-end an overflowing column cannot be scrolled back to its
   top in several browsers. This pins the thread to the bottom while
   keeping the scrollback reachable. */
.chat {
  /* the measure --pad-bubble's cqi units resolve against */
  container-type: inline-size;
  margin-top: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: var(--chat-gap);
  width: 100%;
}

/* ── Bubble ── */

.bubble {
  width: fit-content;
  max-width: var(--bubble-max);
  margin: 0;
  padding: var(--pad-bubble);
  background: var(--chat-bubble);
  color: var(--chat-bubble-ink);
  font-size: var(--fs-sm);
  font-weight: 400;
  line-height: var(--lh-snug);
  border-radius: var(--radius-bubble);
  animation: bubble-in var(--dur-mid) var(--ease-spring) both;
}

@keyframes bubble-in {
  from { opacity: 0; transform: translateY(8px) scale(.96); }
  to   { opacity: 1; transform: none; }
}

/* ── Typing indicator ── */

.typing {
  display: flex;
  align-items: center;
  gap: 5px;
  padding: var(--pad-bubble);
  background: var(--chat-bubble);
  border-radius: var(--radius-bubble);
  animation: bubble-in var(--dur-fast) var(--ease-spring) both;
}

.typing span {
  width: 7px;
  height: 7px;
  /* the bubble ink, not white — white dots vanish on grey */
  background: var(--chat-bubble-ink);
  border-radius: 50%;
  animation: typing 1.1s var(--ease) infinite;
}

.typing span:nth-child(2) { animation-delay: .16s; }
.typing span:nth-child(3) { animation-delay: .32s; }

@keyframes typing {
  0%, 65%, 100% { opacity: .45; transform: translateY(0); }
  32%           { opacity: 1;   transform: translateY(-4px); }
}

/* ── Hint (empty state) ── */

.messages:not(.has-message) { justify-content: center; }
.messages:not(.has-message) .chat { margin-top: 0; align-items: center; }

.hint {
  margin: 0;
  font-size: var(--fs-title-fluid);
  font-weight: 400;
  line-height: 1.6;
  text-align: center;
  color: var(--ink);
  animation: bubble-in var(--dur-mid) var(--ease-spring) both;
}

.hint-dot {
  display: inline-block;
  width: .58em;
  height: .58em;
  margin: 0 .12em;
  background: var(--accent);
  border-radius: 50%;
  vertical-align: baseline;
}

@media (prefers-reduced-motion: reduce) {
  .bubble, .hint, .typing { animation: none; }
  .typing span { animation: none; opacity: .7; }
}

/* ═══ RAIL — THINKING ══════════════════════════════════════ */

.thinking {
  flex: none;
  padding: var(--s3) var(--pad-section) var(--s4);
  border-top: 1px solid var(--hairline);
}

.thinking-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s2);
}

.thinking-title {
  margin: 0;
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.2;
}

.check-all {
  flex: none;
  font-size: var(--fs-nav);
  font-weight: 500;
  color: var(--ink-mute);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color .18s ease;
}

.check-all:hover { color: var(--ink); }

.thought-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s2);
}

/* Two across only once the rail itself is wide enough to give each
   card a readable line — not based on viewport width. */
@container (min-width: 460px) {
  .thought-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s3); }
}

.thought {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 62px;
  padding: var(--s2) var(--s3) var(--s2) calc(13px + var(--s3));
  background: var(--surface);
  border-radius: var(--radius-sm);
  overflow: hidden;
  transition: transform .18s ease, box-shadow .18s ease;
}

.thought::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: 13px;
  background: var(--accent);
}

.thought:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
}

.thought p {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 400;
  line-height: 1.45;
  text-align: center;
}

/* ═══ PAGE — SKILLS ════════════════════════════════════════
   skills.html. One flat grid of tools, grouped by discipline.
   Every group holds the same number of items, so the columns line
   up without any per-card sizing.
   ═══════════════════════════════════════════════════════════ */

/* ═══ LONG PAGES ═══════════════════════════════════════════
   A panel whose content outgrows one screen. Shared by skills and
   projects; any future list page should use these rather than
   inventing its own scroller.
   ═══════════════════════════════════════════════════════════ */

.main-scroll,
.main-skills {
  align-items: stretch;
  justify-content: flex-start;
}

/* The sheet scrolls, not the panel — the dock is a sibling of this
   element, so it stays pinned to the bottom of the card instead of
   scrolling away with the content. */
.sheet,
.skills {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  width: 100%;
  max-width: 1000px;
  margin: 0 auto;
  padding-block: var(--s2) var(--s4);
  scrollbar-width: thin;
  scrollbar-color: rgba(0, 0, 0, .18) transparent;
}

/* never squeezed by the scrolling sheet above it */
#dock-root { flex: none; }

.set { margin-bottom: var(--s5); }

/* Groups a run of cards without competing with their names, which
   are what you actually scan. */
.section-label {
  margin: 0 0 var(--s3);
  padding-bottom: var(--s1);
  border-bottom: 1px solid var(--hairline);
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--ink-mute);
}

.skills-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: var(--s4);
}

.skill-name {
  margin: 0 0 var(--s2);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: var(--lh-tight);
  color: var(--ink);
}

/* No bullets: with five short items per card the glyphs are pure
   noise, and the left edge reads cleaner without them. */
.skill-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.skill-list li {
  font-size: var(--fs-sm);
  font-weight: 300;
  line-height: var(--lh-snug);
  color: var(--ink-mute);
}

/* ═══ PAGE — PROJECTS ══════════════════════════════════════ */

.work-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(230px, 1fr));
  gap: var(--s4);
}

.work {
  display: flex;
  flex-direction: column;
  transition: transform var(--dur-fast) var(--ease);
}

.work:hover { transform: translateY(-3px); }

/* √φ, the same ratio as the map cards and the project screenshot —
   one rectangle shape across the whole site. */
.work-thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: var(--phi-half) / 1;
  margin-bottom: var(--s2);
  padding: var(--s3);
  background: var(--screen);
  border-radius: var(--radius-sm);
  color: var(--accent);
  overflow: hidden;
  transition: background var(--dur-fast) var(--ease);
}

.work:hover .work-thumb { background: #eef0f3; }

.work-thumb svg { width: 44%; height: 44%; }

/* A bare <img> is a screenshot: fill the thumb edge to edge. */
.work-thumb img { width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }

/* .work-icon is an illustration: sit it centred at glyph size on the
   grey, the same way the SVG placeholders did. Cropping a square
   illustration to a √φ box would cut its edges off. */
.work-thumb .work-icon {
  width: 46%;
  height: auto;
  object-fit: contain;
  border-radius: 0;
}

/* Wordmark stand-ins until real artwork exists. */
.work-mark {
  font-size: var(--fs-body);
  font-weight: 700;
  line-height: var(--lh-tight);
  text-align: center;
}

.work-mark-aw { color: #d93a2b; }
.work-mark-cs { color: var(--accent); }

.work-name {
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: var(--lh-tight);
  color: var(--ink);
}

.work-meta {
  margin: 5px 0 0;
  font-size: var(--fs-nav);
  font-weight: 300;
  line-height: var(--lh-snug);
  color: var(--ink-mute);
}

/* ═══ PAGE — STUDIES ═══════════════════════════════════════
   studies.html. Shares every token with the homepage: same type
   scale, same 4:1 shell, same rail. Only the panel contents differ.
   ═══════════════════════════════════════════════════════════ */

.main-prose { justify-content: center; }

/* One measure and one centre line for both paragraphs. The Figma had
   the lead 770px and the body 968px on different left edges, which
   is what made them look misaligned. */
.prose {
  width: 100%;
  max-width: 62ch;
  margin: auto;
}

.prose-lead {
  margin: 0;
  font-size: var(--fs-lead);
  font-weight: 500;
  line-height: 1.65;
}

/* Rule under the lead. 107px in the mock; φ⁻¹ of that lands on the
   Fibonacci 55+13, so it is built from the spacing scale. */
.prose-rule {
  display: block;
  width: calc(var(--s5) + var(--s5));
  height: 3px;
  margin: var(--s3) 0 var(--s4) var(--s2);
  background: var(--accent);
  border-radius: 2px;
}

.prose-body {
  margin: 0;
  font-size: var(--fs-title);
  font-weight: 300;
  line-height: 1.9;
}

/* Inline emphasis. Kept as a colour shift only — no weight change,
   so the line's rhythm is undisturbed. */
.hl { color: var(--accent); }

a.hl { text-decoration: none; }
a.hl:hover { text-decoration: underline; text-underline-offset: 4px; }

/* ── Rail sub-heading ── */

.hero-sub {
  margin: var(--s1) 0 0;
  font-size: var(--fs-lead-fluid);
  font-weight: 300;
  line-height: 1.3;
  text-align: center;
}

/* ── Rail project card ── */

/* Takes the rail's slack so the card sits between the hero and the
   thinking block, the way .messages does on the homepage. */
/* A bubble that happens to hold media. Sits in .chat like any
   message, so its position, alignment and gap come from the thread
   — nothing here restates them. */
.project {
  display: flex;
  flex-direction: column;
  width: var(--bubble-max);
  padding: var(--pad-bubble);
  background: var(--chat-bubble);
  border-radius: var(--radius-bubble);
}

/* Bubble prose, so it takes the bubble's ink. The title below stays
   full --ink: it is a heading inside the bubble, not more message. */
.project-intro {
  flex: none;
  margin: 0;
  font-size: var(--fs-sm);
  font-weight: 400;
  line-height: var(--lh-snug);
  color: var(--chat-bubble-ink);
}

.project-title {
  flex: none;
  margin: var(--s1) 0 var(--s2);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: 1.4;
}

/* Grows into the rail's spare height and gives it back when short.
   The √φ ratio is a ceiling rather than a fixed size — at a 20% rail
   the card would otherwise push the page into a scrollbar. */
/* A real <img>, not a CSS background: it carries alt text, gets
   width/height for layout stability, and can lazy-load. Sits on the
   bubble's standard inset like the copy above it.
   flex-shrink lets it give ground on a short viewport but never past
   the floor — with min-height:0 it collapsed to nothing. */
.project-shot {
  display: block;
  width: 100%;
  height: auto;
  /* never shrinks: the slot scrolls instead, so the screenshot keeps
     its shape at every viewport */
  flex: none;
  aspect-ratio: var(--phi-half) / 1;
  object-fit: cover;
  object-position: top center;
  background: #fff;
  border-radius: var(--radius-xs);
  box-shadow: var(--shadow-sm);
}

.project-cta {
  flex: none;
  display: block;
  margin-top: var(--s2);
  padding: var(--s1) var(--s2);
  background: var(--accent);
  border-radius: var(--radius-sm);
  font-size: var(--fs-nav);
  font-weight: 500;
  line-height: 1.6;
  color: #fff;
  text-align: center;
  transition: filter .18s ease, transform .18s ease;
}

.project-cta:hover { filter: brightness(1.08); transform: translateY(-1px); }

/* ── Light dock tile ── */

.tile-light {
  background: rgba(255, 255, 255, .92);
  box-shadow: inset 0 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, .08);
  overflow: hidden;
}

.tile-light svg { width: 100%; height: 100%; display: block; }

/* ═══ RESPONSIVE ═══════════════════════════════════════════ */

@media (max-width: 1180px) {
  :root { --fs-hero: 40px; --fs-lead: 24.7px; }
  .container { grid-template-columns: 1fr; padding: 0 var(--s2) var(--s2); }
  .main { padding-bottom: var(--s6); }
  /* Rail now sits below, so the page scrolls regardless — the
     viewport-height cap would only shrink the cards pointlessly. */
  .screen { width: 100%; }
  .rail-body { padding-top: var(--s4); }
}

@media (max-width: 720px) {
  :root {
    --fs-hero: 34px; --fs-lead: 21px;
    --fs-title: 20px; --fs-body: 16px;
    --gutter: var(--s3);
  }
  .header { height: 64px; }
  .cards { grid-template-columns: 1fr; gap: var(--s5); }
  .header-nav { display: none; }
}
