/* =====================================================================
   Markus Lunde — Portfolio
   Plain HTML/CSS/JS · Bento-grid layout
   Edit the design tokens below to re-theme the whole site.
   ===================================================================== */

/* ----------------------------- Tokens ------------------------------ */
:root {
  /* Light theme: graphite, matched to the M logo (its near-black #1a1a1a on
     white and greys). No accent colour: emphasis is weight and black / white,
     and the brand tiles and project covers bring the colour. */
  --bg: #efefef;
  --card: #ffffff;
  --card-2: #f4f4f4;
  --ink: #1a1a1a;             /* the logo's black */
  --ink-soft: #646464;
  --line: rgba(0, 0, 0, 0.09);
  --accent: #1a1a1a;          /* emphasis (dots, pin, focus, buttons): ink for now; set a colour here to re-brand */
  --accent-soft: #e4e4e4;     /* halos and rings around --accent */
  --glass: rgba(255, 255, 255, 0.78);  /* translucent --card for frosted controls */
  --live: #2fbf71;            /* "now" and "open to new work" */
  --live-soft: rgba(47, 191, 113, 0.22);
  --linkedin: #0a66c2;                 /* LinkedIn brand blue */
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 6px 16px rgba(0, 0, 0, 0.07);
  --shadow-hover: 0 3px 8px rgba(0, 0, 0, 0.08), 0 12px 26px rgba(0, 0, 0, 0.11);
  /* About twice the tile shadow, so the popup's shadow matches the tile's at
     the start and end of the grow animation, when it's scaled to tile size */
  --shadow-sheet: 0 2px 5px rgba(0, 0, 0, 0.07), 0 14px 36px rgba(0, 0, 0, 0.16);
  /* Live tiles wear their service's own light / dark colours.
     Hevy light: measured on hevy.com. */
  --hv-bg: #fff;
  --hv-ink: #191a1b;
  --hv-muted: #83898f;
  --hv-line: #f2f4f7;          /* dividers and shaded set rows */
  --hv-ring: #e4e6e9;          /* outline of the round exercise pictures */
  --hv-logo: brightness(0);    /* the Hevy logo file is white: black on light */
  /* Letterboxd light: from Letterboxd's logo for light backgrounds */
  --lb-bg: #fff;
  --lb-ink: #556677;           /* wordmark, headings, circle overlaps */
  --lb-muted: #667788;
  --lb-line: #e1e6eb;
  --lb-poster: #e8edf1;        /* empty poster slots */
  --lb-shimmer: #f3f5f7;
  --lb-edge: rgba(20, 24, 28, 0.08);
  --lb-brand: #556677;
  /* Oslo map: land, parks, fjord and roads in quiet greys (water a touch cool) */
  --map-land: #f0f0f0;
  --map-park: #e3e6e0;
  --map-water: #d6dde1;
  --map-road: #ffffff;
  --radius: 26px;
  --radius-sm: 16px;
  --gap: 18px;
  --maxw: 1120px;
  --control-h: 50px;          /* the menu pill and the round theme toggle */
}

:root[data-theme="dark"] {
  --bg: #0e0e0e;
  --card: #1a1a1a;           /* the logo's black, lifted off the page */
  --card-2: #242424;
  --ink: #f2f2f2;
  --ink-soft: #a3a3a3;
  --line: rgba(255, 255, 255, 0.1);
  --accent: #f2f2f2;
  --accent-soft: #333333;
  --glass: rgba(26, 26, 26, 0.78);     /* translucent --card for frosted controls */
  --live-soft: rgba(47, 191, 113, 0.28);
  --linkedin: #70b5f9;                 /* LinkedIn's own dark-mode blue; the standard one vanishes here */
  --shadow: 0 1px 3px rgba(0, 0, 0, 0.4), 0 6px 16px rgba(0, 0, 0, 0.45);
  --shadow-hover: 0 4px 10px rgba(0, 0, 0, 0.45), 0 12px 26px rgba(0, 0, 0, 0.55);
  --shadow-sheet: 0 2px 6px rgba(0, 0, 0, 0.45), 0 14px 36px rgba(0, 0, 0, 0.5);
  /* Hevy dark: the app's dark UI (Hevy's own store screenshots): #1c1c1e cards,
     white text, grey #9b9b9d. Lines are one step up from the card. */
  --hv-bg: #1c1c1e;
  --hv-ink: #fff;
  --hv-muted: #9b9b9d;
  --hv-line: #2c2c2e;
  --hv-ring: #3a3a3c;
  --hv-logo: none;             /* the white logo as it is */
  /* Letterboxd dark: measured on letterboxd.com (content #14181c, text #99aabb,
     section rules #445566, posters on #2c3440, white circle overlaps) */
  --lb-bg: #14181c;
  --lb-ink: #99aabb;
  --lb-muted: #99aabb;         /* their #667788 is too faint for small text on #14181c */
  --lb-line: #445566;
  --lb-poster: #2c3440;
  --lb-shimmer: #384352;
  --lb-edge: rgba(255, 255, 255, 0.05);
  --lb-brand: #fff;
  --map-land: #2b2b2b;
  --map-park: #2c332a;
  --map-water: #12181d;
  --map-road: #3b3b3b;
}

/* ----------------------------- Base -------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

html { scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: "Archivo", system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
  font-size: 16px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background-color 0.35s ease, color 0.35s ease;
}

a { color: inherit; text-decoration: none; }
h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -0.02em; line-height: 1.12; }
p { margin: 0; }

/* Display type: the big headings (the intro line, case study titles and
   their section heads) in Archivo set wide and heavy, like the logo's blocky
   shapes. Everything else is Archivo at its normal width. */
.hero__name, .case__title, .case__story h3 { font-stretch: 118%; letter-spacing: -0.015em; }

.muted { color: var(--ink-soft); font-size: 0.86rem; }
.accent-text { color: var(--accent); }

/* Visually hidden, still read by screen readers */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.eyebrow {
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* ----------------------------- Top bar ----------------------------- */
/* Sticky and transparent. It slides out of view while scrolling down and
   back in on scroll up (JS toggles .topbar--hidden). The empty strip ignores
   the pointer so it never blocks clicks on tiles scrolling underneath. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 50;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);   /* the menu stays centred */
  align-items: center;
  gap: 12px;
  /* Full page width so the logo sits in the page's far corner, not the content's. */
  width: 100%;
  margin: 0;
  padding: 18px clamp(18px, 3vw, 40px);
  pointer-events: none;
  transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1);
}
.topbar--hidden { transform: translateY(-110%); }
.logo, .nav, .theme-toggle { pointer-events: auto; }

/* Menu wrapper: invisible on desktop (the menu sits in the top bar's grid),
   the fixed bottom dock on phones. */
