/* ============================================================================
   Motion grid — PROPOSAL
   A ruled grid of mixed-size tiles that breathes on hover. Loaded only by
   index.html (the home page), on top of site.css.
   ========================================================================= */

:root {
  --row: 11.5rem;            /* height of one grid row at rest            */
  --grid-ease: cubic-bezier(.65, 0, .25, 1);
  --grid-time: .6s;

  /* white either side of every rule, above the graphics, so nothing can
     touch a line: 2px white + 1px black + 2px white reads as one 5px band */
  --halo: 2px;

  /* the two ends of every title animation */
  --w-rest: 75;   --g-rest: 300;     /* narrow + light  */
  --w-hot: 100;   --g-hot: 800;      /* wide + bold     */
}

/* --- the grid -------------------------------------------------------------
   Dividers are 1px gaps showing the black background through, so the rules
   stay continuous however the tracks resize. The height is fixed, so growing
   one row squeezes the others instead of pushing the rest of the page down. */

.mgrid {
  display: grid;
  grid-template-columns: repeat(var(--cols, 6), 1fr);
  grid-template-rows: repeat(var(--rows, 5), 1fr);
  height: calc(var(--row) * var(--rows, 5) + (var(--rows, 5) - 1) * 1px);
  gap: 1px;
  border: var(--rule);
  background: var(--ink);
  transition:
    grid-template-columns var(--grid-time) var(--grid-ease),
    grid-template-rows    var(--grid-time) var(--grid-ease);
}

/* On desktop motion-grid.js positions every tile itself (see its section 2),
   so tiles can make room locally instead of stretching whole rows and
   columns. The CSS grid above is the resting layout it reads, and the
   fallback if scripts don't run. */
.mgrid.is-free { display: block; position: relative; }
.mgrid.is-free > .tile {
  position: absolute;
  transition:
    left   var(--grid-time) var(--grid-ease),
    top    var(--grid-time) var(--grid-ease),
    width  var(--grid-time) var(--grid-ease),
    height var(--grid-time) var(--grid-ease);
}

.tile {
  position: relative;
  min-width: 0;
  min-height: 0;
  background: var(--paper);
  container-type: size;      /* titles size themselves to their tile */
}
.tile.is-active, .tile.is-leaving { z-index: 5; }

/* The white halo along the tile's edges, drawn on top of whatever fills it.
   Together with the 1px black gap between tiles it makes each rule a 5px
   white band with the line down its middle. */
.tile__card::after {
  content: "";
  position: absolute;
  inset: 0;
  box-shadow: inset 0 0 0 var(--halo) var(--paper);
  pointer-events: none;
  z-index: 5;
}

/* the card is what actually moves in overlay mode; at rest it is the tile */
.tile__card {
  position: absolute;
  left: 0; top: 0;
  width: 100%; height: 100%;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--paper);
  transition:
    left   var(--grid-time) var(--grid-ease),
    top    var(--grid-time) var(--grid-ease),
    width  var(--grid-time) var(--grid-ease),
    height var(--grid-time) var(--grid-ease);
}
.mgrid[data-mode="overlay"] .tile.is-active .tile__card,
.mgrid[data-mode="both"]    .tile.is-active .tile__card {
  outline: 1px solid var(--ink);   /* reads as a panel lifted onto the grid */
}

/* --- video tiles ----------------------------------------------------------
   Fitted, like every video on the site: edge to edge, square corners. At
   rest it is cropped by its tile; growing the tile reveals more of it. */

.tile__media {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;          /* the resting zoom must not spill onto the bar */
  background-color: rgba(51, 71, 252, .05);
  background-image: repeating-linear-gradient(45deg, rgba(51, 71, 252, .1) 0 10px, transparent 10px 20px);
}
/* At rest a tile shows its thumbnail, cropped to fill the block.
   Hovered, the cropped thumbnail fades out and the whole graphic fades in,
   uncropped, held --inset away from the edges on white. The padding is what
   separates the active project from the ones around it. The video uses the
   same inset geometry, and appears over it once frames arrive.
   (.tile__full is a copy of the thumbnail made by motion-grid.js.) */