.dock { display: contents; }

/* The logo, on its own. Shrink-wrapped so its grid column's empty space isn't
   clickable. */
.logo {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  padding: 6px;
  margin: -6px;                /* a bigger tap area, without moving the logo */
  border-radius: 50%;
}

/* The ML logo: a round badge as big as the theme toggle, in the text colour,
   with the M cut out in the page colour. So it's a near-black badge on light
   and a near-white one on dark. */
.logo__mark { display: block; flex: none; width: var(--control-h); height: var(--control-h); }
.logo__disc { fill: var(--ink); }
.logo__ml { fill: var(--bg); }

/* Logo animation (main.js): while the logo moves, it's drawn from its parts.
   While rising, that's the separate left and right pieces. While only the
   square moves, it's the two as one shape, so they never show a seam. The
   rest of the time, it's the one original shape. Until the script has
   started, the logo stays hidden, so it never shows whole and then jumps to
   its first frame. While rising, the pieces are clipped at the M's baseline
   (#logoRise), so they rise up out of it. */
.logo__parts { display: none; }
.logo__mark.is-moving .logo__parts { display: inline; }
.logo__mark.is-moving .logo__whole { display: none; }
.logo__part--a, .logo__part--b { display: none; }
.logo__mark.is-rising .logo__part--a,
.logo__mark.is-rising .logo__part--b { display: inline; }
.logo__mark.is-rising .logo__part--m { display: none; }
.logo__mark.is-rising .logo__parts { clip-path: url(#logoRise); }
.js .logo__mark:not(.is-ready) { visibility: hidden; }

.nav {
  position: relative;
  justify-self: center;
  display: flex;
  gap: 4px;
  height: var(--control-h);
  padding: 5px;
  background: var(--glass);
  border: 1px solid var(--line);
  border-radius: 999px;
  box-shadow: var(--shadow);
}
.nav__tab {
  position: relative;
  z-index: 1;               /* above the sliding indicator */
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  padding: 8px 18px;
  border-radius: 999px;
  cursor: pointer;
  transition: color 0.2s ease, background-color 0.2s ease;
}
.nav__tab:hover { color: var(--ink); }
/* No-JS fallback: the active tab carries its own dark fill */
.nav__tab.is-active {
  background: var(--ink);
  color: var(--bg);
}
/* With JS, one dark thumb slides between tabs instead (positioned by JS) */
.nav__indicator {
  position: absolute;
  top: 0;
  left: 0;
  width: 0;
  border-radius: 999px;
  background: var(--ink);
  opacity: 0;
  pointer-events: none;
  transition: transform 0.5s cubic-bezier(0.34, 1.4, 0.64, 1),
              width 0.5s cubic-bezier(0.34, 1.4, 0.64, 1);
}
.nav.has-indicator .nav__indicator { opacity: 1; }
.nav.has-indicator .nav__tab.is-active { background: transparent; color: var(--bg); }

/* A circle exactly as tall as the menu pill, with just the sun or moon */
.theme-toggle {
  justify-self: end;
  display: inline-grid;
  place-items: center;
  width: var(--control-h);
  height: var(--control-h);
  padding: 0;
  border: 1px solid var(--line);
  background: var(--glass);
  color: var(--ink);
  border-radius: 50%;
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: transform 0.15s ease;
}
.theme-toggle:hover { transform: translateY(-1px); }
.theme-toggle__icon { display: inline-flex; }
.theme-toggle__icon--moon { display: none; }
:root[data-theme="dark"] .theme-toggle__icon--sun { display: none; }
:root[data-theme="dark"] .theme-toggle__icon--moon { display: inline-flex; }

/* ----------------------------- Bento grid -------------------------- */
.bento {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  /* Square base unit: row height = column width (--cell, set by main.js).
     Rows never grow with content; see the tile rules below. */
  grid-auto-rows: var(--cell, 200px);
  grid-auto-flow: row;
  gap: var(--gap);
  max-width: var(--maxw);
  margin: 8px auto 40px;
  padding: 0 clamp(16px, 4vw, 28px);
}

/* ----------------------------- Card base --------------------------- */
.card {
  position: relative;
  overflow: hidden;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 22px;
  display: flex;
  flex-direction: column;
  transition: transform 0.3s cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 0.3s ease, opacity 0.35s ease, filter 0.35s ease;
}

/* Only interactive tiles lift on hover — static info tiles stay put, and so
   does a placeholder tile (a link with no href yet) */
.card.is-expandable:not(.is-dimmed):hover,
a.card--tile[href]:not(.is-dimmed):hover,
a.card--letterboxd[href]:not(.is-dimmed):hover,
button.card--tile:not(.is-dimmed):hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-hover);
  transition: transform 0.22s ease, box-shadow 0.22s ease;
  transition-delay: 0s;
}

/* ----------------------------- Tile system ------------------------- */
/* TILE RULES. Every tile follows these, so tiles keep their shape and the
   grid never has holes.
   1. Four shapes only: S (1x1), W (2x1), T (1x2) and L (2x2). A tile's shape
      comes only from its data-shape attribute, optionally changed per layout
      with data-shape-tablet (<=900px, also used on phones) and
      data-shape-phone (<=560px). Nothing else sets grid spans.
   2. Rows are exactly one cell tall, and a cell is square (--cell = column
      width, set by main.js). A tile's size never depends on its content, so
      no tile can stretch its neighbours.
   3. Content adapts to the tile instead: every tile is a size container, text
      scales with it inside clamp() limits (cqi units), long text is clamped,
      and lists show what fits ("See N more").
   4. The About / Work filters move tiles; they never change a tile's shape.
   5. A tile changes shape between layouts only where it would be unusable
      otherwise (the hero, workout tile and the last project become L on phones).
   6. Every order must fill complete rows in All, About and Work on desktop
      (4 columns), tablet and phone (2 columns). After adding, removing or
      moving a tile, run .claude/layout-check.js in the browser. */
.card { container-type: size; }

[data-shape="S"] { grid-column: span 1; grid-row: span 1; }
[data-shape="W"] { grid-column: span 2; grid-row: span 1; }
[data-shape="T"] { grid-column: span 1; grid-row: span 2; }
[data-shape="L"] { grid-column: span 2; grid-row: span 2; }
@media (max-width: 900px) {
  [data-shape-tablet="S"] { grid-column: span 1; grid-row: span 1; }
  [data-shape-tablet="W"] { grid-column: span 2; grid-row: span 1; }
  [data-shape-tablet="T"] { grid-column: span 1; grid-row: span 2; }
  [data-shape-tablet="L"] { grid-column: span 2; grid-row: span 2; }
}
@media (max-width: 560px) {
  [data-shape-phone="S"] { grid-column: span 1; grid-row: span 1; }
  [data-shape-phone="W"] { grid-column: span 2; grid-row: span 1; }
  [data-shape-phone="T"] { grid-column: span 1; grid-row: span 2; }
  [data-shape-phone="L"] { grid-column: span 2; grid-row: span 2; }
}