.tile__media .tile__thumb {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.04);
  /* black and white at rest; the hover cross-fades to the full graphic in
     colour, so the colour arrives as the tile opens */
  filter: grayscale(1);
  opacity: .85;
  transition:
    opacity .35s var(--ease),
    filter .35s var(--ease),
    transform var(--grid-time) var(--grid-ease);
}
.tile__media .tile__full,
.tile__media video {
  position: absolute;
  top: var(--inset);
  left: var(--inset);
  width: calc(100% - 2 * var(--inset));
  height: calc(100% - 2 * var(--inset));
  object-fit: contain;
  opacity: 0;
  transition: opacity .35s var(--ease);
}
.tile.is-active .tile__media { background: var(--paper); }
/* behind a thumbnail the cell is white, so 85% reads as a lighter grey,
   not as the placeholder hatching showing through */
.tile__media:has(.tile__thumb) { background: var(--paper); }
.tile.is-active .tile__media .tile__thumb { opacity: 0; transform: scale(1); filter: none; }
.tile.is-active .tile__media .tile__full { opacity: 1; }
/* the video only shows once it is actually playing */
.tile.is-playing .tile__media video { opacity: 1; }

/* The bar inside every project tile: its title on the left, its number on
   the far right. All black at rest; hovered, the bar turns blue and both go
   white and bold. */
.tile__bar {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: .6rem;
  /* tight at rest (4px around the title and number); hovered, it opens up
     to the full spacing */
  padding: var(--bar-rest, 4px);
  border-top: var(--rule);
  background: var(--paper);
  color: var(--ink);
  transition: background-color .3s var(--ease), color .3s var(--ease),
              padding var(--grid-time) var(--grid-ease);
  /* the same halo on the divider: white above the line, over the graphic,
     and white just below it (visible when the bar turns blue) */
  position: relative;
  z-index: 1;
  box-shadow: 0 calc(-1 * var(--halo)) 0 var(--paper), inset 0 var(--halo) 0 var(--paper);
}
.tile.is-active .tile__bar { background: var(--blue); color: var(--paper); padding: .45rem .7rem .55rem; }

.tile__no {
  flex: none;
  font-size: 1rem;
  font-variant-numeric: tabular-nums;
  font-variation-settings: "wdth" var(--w-rest), "wght" var(--g-rest);
  transition: font-variation-settings .55s var(--grid-ease);
}
.tile.is-active .tile__no { font-variation-settings: "wdth" var(--w-hot), "wght" var(--g-hot); }

/* --- variable type --------------------------------------------------------
   Every title is split into letters (motion-grid.js). Each letter animates
   width + weight on its own delay, which produces the wave; data-type="word"
   drops the delay so the whole word moves at once. */

.tt {
  font-family: var(--sans);
  font-optical-sizing: auto;
  line-height: .95;
  letter-spacing: -.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: clip;
  min-width: 0;
}
.tt .ch {
  display: inline-block;
  --w: var(--w-rest);
  --g: var(--g-rest);
  font-variation-settings: "wdth" var(--w), "wght" var(--g);
  transition: font-variation-settings .55s var(--grid-ease);
  transition-delay: calc(var(--i, 0) * 22ms);
}
.tt .sp { display: inline-block; width: .28em; }

.tile.is-active .tt .ch { --w: var(--w-hot); --g: var(--g-hot); }
.mgrid[data-type="word"] .tt .ch { transition-delay: 0s; }

/* At rest the title and number are trimmed to their letters — from the top
   of the capitals down to the bottom of the descenders — so the 4px of
   padding is measured from the letters themselves, and a g or p never runs
   into the rule. Hovered, the full line box (and full spacing) returns. */