/* ----------------------------- Hero -------------------------------- */
/* Intro tile: circular portrait (invisible circle) left, brief text right */
.card--hero { padding: 0 clamp(20px, 3vw, 34px); }
.hero { display: flex; flex-direction: row; align-items: center; gap: clamp(16px, 2.5vw, 28px); height: 100%; }
.hero__avatar {
  flex: 0 0 auto;
  width: clamp(120px, 15vw, 168px);
  height: clamp(120px, 15vw, 168px);
  border-radius: 50%;
  overflow: hidden;
  background: none;
}
/* Transparent square photo clipped to a circle — the shirt curves along the edge */
.avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}
.hero__text { flex: 1; align-self: center; }
.hero__wave { color: var(--ink-soft); font-weight: 500; margin-bottom: 8px; font-size: 0.95rem; }
.hero__name {
  font-size: clamp(1.05rem, 1.5vw, 1.28rem);
  font-weight: 800;
  line-height: 1.3;
  color: var(--ink);
  margin: 0;
}
.hero__status { margin-top: 12px; }
.wave { display: inline-block; transform-origin: 70% 70%; animation: wave 2.4s ease-in-out infinite; }
@keyframes wave {
  0%, 60%, 100% { transform: rotate(0); }
  10%, 30% { transform: rotate(14deg); }
  20% { transform: rotate(-8deg); }
}

/* ----------------------------- Tile (icon links) ------------------- */
.card--tile {
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.card--tile:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

/* ----------------------------- Logo tiles (Gmail / LinkedIn) ------- */
/* Just the logo, centred. Sized from the grid cell so it scales with the tile.
   The pieces are animated from JS (assemble / draw on) on reveal and hover. */
.card--logo {
  align-items: center;
  justify-content: center;
  width: 100%;
  font: inherit;              /* the Gmail tile is a <button> */
  color: inherit;
  cursor: pointer;
}
.logo-art { display: block; flex: none; width: auto; }
.logo-art--gmail {
  height: calc(var(--cell, 200px) * 0.27);
  overflow: hidden;           /* pieces slide in from the logo's own edges */
}
.logo-art--linkedin,
.logo-art--instagram {
  height: calc(var(--cell, 200px) * 0.34);
  overflow: visible;          /* room for the dot's springy overshoot */
}
/* Logos that draw themselves (main.js): stroked lines and a dot that pops in */
.logo-dot { transform-box: fill-box; transform-origin: center; }
.logo-stroke { fill: none; stroke-linecap: round; stroke-linejoin: round; }
.li-dot { fill: var(--linkedin); }
.li-stroke { stroke: var(--linkedin); stroke-width: 3.2; }
.ig-dot { fill: url(#ig-gradient); }
.ig-stroke { stroke: url(#ig-gradient); stroke-width: 2.1; }

/* Corner chip (same look as the expand button): copy icon on Gmail,
   external-link arrow on LinkedIn. Gmail's grows into a "Copied ✓" pill. */
.expand-btn.tile-action {
  border-radius: 999px;
  overflow: hidden;
  /* The hidden "Copied ✓" label is wider than the collapsed chip and sets the
     grid cell's width; centring the cell keeps the icon in the middle. */
  place-content: center;
  transition: opacity 0.2s ease, transform 0.2s ease,
              width 0.32s cubic-bezier(0.34, 1.4, 0.64, 1),
              background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
.tile-action__icon,
.tile-action__done { grid-area: 1 / 1; transition: opacity 0.15s ease; }
.tile-action__done {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 0.78rem;
  font-weight: 600;
  white-space: nowrap;
  opacity: 0;
}
.tile-action__check {
  fill: none;
  stroke: currentColor;
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 21;
  stroke-dashoffset: 21;
}
.card--gmail.is-copied .tile-action {
  width: 96px;
  opacity: 1;
  transform: none;
  background: var(--ink);
  color: var(--bg);
  border-color: transparent;
}
.card--gmail.is-copied .tile-action__icon { opacity: 0; }
.card--gmail.is-copied .tile-action__done { opacity: 1; transition-delay: 0.12s; }
.card--gmail.is-copied .tile-action__check {
  animation: check-draw 0.35s cubic-bezier(0.4, 0, 0.2, 1) 0.2s forwards;
}
@keyframes check-draw { to { stroke-dashoffset: 0; } }

/* ----------------------------- Hevy / latest workout -------------- */
/* Styled like Hevy's own workout card: Figtree, grey labels, Duration / Volume /
   Records, round exercise pictures and "See N more exercises", in Hevy's light
   or dark colours to match the site theme (--hv-* tokens at the top). Filled in
   by js/hevy.js; the list shows the exercises that fit the tile. The expanded
   view uses the same .hevy-skin. The expand button stays the site's own. */
.hevy-skin {
  background: var(--hv-bg);
  color: var(--hv-ink);
  font-family: "Figtree", system-ui, sans-serif;
}
.card--hevy { padding: 18px; }

/* Avatar + username + date (also used by the expanded view) */
.hevy-user {
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  padding-right: 40px;        /* clears the expand chip */
}
.hevy-user__avatar {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  overflow: hidden;
  background: var(--hv-line);
}
.hevy-user__avatar img { display: block; width: 100%; height: 100%; object-fit: cover; }
.hevy-user__text { display: flex; flex-direction: column; min-width: 0; }
.hevy-user__name,
.hevy-user__date { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hevy-user__name { font-size: 0.95rem; font-weight: 600; line-height: 1.3; color: var(--hv-ink); }
.hevy-user__date { color: var(--hv-muted); font-size: 0.8rem; line-height: 1.3; }

.hevy__title {
  margin-top: 14px;
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Duration / Volume / Records: small grey labels over the values */
.hevy-stats { display: flex; gap: 20px; margin: 10px 0 0; }
.hevy-stats dt { color: var(--hv-muted); font-size: 0.69rem; line-height: 1.3; }
.hevy-stats dd {
  display: flex;
  align-items: center;
  gap: 3px;
  margin: 2px 0 0;
  font-size: 0.94rem;
  line-height: 1.3;
  white-space: nowrap;
}
.hevy-stats [hidden] { display: none; }

/* Hevy's gold record medal (sprite #ic-medal) */
.hevy-medal { width: 1.1em; height: 1.1em; flex: none; }

/* Exercise rows: round picture + "3 sets Incline Bench Press (Dumbbell)". The
   list takes the tile's remaining height; js/hevy.js hides the rows that don't
   fit and fills in "See N more exercises". */
.hevy-exercises {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
  display: grid;
  align-content: start;
  gap: 10px;
  margin: 14px 0 0;
  padding: 14px 0 0;
  border-top: 1px solid var(--hv-line);
  list-style: none;
}
.hevy-ex { display: flex; align-items: center; gap: 10px; min-width: 0; }
.hevy-ex[hidden] { display: none; }
.hevy-ex__text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  min-width: 0;
  font-size: 0.875rem;
  line-height: 1.3;
}
.hevy-ex__ghost { display: block; height: 12px; border-radius: 999px; background: var(--hv-line); flex: 1; }

/* Round exercise picture, or Hevy's dumbbell for exercises without one */
.hevy-thumb {
  --size: 40px;
  flex: none;
  display: grid;
  place-items: center;
  width: var(--size);
  height: var(--size);
  border-radius: 50%;
  border: 0.8px solid var(--hv-ring);
  overflow: hidden;
  background: var(--hv-bg);
  color: var(--hv-muted);
}
.hevy-thumb img { display: block; width: 100%; height: 100%; object-fit: cover; }
.hevy-thumb svg { width: 55%; height: 55%; }

.hevy__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: auto;           /* sits at the bottom of the tile */
  padding-top: 12px;
}
.hevy__more { color: var(--hv-muted); font-size: 0.82rem; }

/* Shorter tile (the big square on phones): tighter spacing and smaller
   pictures, so three exercises fit, as on Hevy's card */
@container (max-height: 420px) {
  .hevy__title { margin-top: 10px; }
  .hevy-stats { margin-top: 6px; }
  .hevy-exercises { margin-top: 10px; padding-top: 10px; gap: 8px; }
  .hevy-exercises .hevy-thumb { --size: 34px; }
  .hevy__foot { padding-top: 8px; }
}

/* Hevy mark + wordmark, like the header on hevy.com */
.hevy-brand {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  color: var(--hv-ink);
  font-family: "Figtree", system-ui, sans-serif;
  font-size: 0.8rem;
  font-weight: 800;
  letter-spacing: 0.04em;
}
.hevy-brand img { width: 1.3em; height: 1.3em; filter: var(--hv-logo); }   /* the logo file is white */

/* Hidden sprite holding the medal and dumbbell icons (used via <use href="#...">) */
.sprite { position: absolute; width: 0; height: 0; overflow: hidden; }

/* ----------------------------- Letterboxd / recently watched ------- */
/* In Letterboxd's own colours, light or dark to match the site theme (--lb-*
   tokens at the top): the three-circle mark, a section heading with a rule
   under it as on letterboxd.com, four posters in a row with your rating over
   them. Filled in by js/letterboxd.js. Posters are ALWAYS 2:3 (never
   stretched): their height comes from the tile, capped so all four fit its
   width. The corner button stays the site's own. */
.card--letterboxd {
  padding: 16px;
  background: var(--lb-bg);
  color: var(--lb-ink);
}
.card--letterboxd:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }
.card--letterboxd .eyebrow { color: var(--lb-ink); }
.card--letterboxd:not(.is-dimmed):hover .expand-btn,
.card--letterboxd:focus-visible .expand-btn { opacity: 1; transform: scale(1); }

.letterboxd__head,
.letterboxd__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex: none;
}
.letterboxd__head {
  margin-right: 38px;         /* clears the corner chip */
  padding-bottom: 6px;
  border-bottom: 1px solid var(--lb-line);
}
.letterboxd__foot {
  margin-top: 10px;
  color: var(--lb-muted);
  font-size: 0.72rem;
  line-height: 1.2;
}
.letterboxd__brand { color: var(--lb-brand); font-weight: 700; }

/* Letterboxd's mark: three overlapping circles; where they overlap shows the
   wordmark colour (slate on light, white on dark), as in their logos */
.letterboxd-mark { display: block; flex: none; width: 30px; height: auto; }
.letterboxd-mark__overlap { fill: var(--lb-brand); }

.letterboxd-posters {
  flex: 1 1 0;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-top: 10px;
}
.letterboxd-film {
  position: relative;
  display: block;
  flex: none;
  /* As tall as the row allows, but never so tall that four won't fit across */
  height: min(100%, (100cqi - 24px) / 4 * 1.5);
  aspect-ratio: 2 / 3;                        /* DO NOT stretch posters */
  overflow: hidden;
  border-radius: 8px;
  background: var(--lb-poster);
  box-shadow: inset 0 0 0 1px var(--lb-edge);
}
.letterboxd-film__poster { width: 100%; height: 100%; object-fit: cover; display: block; }
.letterboxd-film__fallback {
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
  color: var(--lb-ink);
  font-size: 1.5rem;
  font-weight: 700;
  background: var(--lb-poster);
}
.letterboxd-film__shade {
  position: absolute;
  inset: auto 0 0;
  height: 55%;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.78), transparent);
  pointer-events: none;
}
.letterboxd-film__meta {
  position: absolute;
  left: 6px;
  right: 6px;
  bottom: 5px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 4px;
  color: #fff;
  font-size: clamp(0.56rem, 3cqi, 0.72rem);
  font-weight: 600;
  line-height: 1;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.8);
}
.letterboxd-film__rewatch { color: #fff; font-size: 1.15em; }
.letterboxd-film--ghost {
  background: linear-gradient(110deg, var(--lb-poster) 20%, var(--lb-shimmer) 38%, var(--lb-poster) 56%);
  background-size: 220% 100%;
  animation: letterboxd-shimmer 1.6s linear infinite;
}
@keyframes letterboxd-shimmer { to { background-position-x: -220%; } }

/* Short strip (phones): less chrome around the posters so they stay readable.
   (A container query can't style the tile itself, hence the media query.) */
@container (max-height: 200px) {
  .letterboxd-posters { margin-top: 8px; }
  .letterboxd__foot { margin-top: 8px; }
}
@media (max-width: 560px) {
  .card--letterboxd { padding: 12px 14px; }
}

/* ----------------------------- Small print on tiles ---------------- */
/* Text sizes follow the tile (cqi = 1% of the tile's width) so the same copy
   fits a small phone square and a desktop square alike */
.card .eyebrow { font-size: clamp(0.62rem, 5.4cqi, 0.72rem); }

/* "Open to new work": a green dot with a soft pulse */
.status {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-size: clamp(0.7rem, 6.4cqi, 0.82rem);
  color: var(--ink-soft);
  font-weight: 500;
}
.status__dot {
  flex: none;
  width: 8px; height: 8px; border-radius: 50%;
  background: var(--live);
  box-shadow: 0 0 0 3px rgba(47, 191, 113, 0.2);
  animation: status-pulse 2.4s ease-out infinite;
}
@keyframes status-pulse {
  0% { box-shadow: 0 0 0 0 rgba(47, 191, 113, 0.45); }
  70%, 100% { box-shadow: 0 0 0 8px rgba(47, 191, 113, 0); }
}

/* ----------------------------- Oslo map ---------------------------- */
/* A map of central Oslo from OpenStreetMap data (--map-* colours) fills the
   tile. The place sits at the top and the local time at the bottom, each on a
   soft fade of the card's own colour so they read over the map. main.js ("Oslo
   clock") keeps the time and sets data-sky="day" / "night" (sun or moon). */
.card--map {
  justify-content: space-between;
  padding: clamp(14px, 8cqi, 22px);
}
.map-art { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.card--map::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(to bottom, color-mix(in srgb, var(--card) 85%, transparent), transparent 34%, transparent 62%, color-mix(in srgb, var(--card) 85%, transparent));
}
.map-land { fill: var(--map-land); }
.map-park { fill: var(--map-park); }
.map-water { fill: var(--map-water); }
.map-river { fill: none; stroke: var(--map-water); stroke-width: 1.4; stroke-linecap: round; stroke-linejoin: round; }
.map-road { fill: none; stroke: var(--map-road); stroke-width: 3; stroke-linecap: round; stroke-linejoin: round; }
.map-road--minor { stroke-width: 1.6; }
.map-pin path { fill: var(--accent); }
.map-pin__halo { fill: var(--accent); opacity: 0.2; }
.map-pin__eye { fill: var(--card); }

.map__place,
.map__clock { position: relative; z-index: 1; }    /* above the map and its fades */
.map__place { display: flex; flex-direction: column; gap: 2px; }
.map__city { font-size: clamp(0.9rem, 9.2cqi, 1.18rem); font-weight: 600; letter-spacing: -0.01em; line-height: 1.25; }
.map__clock { display: flex; flex-wrap: wrap; align-items: center; column-gap: 6px; }
.map__time {
  font-size: clamp(1.1rem, 13cqi, 1.7rem);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.02em;
  line-height: 1;
}
.map__sky svg { display: none; width: clamp(16px, 10cqi, 22px); height: clamp(16px, 10cqi, 22px); }
.card--map[data-sky="day"] .map__sun,
.card--map[data-sky="night"] .map__moon { display: block; }
.map__sun circle { fill: var(--accent); }
.map__sun path { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; }
.map__moon path { fill: var(--ink-soft); }
.map__zone { flex-basis: 100%; margin-top: 4px; font-size: clamp(0.66rem, 5.8cqi, 0.8rem); color: var(--ink-soft); }

/* ----------------------------- Tools ------------------------------- */
/* The apps as their own icons in a row, each with its name under it, sized
   from the tile (as big as its width and height allow). */
.card--tools { justify-content: space-between; gap: 10px; }
.tools {
  list-style: none;
  margin: 0;
  padding: 0;
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: repeat(4, auto);
  justify-content: space-evenly;
  align-content: center;
}
.tool { display: flex; flex-direction: column; align-items: center; gap: clamp(6px, 1.8cqi, 10px); }
.tool__icon {
  display: block;
  width: min(17cqi, 50cqh);
  height: min(17cqi, 50cqh);
  border-radius: 23%;
  box-shadow: 0 0 0 1px var(--line), 0 2px 8px rgba(0, 0, 0, 0.16);   /* the ring keeps dark icons (Figma) apart from a dark card */
}
.tool__icon text { font: 700 19px/1 "Archivo", system-ui, sans-serif; letter-spacing: -0.5px; }
.tool__name { font-size: clamp(0.7rem, 2.7cqi, 0.9rem); font-weight: 600; line-height: 1.2; white-space: nowrap; }

/* ----------------------------- Experience (career) ----------------- */
/* Work and school in one timeline, newest first, as dots on a line: the role,
   then its years and where. Only what's happening now is green (live, like
   "open to new work"); degrees get a filled dot. The list fills the tile and
   fades out towards the bottom, so older entries show but step back; the full
   timeline (and the CV, once it's in) opens from here. */
.card--career { gap: 12px; }
.career {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
  list-style: none;
  margin: -6px 0 0 -6px;                       /* a 6px gutter, pulled back out, so the dots */
  padding: 6px 0 0 28px;                       /* and the "now" halo aren't clipped at the edge */
  -webkit-mask-image: linear-gradient(to bottom, #000 60%, transparent);
  mask-image: linear-gradient(to bottom, #000 60%, transparent);
}
.career::before {                              /* the line through the dots */
  content: "";
  position: absolute;
  left: 10px;
  top: 12px;
  bottom: 0;
  width: 2px;
  border-radius: 2px;
  background: var(--line);
}
.career__item {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-bottom: clamp(10px, 3.2cqi, 18px);
}
.career__item::before {                        /* the dot */
  content: "";
  position: absolute;
  left: -22px;
  top: 0.3em;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--card);
  box-shadow: inset 0 0 0 2px var(--ink-soft);
}
.career__item--now::before { background: var(--live); box-shadow: 0 0 0 4px var(--live-soft); }   /* live, like "open to new work" */
.career__item--degree::before { background: var(--ink); box-shadow: none; }          /* degrees: filled */
.career__role { font-size: clamp(0.88rem, 3.6cqi, 1.12rem); font-weight: 600; line-height: 1.25; }
.career__meta { font-size: clamp(0.74rem, 2.9cqi, 0.92rem); color: var(--ink-soft); line-height: 1.3; }
.career__when { font-weight: 600; font-variant-numeric: tabular-nums; }
.career__item--now .career__when { color: var(--ink); }
.career__more { font-size: clamp(0.72rem, 2.8cqi, 0.86rem); font-weight: 600; color: var(--ink-soft); }

/* ----------------------------- Projects ---------------------------- */
/* A project tile is only its cover, filling the whole tile (no title bar): an
   image or looping video as .project__cover, cropped to the tile's shape.
   Until real covers are in, placeholder images (assets/projects/placeholder-*.svg).
   The gradient only shows while a cover loads. When a project opens, its cover
   flies into the case study's frame (main.js). */
.card--project { padding: 0; }
.project__media {
  position: relative;
  flex: 1;
  min-height: 0;
  background: var(--cover-bg, linear-gradient(150deg, var(--card-2), var(--accent-soft)));
  overflow: hidden;
}
/* A project can set its own colours behind its cover (--cover-light and
   --cover-dark on the tile, for cut-out photos, e.g. Moen Marin's pale blue
   and near-black navy). The case study's frame and the cover's flight between them get the
   same (main.js copies them over). Without them it's the soft grey. */
.card--project, .case__frame, .media-flight { --cover-bg: var(--cover-light); }
:root[data-theme="dark"] :is(.card--project, .case__frame, .media-flight) { --cover-bg: var(--cover-dark); }

/* A project's logo, small in the tile's bottom-left corner: an inline SVG in
   currentColor, in the project's colours per theme (--logo-light and
   --logo-dark on it), else the text colour. It's only on the tile: it fades
   out as the tile opens (main.js), and the case study doesn't have it. */
.project__logo {
  position: absolute;
  left: clamp(14px, 4.5cqi, 22px);
  bottom: clamp(14px, 4.5cqi, 22px);
  z-index: 2;
  height: calc(clamp(13px, 4cqi, 20px) * var(--logo-scale, 1));   /* --logo-scale evens out logos that look smaller */
  width: auto;                                  /* from the SVG's own proportions */
  color: var(--logo-light, var(--ink));
  pointer-events: none;
}
:root[data-theme="dark"] .project__logo { color: var(--logo-dark, var(--ink)); }
.project__logo--top { top: clamp(14px, 4.5cqi, 22px); bottom: auto; }   /* when the cover fills the bottom-left */
.project__logo--right { right: clamp(14px, 4.5cqi, 22px); left: auto; } /* bottom-right, e.g. on a dark part of a photo */
.logo-flight { position: fixed; z-index: 121; margin: 0; }
.project__cover {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* ----------------------------- Button ------------------------------ */
.btn {
  display: inline-flex;
  align-items: center;
  padding: 12px 22px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--card);
  font-weight: 600;
  font-size: 0.92rem;
  transition: transform 0.15s ease;
  white-space: nowrap;
}
.btn:hover { transform: translateY(-2px); }

/* ----------------------------- Footer ------------------------------ */
.footer {
  max-width: var(--maxw);
  margin: 0 auto;
  padding: 24px clamp(16px, 4vw, 28px) 48px;
  display: flex;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  font-size: 0.85rem;
  color: var(--ink-soft);
}
.footer__credit { flex-basis: 100%; font-size: 0.75rem; }
.footer__credit:hover { text-decoration: underline; }

/* ----------------------------- Back to top ------------------------- */
.to-top {
  position: fixed;
  /* --sbc offsets the scrollbar that disappears while a tile is open */
  right: calc(24px + var(--sbc, 0px));
  bottom: 24px;
  width: 44px; height: 44px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--glass);
  color: var(--ink);
  cursor: pointer;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.10);
  opacity: 0;
  pointer-events: none;
  transform: translateY(8px) scale(0.9);
  transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.22, 1, 0.36, 1);
  z-index: 65;
}
.to-top.is-visible { opacity: 1; pointer-events: auto; transform: none; }
.to-top:hover { background: var(--card); }