.tile:not(.is-active) .tile__bar { align-items: center; }
.tile:not(.is-active) .tile__bar .tt,
.tile:not(.is-active) .tile__no { text-box: trim-both cap text; }
/* long titles are still cut off at the side in narrow tiles, but nothing is
   clipped above or below: ascenders, descenders and round overshoots stay
   whole even though they reach past the trimmed box */
.tile__bar .tt { overflow-x: clip; overflow-y: visible; }

/* every project title is the same size: 16 on the old canvas */
.tile__bar .tt { font-size: 1rem; line-height: 1.1; color: inherit; }

/* --- type tiles ----------------------------------------------------------- */

.tile--type .tile__card {
  justify-content: flex-end;
  padding: .9rem 1rem;
}

/* the big word reacts to the cursor anywhere over the grid */
.tile--word .tt {
  font-size: clamp(2rem, 30cqi, 11rem);
  color: var(--blue);
  line-height: .82;
}
.tile--word .tt .ch { transition: font-variation-settings .18s linear; transition-delay: 0s; }

.tile--label .tt { font-size: clamp(1.6rem, 34cqi, 6rem); color: var(--ink); }
.tile--label small {
  display: block;
  margin-top: .35rem;
  font-family: var(--note);
  font-style: italic;
  font-size: .9rem;
  color: var(--ink-mute);
}

.tile--text .tile__card { justify-content: space-between; }
.tile--text p {
  font-size: clamp(.95rem, 7cqi, 1.5rem);
  line-height: 1.15;
  font-variation-settings: "wdth" 85, "wght" 600;
  letter-spacing: -.01em;
}


/* --- project tiles --------------------------------------------------------
   Case studies: a thumbnail, and a link through to the full page. */
a.tile { color: inherit; text-decoration: none; display: block; }

/* --- identity tile --------------------------------------------------------
   The name and the role. motion-grid.js (section 2c) sets the arrangement,
   size, width and weight so the space around them is equal on all four
   sides, whatever shape the tile is in. What follows is the structure it
   drives, plus a plain fallback (before fonts load, and on phones).

   Each line element is trimmed to its ink with the fonts' real metrics
   (--n-* / --r-*), so a margin measured on the letters is a true margin. */
.tile--id {
  --id-pad: 1.4rem;
  --id-pad-r: var(--id-pad);
}
.tile--id .tile__card {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--id-pad) var(--id-pad-r) var(--id-pad) var(--id-pad);
}
.tile--id .id-block {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  row-gap: .12em;
  /* fallback size: fills the width with the name on one line */
  font-size: calc((100cqi - var(--id-pad) - var(--id-pad-r)) * .192);
}

.tile--id .tt {
  display: flex;
  /* the word space narrows with the letters: .2em condensed → .28em normal */
  column-gap: calc((.2 + .08 * (var(--id-w, 100) - 75) / 25) * 1em);
  row-gap: .1em;                  /* between stacked lines (N_GAP) */
  font-size: 1em;
  /* tight at normal width, opening up as the letters condense (0 at the
     narrowest), so condensed letters never run into each other */
  letter-spacing: calc((-.045 + .045 * (100 - var(--id-w, 100)) / 25) * 1em);
  overflow: visible;
}
.tile--id .tt .ch {
  --w: var(--id-w, 100);
  --g: var(--id-g, 800);
  /* optical size pinned, so width scales exactly with size (see the script) */
  font-variation-settings: "wdth" var(--w), "wght" var(--g), "opsz" 96;
  transition: none;               /* follows the tile frame by frame */
}

.tile--id .tile__role {
  display: flex;
  column-gap: .25em;
  row-gap: .16em;                 /* R_GAP */
  font-size: .388em;
  font-family: var(--serif);
  font-style: italic;
  /* optical size pinned (as above) — at 100, the same cut the script reads
     its height measurements from, so the maths matches what renders */
  font-variation-settings: "wght" 615, "opsz" 100;
  font-feature-settings: "smcp", "liga";
  letter-spacing: -.01em;
}