/* ----------------------------- Frosted controls -------------------- */
/* Menu, theme toggle and back-to-top float over tiles, so they blur what's
   behind them. Small elements only; the full-screen overlay stays blur-free. */
.nav, .theme-toggle, .to-top {
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .nav, .theme-toggle, .to-top { background: var(--card); }
}

/* ----------------------------- Expandable cards -------------------- */
.is-expandable { cursor: pointer; }
.is-expandable:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; }

.expand-btn {
  position: absolute;
  top: 14px;
  right: 14px;
  z-index: 3;
  width: 34px;
  height: 34px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
  opacity: 0;
  transform: scale(0.8);
  transition: opacity 0.2s ease, transform 0.2s ease, background-color 0.2s ease;
  box-shadow: var(--shadow);
}
.is-expandable:hover .expand-btn,
.is-expandable:focus-visible .expand-btn,
.card--logo:not(.is-dimmed):hover .expand-btn,
.card--logo:focus-visible .expand-btn { opacity: 1; transform: scale(1); }
.expand-btn:hover { background: var(--card-2); }
/* On dark project cards the icon needs a light chip to stay visible */
.card--project .expand-btn { background: rgba(255, 255, 255, 0.9); color: #1a1a1a; border-color: transparent; }

/* ----------------------------- Overlay + sheet (popup) ------------- */
/* A tile grows into its popup and back (main.js, "Expandable modal"):
   - .sheet is the growing box: background, corners and shadow. main.js sets
     its width and height when it opens: on desktop the tile's own proportions
     scaled up as far as the screen allows (projects: one large card size for
     all), on tablets and phones a near-full-screen card.
   - .sheet__body gets the inverse scale while the box grows, so the content
     keeps its real size; it's also the container the popup layouts query.
   - .sheet__ghost is a copy of the tile that fades out (and back in on close).
   - .portrait-flight is the About portrait flying between tile and popup, and
     .media-flight a project's cover flying between tile and case study frame. */
.overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
}
.overlay[hidden] { display: none; }

.overlay__backdrop {
  position: absolute;
  inset: 0;
  /* Plain dim (no live backdrop-blur); faded in and out by main.js */
  background: rgba(0, 0, 0, 0.55);
}

.sheet {
  position: relative;
  background: var(--card);
  border-radius: 28px;
  box-shadow: var(--shadow-sheet);
  overflow: hidden;
  transform-origin: 0 0;
}
.sheet__body {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  transform-origin: 0 0;
  container: sheet / size;
}
.sheet__ghost.card {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 6;
  margin: 0;
  pointer-events: none;
  background: transparent;    /* the sheet behind it has the same colour */
  box-shadow: none;
}
.portrait-flight {
  position: fixed;
  z-index: 120;
  margin: 0;
  border-radius: 50%;
  overflow: hidden;
  pointer-events: none;
  transform-origin: 0 0;
}
.portrait-flight img { display: block; width: 100%; height: 100%; object-fit: cover; }
.media-flight {
  position: fixed;
  z-index: 120;
  margin: 0;
  overflow: hidden;
  pointer-events: none;
  transform-origin: 0 0;
  background: var(--cover-bg, var(--card-2));
}
.media-flight > * {
  position: absolute;
  top: 0;
  left: 0;
  display: block;
  max-width: none;
  transform-origin: 0 0;
}

.sheet__close {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 5;
  width: 38px;
  height: 38px;
  display: grid;
  place-items: center;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--card);
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--shadow);
  transition: transform 0.15s ease, background-color 0.2s ease;
}
.sheet__close:hover { transform: scale(1.06); background: var(--card-2); }