/* every line, trimmed to its ink */
.tile--id .nl,
.tile--id .rl { display: block; white-space: nowrap; }
.tile--id .nl {
  line-height: var(--n-lh, .8);
  margin: calc(var(--n-tt, 0) * -1em) 0 calc(var(--n-tb, 0) * -1em);
}
.tile--id .rl {
  line-height: var(--r-lh, 1.1);
  margin: calc(var(--r-tt, 0) * -1em) 0 calc(var(--r-tb, 0) * -1em);
}

/* the arrangements (numbers as in the script) */
.tile--id.id-v0 .id-block { flex-direction: row; align-items: flex-end; }
.tile--id.id-v0 .tile__role,
.tile--id.id-v2 .tt { flex-direction: column; }

/* --- after the grid: the about block, as on the old home page ----------- */
.home-after { margin-top: 0; }

/* --- proposal controls ----------------------------------------------------
   Not part of the design — a switcher so the options can be compared. */

.proto {
  position: fixed;
  left: 1rem;
  bottom: 1rem;
  z-index: 60;
  display: grid;
  gap: .5rem;
  padding: .7rem .8rem .8rem;
  border: var(--rule);
  background: var(--paper);
  font-size: .8rem;
  max-width: 22rem;
}
.proto__head { display: flex; justify-content: space-between; gap: 1rem; align-items: baseline; }
.proto__head b { font-variation-settings: "wght" 800; }
.proto__head a { color: var(--blue); text-decoration: underline; }
.proto__row { display: flex; align-items: center; gap: .35rem; flex-wrap: wrap; }
.proto__row > span { width: 4.6rem; color: var(--ink-mute); }
.proto button {
  font: inherit;
  padding: .25em .8em .3em;
  border: 1.5px solid var(--blue);
  border-radius: 999px;
  background: var(--paper);
  color: var(--blue);
  cursor: pointer;
}
.proto button[aria-pressed="true"] { background: var(--blue); color: var(--paper); }
.proto[data-collapsed] .proto__row { display: none; }

.mgrid-intro {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1rem;
  padding: 1.2rem 0 .8rem;
}
.mgrid-intro p { color: var(--ink-mute); font-size: .9rem; }

/* --- touch + small screens ------------------------------------------------
   No hover to drive anything, so the grid becomes a single column and the
   tile nearest the middle of the screen becomes "active" as you scroll —
   its title widens and its video plays. */

@media (max-width: 900px), (hover: none) {
  .mgrid {
    grid-template-columns: 1fr 1fr !important;
    grid-template-rows: none !important;
    grid-auto-rows: auto;
    height: auto;
  }
  .tile { grid-column: auto !important; grid-row: auto !important; container-type: inline-size; }
  .tile__card { position: relative; width: 100% !important; height: 100% !important; left: 0 !important; top: 0 !important; }
  .tile--video, .tile--word, .tile--id, .tile--project { grid-column: span 2 !important; }
  .tile--id { --id-pad: 1rem; }
  .mgrid .tile--id .tile__card { min-height: 9rem; }
  /* each tile takes its graphic's proportions (--ar, set by motion-grid.js),
     plus the margin — so the uncropped view has an even margin here too */
  .tile--video .tile__card, .tile--project .tile__card { aspect-ratio: auto !important; }
  .tile--video .tile__media, .tile--project .tile__media {
    flex: none;
    height: calc((100cqi - 2 * var(--inset)) / var(--ar, 1.333) + 2 * var(--inset));
  }
  .tile--word .tile__card { aspect-ratio: 16 / 7; }
  .tile--type .tile__card, .tile--text .tile__card { min-height: 9rem; }
  .proto { left: .5rem; right: .5rem; bottom: .5rem; max-width: none; }
}

@media (prefers-reduced-motion: reduce) {
  .mgrid, .tile__card, .tile__media video, .tt .ch { transition: none !important; }
}