.sheet__content {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Hide the scrollbar while a tile is open (still scrollable) */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.sheet__content::-webkit-scrollbar { width: 0; height: 0; display: none; }

/* ----------------------------- Popup layouts ---------------------- */
/* A popup has its tile's shape (see "Overlay + sheet"), so its layout follows
   that shape: the "sheet" container (the popup body) is queried by aspect
   ratio. Wide popups (from wide tiles) put things side by side; square and
   tall ones stack them. Sizes use cqi / cqh, the popup's own width / height. */

/* ----------------------------- Detail: About ---------------------- */
.detail--about {
  min-height: 100cqh;          /* short content sits centred in the popup */
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: clamp(24px, 6cqi, 52px);
  text-align: center;
}
/* The portrait with "Open to new work" under it, as on the tile */
.detail__me { display: flex; flex-direction: column; align-items: center; gap: 14px; margin-bottom: 22px; }
.detail__avatar { width: 128px; height: 128px; overflow: hidden; background: none; border-radius: 50%; }
.detail__status { font-size: 0.9rem; }
@container sheet (min-aspect-ratio: 3 / 2) {
  .detail--about {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr);
    align-items: center;
    align-content: center;
    gap: clamp(28px, 5cqi, 64px);
    padding: clamp(32px, 6cqi, 72px);
  }
  .detail--about .detail__me { gap: 18px; margin: 0; }
  .detail--about .detail__avatar { width: min(48cqh, 260px); height: min(48cqh, 260px); }
  .detail--about .detail__prose { margin: 0; max-width: 60ch; }
}
.detail__avatar .avatar-img { object-position: center; }
.detail__prose { text-align: left; max-width: 52ch; margin: 0 auto; display: flex; flex-direction: column; gap: 18px; }
.detail__prose p { font-size: 1.06rem; line-height: 1.6; color: var(--ink-soft); }
.detail__prose strong { color: var(--ink); font-weight: 600; }
.hl { color: var(--accent); font-weight: 500; }

/* ----------------------------- Detail: Experience timeline -------- */
.detail--exp { padding: clamp(24px, 6cqi, 52px); }
/* Wide popup: Work and Education side by side */
@container sheet (min-aspect-ratio: 3 / 2) {
  .detail--exp .timeline {
    display: grid;
    grid-auto-flow: column;
    grid-template-rows: auto 1fr;
    grid-auto-columns: minmax(0, 1fr);
    column-gap: clamp(28px, 5cqi, 56px);
    row-gap: 8px;
  }
  .detail--exp .timeline__group { margin-top: 0; }
}
.exp__header { margin-bottom: 26px; }
.exp__title { font-size: clamp(1.6rem, 3vw, 2.2rem); margin-bottom: 6px; }
.exp__sub { color: var(--ink-soft); font-size: 0.95rem; }
.exp__cv { margin-top: 16px; }
.exp__cv[hidden] { display: none; }        /* .btn's display would override hidden */

.timeline { display: flex; flex-direction: column; gap: 8px; }
.timeline__group {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
  margin: 22px 0 6px;
}
.timeline__group:first-child { margin-top: 0; }

/* Vertical rule + node dots. Same rules as the Experience tile: only what's
   happening now is green (a filled dot with a soft ring, its dates in full
   ink); degrees get a filled dot in the text colour; everything else is a
   grey ring with grey dates. */
.tl { list-style: none; margin: 0; padding: 0 0 0 26px; position: relative; }
.tl::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 6px;
  bottom: 6px;
  width: 2px;
  background: var(--line);
  border-radius: 2px;
}
.tl__item { position: relative; padding: 0 0 24px; }
.tl__item:last-child { padding-bottom: 0; }
.tl__item::before {
  content: "";
  position: absolute;
  left: -26px;
  top: 5px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--card);
  border: 2px solid var(--ink-soft);
  box-shadow: 0 0 0 3px var(--card);
}
.tl__item--now::before { background: var(--live); border-color: var(--live); box-shadow: 0 0 0 4px var(--live-soft); }
.tl__item--degree::before { background: var(--ink); border-color: var(--ink); }        /* degrees: filled */
.tl__period {
  display: block;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--ink-soft);
  margin-bottom: 3px;
}
.tl__item--now .tl__period { color: var(--ink); }
.tl__role { font-size: 1.05rem; font-weight: 600; margin: 0; }
.tl__org { font-size: 0.88rem; color: var(--ink-soft); margin: 1px 0 6px; }
.tl__desc { font-size: 0.94rem; line-height: 1.55; color: var(--ink-soft); }

/* ----------------------------- Detail: Latest workout ------------- */
/* Laid out like the workout page on hevy.com: every exercise with its round
   picture, then SET / WEIGHT & REPS rows ("50kg x 10 reps"), every other row
   shaded, record sets marked with the gold medal. Same Hevy colours as the tile. */
.sheet:has(.hevy-skin) { background: var(--hv-bg); border-color: transparent; }

.workout-detail { padding: clamp(20px, 7cqi, 44px); }
.hevy-user--lg { padding-right: 46px; }             /* clears the close button */
.hevy-user--lg .hevy-user__avatar { width: 48px; height: 48px; }
.hevy-user--lg .hevy-user__name { font-size: 1.06rem; }
.hevy-user--lg .hevy-user__date { font-size: 0.94rem; white-space: normal; }
.workout-detail__title { margin-top: 18px; font-size: 1.3rem; font-weight: 600; letter-spacing: 0; }
.hevy-stats--lg { gap: 28px; margin-top: 12px; }
.hevy-stats--lg dd { font-size: 1rem; }

.workout-exercises {
  display: grid;
  gap: 26px;
  margin-top: 20px;
  padding-top: 22px;
  border-top: 1px solid var(--hv-line);
}
.workout-exercise__head { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
.workout-exercise__head .hevy-thumb { --size: 48px; }
.workout-exercise__title { font-size: 1.06rem; font-weight: 600; line-height: 1.3; letter-spacing: 0; }
.workout-exercise__notes { margin: -2px 0 10px; color: var(--hv-muted); font-size: 0.9rem; }

/* Set rows: number (or W / D / F) and "50kg x 10 reps", plus any records */
.workout-sets__head,
.workout-set {
  display: grid;
  grid-template-columns: 64px 1fr;
  align-items: center;
}
.workout-sets__head {
  padding-bottom: 6px;
  color: var(--hv-muted);
  font-size: 0.69rem;
  letter-spacing: 0.02em;
}
.workout-sets__head span:first-child,
.workout-set__n { text-align: center; }
.workout-set { min-height: 46px; padding: 8px 0; border-radius: 10px; }
.workout-set:nth-child(odd) { background: var(--hv-line); }   /* child 1 is the heading */
.workout-set__n { font-size: 1.06rem; font-weight: 600; }
.workout-set__n--warmup { color: #f5a200; }
.workout-set__n--dropset { color: #7a5af8; }
.workout-set__n--failure { color: #f04438; }
.workout-set__val {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
  font-size: 0.94rem;
  font-variant-numeric: tabular-nums;
}
.workout-set__record {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  color: var(--hv-muted);
  font-size: 0.81rem;
}

/* Your username links to your Hevy profile, "View on Hevy" to the workout */
a.hevy-user__name[href]:hover,
.workout-detail__profile:hover { text-decoration: underline; }
.workout-detail__profile { margin-left: auto; color: var(--hv-ink); font-weight: 600; white-space: nowrap; }
.workout-detail__source {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-top: 30px;
  padding-top: 18px;
  border-top: 1px solid var(--hv-line);
  color: var(--hv-muted);
  font-size: 0.82rem;
}

/* ----------------------------- Detail: Case study ----------------- */
/* Every project opens to the same large card (main.js, popupSize) with the
   same story-page layout, in one scrolling column: the title and one line, the
   cover in a rounded frame, the facts, then the story in a narrower reading
   column. The frame gets the tile's cover (copied by main.js, which flies it
   in from the tile). It's the column's width and 3:2, but never taller than
   62% of the card, so it's always in the first view (on short screens the
   cover is cropped a little wider). */
.case {
  width: min(100%, 900px);
  margin: 0 auto;
  padding: clamp(22px, 5cqi, 48px) clamp(22px, 5cqi, 48px) clamp(36px, 7cqi, 64px);
}
.case__head { padding-right: 48px; }          /* clears the close button */
.case__title {
  font-weight: 800;
  font-size: clamp(2rem, 5cqi, 3.2rem);
  line-height: 1.05;
}
.case__lede { margin-top: 8px; max-width: 60ch; color: var(--ink-soft); font-size: 1.05rem; line-height: 1.5; }

.case__frame {
  position: relative;
  width: 100%;                  /* stays full width when max-height kicks in */
  margin: clamp(18px, 3cqi, 28px) 0 0;
  aspect-ratio: 3 / 2;
  max-height: 62cqh;
  border-radius: 20px;
  overflow: hidden;
  background: var(--cover-bg, var(--card-2));
}
/* A hairline ring drawn over the image, so light covers keep an edge */
.case__frame::after,
.case__figure::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: inset 0 0 0 1px var(--line);
  pointer-events: none;
}

.case__facts {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 18px 24px;
  margin: clamp(20px, 3cqi, 28px) 0 0;
  padding-bottom: clamp(20px, 3cqi, 28px);
  border-bottom: 1px solid var(--line);
}
.case__facts dt {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.case__facts dd { margin: 6px 0 0; font-size: 0.98rem; font-weight: 500; line-height: 1.4; }
@container sheet (max-width: 560px) {
  .case__facts { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.case__story { max-width: 66ch; margin: 0 auto; }
.case__story section { margin-top: clamp(28px, 4cqi, 40px); }
.case__story h3 { font-size: 1.2rem; font-weight: 700; margin-bottom: 8px; }
.case__story p { color: var(--ink-soft); line-height: 1.65; }
.case__story p + p { margin-top: 12px; }

/* Images in the story: an <img> or <video> inside .case__figure, cropped to
   16:10. Without one, it's a dashed placeholder with a note of what goes there. */
.case__figure {
  position: relative;
  margin: clamp(24px, 3.5cqi, 34px) 0 0;
  aspect-ratio: 16 / 10;
  border-radius: 18px;
  overflow: hidden;
  background: var(--card-2);
}
.case__figure > img,
.case__figure > video { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
.case__figure:not(:has(img, video)) {
  display: grid;
  place-items: center;
  padding: 16px;
  text-align: center;
  color: var(--ink-soft);
  font-size: 0.85rem;
}
.case__figure:not(:has(img, video))::after { box-shadow: none; border: 1px dashed var(--line); }

/* Lock scroll when a sheet is open */
body.is-locked { overflow: hidden; }

/* ----------------------------- Reveal & filter animations ---------- */
/* Hidden start-state only applies when JS is running; otherwise tiles show. */
.js .card {
  opacity: 0;
  transform: translateY(16px) scale(0.98);
}
.card.is-visible {
  opacity: 1;
  transform: none;
  transition: opacity 0.5s ease, transform 0.5s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(var(--i, 0) * 55ms);
}
/* Filtered-out tiles stay in place but read as plainly "off" — muted & flat */
.card.is-dimmed {
  opacity: 0.5;
  filter: saturate(0.3) contrast(0.9);
  pointer-events: none;
  cursor: default;
  box-shadow: none;
}

/* ----------------------------- Responsive -------------------------- */
/* Tile shapes per layout come from the tile system above (data-shape*). */
@media (max-width: 900px) {
  .bento { grid-template-columns: repeat(2, 1fr); }
}

@media (max-width: 560px) {
  /* Two-column grid so small tiles pair up (and stay square via the cell unit) */
  .bento {
    grid-template-columns: repeat(2, 1fr);
    gap: 14px;
    padding: 0 18px;
  }
  .hero__name { font-size: 1.25rem; }

  .card--hevy { padding: 16px; }
  .sheet { border-radius: 24px; }
  .hevy-stats--lg { gap: 24px; }
}

/* Phones and narrow tablets (up to 640px): the header is one row that scrolls
   away, and the menu moves to a dock at the bottom. (640px, not the grid's
   560px, dates from when my name sat next to the logo and needed the room.) */
@media (max-width: 640px) {
  /* Top bar: the same logo and toggle as on desktop, in one row that scrolls
     away with the page. No sticky and no transform, so the fixed dock inside it
     stays anchored to the screen. */
  .topbar,
  .topbar.topbar--hidden {
    position: static;
    transform: none;
    grid-template-columns: minmax(0, 1fr) auto;
    pointer-events: auto;
  }

  /* Bottom dock: the menu, pinned and always visible. It spans the same 18px
     gutters as the logo and grid, and stretches into equal tabs. */
  .dock {
    position: fixed;
    left: 0;
    right: 0;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    z-index: 60;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 18px;
    pointer-events: none;
  }
  .nav { flex: 1; height: 56px; padding: 4px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18), var(--shadow); }   /* thumb-sized */
  .nav__tab { flex: 1; padding: 15px 12px; line-height: 1; font-size: 1rem; }

  /* Back-to-top sits just above the dock's right end */
  .to-top {
    right: 18px;
    bottom: calc(16px + env(safe-area-inset-bottom, 0px) + 56px + 12px);
  }

  /* Leave room so the last row / footer clears the dock */
  body { padding-bottom: 96px; }
}

/* Small phones: tighten the tabs so the menu fits a 320px screen */
@media (max-width: 360px) {
  .nav__tab { padding: 15px 8px; font-size: 0.95rem; }
}

/* ----------------------------- Reduced motion ---------------------- */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .card { opacity: 1; transform: none; }
  .wave { animation: none; }
  .status__dot { animation: none; }
  .card--gmail.is-copied .tile-action__check { animation: none; stroke-dashoffset: 0; }
  * { transition-duration: 0.01ms !important; }
}
