/* Case-study components — shared by every project page.
   Lifted out of the original puregym stylesheet, plus the blocks the other
   case studies need. Chrome and type scale live in site.css; a page's own
   stylesheet sets --col and holds anything genuinely unique to it.

   Every page sets --col to the content width measured off the Framer original:
     puregym 835 · techatnyu 794 · clarusai 717 · povi 717 · the-dial 810 */

/* ---------- pinned table of contents ---------- */

/* Sits at the left edge, independent of the centred column, and rides at the
   vertical middle of the viewport. It is dropped entirely once the column
   would run into it. */
#toc {
  position: fixed;
  left: 40px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 5;
  display: none;
  flex-direction: column;
  gap: 27px;
  font-size: 16px;
}
#toc a {
  color: var(--caption);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.2s ease;
  /* case.js scales these from the pointer's proximity. Growing from the left
     edge keeps the column's one hard edge and stops items displacing each
     other — displacement would move the very centres the proximity maths
     reads, so the effect would feed back into itself. The vertical centre is
     fixed for the same reason, which is what lets those centres be cached. */
  transform-origin: left center;
}
#toc a:hover { color: var(--ink); }
/* scroll-spy: case.js marks whichever section is currently in view */
#toc a.is-active { color: var(--accent); }

.suit {
  font-family: Arial, sans-serif;
  font-size: 14px;
  margin-right: 4px;
}

@media (min-width: 1280px) {
  #toc { display: flex; }
}

/* ---------- the column ---------- */

/* No bottom padding: the next-project cards close the page flush against its
   bottom edge, the way they do on the original. */
main { padding-bottom: 0; }

.case {
  width: min(var(--col), calc(100% - 72px));
  margin: 0 auto;
  padding: 32px 0 0;
}

/* The hero opens the column, the title follows it. Every media wrapper below
   carries a top margin for the case where it sits between blocks of copy, and
   the one that leads the page has to give that back — the column's own padding
   is the whole gap above it. Stated once here rather than on each of the three
   wrappers, because which one leads is a per-page fact. */
.case > :first-child { margin-top: 0; }

/* The hero is the picture the carousel card was showing, and going back folds
   it into that card's photo well — so it takes the well's corner rather than
   the 12px the media wells further down the page use. The card draws a 24px
   radius into a 1016.663px-wide well (tmplPhotoClipPath in script.js), and the
   exit flight ends on that same fraction, so matching it here is what keeps the
   corner still across the whole trip instead of popping square at the end.

   The expression mirrors .case's own width rule, so it stays correct on a page
   whose column is 717px and on one whose column is 835px, and it keeps up when
   the viewport is what is doing the limiting. card-transition.js reads the
   radius off the element rather than assuming one, so the flight follows. */
.case > :first-child:not(.case-head) {
  --hero-radius: calc(min(var(--col), 100vw - 72px) * 0.02361);
  border-radius: var(--hero-radius);
  overflow: hidden;
}
.case > :first-child:not(.case-head) img,
.case > :first-child:not(.case-head) video { border-radius: inherit; }

/* And the header, no longer the thing that opens the page, needs the gap the
   hero used to take from it. A shade more than the media's own 40px: a title
   under a full-width image wants more air than an image under a paragraph. */
.case-head { margin-top: 48px; }

.case-head h1 { margin-bottom: 14px; }
.case-head .lede { color: rgb(41, 41, 41); }

/* Three across, wrapping to a second row when a page carries a fourth field
   (povi's Event, the-dial's Award). */
.meta {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 20px 0;
  margin: 48px 0 0;
}
.meta > div { min-width: 0; padding-right: 16px; }
.meta dt {
  font-family: "Play", sans-serif;
  font-size: 16px;
  color: var(--accent);
  margin-bottom: 7px;
  text-transform: uppercase;
}
.meta dd {
  margin: 0;
  font-size: 18px;
  color: var(--body);
}

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

.case-section { margin-top: 84px; }
.case-section .eyebrow { margin-bottom: 12px; }
.case-section .statement { margin-bottom: 18px; }
.case-section .body-copy { margin-bottom: 18px; }
.case-section .body-copy:last-child { margin-bottom: 0; }

/* Anchor targets clear the sticky top bar when jumped to from the TOC. */
.case-section { scroll-margin-top: 84px; }

/* ---------- media ---------- */

/* Photo corners — the standard for every case-study page.
   ---------------------------------------------------------------------------
   Every content photograph and clip gets a slight rounding via --photo-radius.
   New pages should not invent a second value: set --photo-radius here if the
   site ever moves, and every shot, row and reel picks it up.

   Already rounded elsewhere and left alone:
     · the hero well (.case > :first-child) — card-well radius, not this token
     · .frame and .embed — 12px wells with overflow:hidden
     · .card, .split — 10px caption wells with overflow:hidden

   Exceptions (handled outside this block):
     · Clarus AI's .strip — photos butted edge to edge; only the outer corners
       of the stack are rounded, in clarusai.css
     · polaroids (About Me, BMW) — photo-viewer.css; photobooth strips
       and the hero fan — aboutme.css

   Polaroids are never rounded with this token. Do not add .strip img here. */
.case { --photo-radius: 6px; }

.shot img,
.shot video,
.media-row img,
.media-row video,
.reel img,
.reel video { border-radius: var(--photo-radius); }

/* Every visual runs the full width of the column; the artwork carries its own
   background, so no panel styling is needed around it. */
.shot { margin: 28px 0 0; }
.shot img,
.shot video { width: 100%; height: auto; }
.shot-hero { margin: 40px 0 0; }

/* n-up rows. Set --n for the count; defaults to 2. The 10px gutter is the
   one the original uses for every multi-up row, at every column width. */
.media-row {
  display: grid;
  grid-template-columns: repeat(var(--n, 2), minmax(0, 1fr));
  gap: 10px;
  margin: 28px 0 0;
  align-items: start;
}
.media-row img,
.media-row video { width: 100%; height: auto; }

/* One continuous sheet — the panels butt together with no gap, exactly as the
   grey background runs unbroken on the original. */
.reel {
  margin-top: 44px;
  font-size: 0;      /* kills the inline-level whitespace between panels */
}
.reel img,
.reel video {
  display: block;
  width: 100%;
  height: auto;
}

/* A rounded media well. --frame-bg carries the letterbox colour behind clips
   that do not fill their slot (black for the video heroes, light grey for
   the-dial's). */
.frame {
  position: relative;
  margin: 40px 0 0;
  border-radius: 12px;
  overflow: hidden;
  background: var(--frame-bg, rgb(247, 247, 247));
}
.frame img,
.frame video { display: block; width: 100%; height: auto; }

/* Responsive iframe well for the YouTube embeds. --ratio is height/width. */
.embed {
  position: relative;
  margin: 40px 0 0;
  border-radius: 12px;
  overflow: hidden;
  background: #000;
  padding-top: calc(var(--ratio, 0.5625) * 100%);
}
.embed iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* The ↗ button the-dial floats over its hero. Holds two copies of Carbon's
   arrow--up-right stacked in one grid cell: on hover the first leaves through
   the top-right corner while the second arrives from the bottom-left, so the
   arrow reads as travelling the way it points. The button clips both. */
.frame-link {
  position: absolute;
  top: 14px;
  right: 14px;
  display: grid;
  place-items: center;
  width: 36px;
  height: 36px;
  border-radius: 4px;
  background: #fff;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
  overflow: hidden;
}
.frame-link svg {
  grid-area: 1 / 1;          /* both copies share the one cell */
  width: 25px;
  height: 25px;
  fill: rgba(0, 0, 0, 0.6);
  transition: transform 0.34s cubic-bezier(.22, .61, .36, 1);
}
/* the incoming copy waits just outside the corner it enters from */
.frame-link svg:nth-of-type(2) { transform: translate(-125%, 125%); }
.frame-link:hover svg:nth-of-type(1),
.frame-link:focus-visible svg:nth-of-type(1) { transform: translate(125%, -125%); }
.frame-link:hover svg:nth-of-type(2),
.frame-link:focus-visible svg:nth-of-type(2) { transform: translate(0, 0); }

/* ---------- captioned media cards ---------- */

/* Used for the-dial's split panel and its micro-interaction gallery: a clip
   with a label bar sitting directly beneath it, all inside one rounded box. */
.cards {
  display: grid;
  grid-template-columns: repeat(var(--n, 2), minmax(0, 1fr));
  gap: 10px;
  margin: 28px 0 0;
  align-items: start;
}
.card {
  border: 1px solid var(--rule);
  border-radius: 10px;
  overflow: hidden;
}
.card > img,
.card > video { display: block; width: 100%; height: auto; }
/* margin:0 matters — this is a <p>, and the default paragraph margin would
   open a band of card background between the clip and its label, which reads
   as a gap and leaves the dark label bar detached from the dark footage. */
.card-label {
  margin: 0;
  padding: 15px 16px;
  font-size: 18px;
  line-height: 1.25;
  color: var(--body);
  background: #fff;
}
.card-label .sub {
  display: block;
  margin-top: 3px;
  font-size: 16px;
  font-weight: 300;
  font-style: italic;
  color: var(--caption);
}
/* the night-mode half of the split panel */
.card.is-dark .card-label { background: rgb(20, 20, 22); color: rgb(242, 242, 242); }
.card.is-dark .card-label .sub { color: rgb(190, 190, 195); }

/* The two halves of the split panel butt together with no gap or inner border,
   reading as one image cut down the middle. */
.split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  margin: 28px 0 0;
  border: 1px solid var(--rule);
  border-radius: 10px;
  overflow: hidden;
}
.split > .card { border: 0; border-radius: 0; }
/* The two clips have different native aspect ratios, but the original crops
   them to a shared height so the seam between the halves runs straight. */
.split > .card > video,
.split > .card > img {
  aspect-ratio: 405 / 389;
  object-fit: cover;
}

/* ---------- stat row ---------- */

/* clarusai's energy figures: label above, value below, centred, with a
   source note under the run. */
.stats {
  display: grid;
  grid-template-columns: repeat(var(--n, 3), minmax(0, 1fr));
  gap: 24px;
  margin: 44px 0 0;
  text-align: center;
}
.stat dt {
  font-size: 18px;
  line-height: 1.4;
  color: var(--body);
  margin-bottom: 14px;
}
.stat dd {
  margin: 0;
  font-size: 22px;
  line-height: 1.4;
  color: var(--ink);
}
.source {
  margin: 28px 0 0;
  font-size: 12px;
  color: rgb(154, 164, 190);
  text-align: center;
}
.source a { color: inherit; }

/* ---------- small text bits ---------- */

.bullets {
  margin: 4px 0 18px;
  padding-left: 22px;
  font-size: 18px;
  line-height: 1.65;
  color: var(--body);
}
.bullets li { margin-bottom: 2px; }

/* An emphasised phrase inside body copy — darker, not coloured. */
.mark {
  color: var(--ink);
  font-weight: 400;
}

.note {
  margin: 10px 0 0;
  font-size: 16px;
  font-weight: 300;
  color: var(--caption);
}

/* ---------- thanks ---------- */

.thanks {
  margin: 132px auto 0;
  width: min(691px, 100%);
  padding: 34px 24px 36px;
  border: 1px solid var(--rule);
  border-radius: 12px;
  background: rgb(252, 252, 252);
  text-align: center;
}
.thanks-title {
  font-family: "DM Sans", sans-serif;
  font-size: 30px;
  font-weight: 400;
  color: var(--ink);
  margin: 0 0 12px;
}
.thanks-blurb {
  width: min(278px, 100%);
  margin: 0 auto 8px;
  font-size: 16px;
  line-height: 1.45;
  color: rgb(196, 196, 196);
}
.thanks-mail {
  margin: 0;
  font-size: 18px;
  color: var(--ink);
}
.thanks-mail a { text-decoration: none; }
.thanks-mail a:hover { color: var(--accent); }

/* ---------- next project ---------- */

.next { margin-top: 172px; }
.next-label {
  margin: 0;
  font-size: 18px;
  color: var(--caption);
  text-align: center;
}

/* The two cards overlap and tilt away from each other, like a dealt pair.
   They sit low and run off the end of the document — the page stops partway
   down them rather than framing them fully. --card-w drives the clip so the
   proportion of card showing stays the same at every size. */
.next-cards {
  position: relative;
  display: flex;
  justify-content: center;
  /* hug the cards; stretching would give the slots a height of their own and
     make what gets clipped depend on the container rather than the card */
  align-items: flex-start;
  margin-top: 34px;
  --card-w: 205px;
  --card-h: calc(var(--card-w) * 246 / 179);   /* the thumbnail's aspect */
  /* the fraction is the knob: lower shows less of the card */
  padding-top: 30px;
  height: calc(30px + var(--card-h) * 0.88);
  overflow: hidden;
}
/* The slot carries the tilt so the card is free to lift straight up. */
.nc1 { transform: rotate(-6deg) translateX(26px); }
.nc2 { transform: rotate(6deg) translateX(-26px); }

/* Scrolling to the end deals the pair in: they start below the clip, fully
   out of sight, and slide up into it. 300px clears the container at both card
   sizes, so they are never caught half-visible at rest. Site.reveal staggers
   the second one behind the first. */
.next-slot {
  --rise: 300px;
  --rise-ms: 600;
  /* spring rather than ease: it overshoots the rest position slightly and
     settles back, which reads as the cards being dealt onto the page rather
     than slid onto it */
  --rise-spring: 1;
  /* these two replay: scroll back up off them and down again and they deal in
     a second time, instead of being spent the first time they are seen */
  --rise-replay: 1;
}

/* The thumbnails are the card faces themselves, exported from the carousel in
   both themes (see export-ui-cards.js), rather than flat artwork kept in step
   by hand — which is how the old ones drifted into showing POVI as #008 while
   the grid called it #004.

   They are backgrounds rather than <img> so the theme can pick between the two
   in CSS alone. An <img> would have to be swapped in script, which means the
   light one is already downloaded and painted before the swap happens; a custom
   property holding a url() that nothing uses is never fetched, so a dark
   visitor loads the dark file only, and there is no flash of the wrong one. The
   link carries the accessible name the <img> used to. */
.next-card {
  display: block;
  width: var(--card-w);
  height: var(--card-h);
  background: var(--card-img) center / cover no-repeat;
  border-radius: 8px;
  overflow: hidden;
  /* the original's three-layer stack — it does not change on hover */
  box-shadow: 0 0.602187px 0.602187px -0.666667px rgba(0, 0, 0, 0.12),
              0 2.28853px 2.28853px -1.33333px rgba(0, 0, 0, 0.13),
              0 10px 10px -2px rgba(0, 0, 0, 0.18);
  transition: transform 0.28s cubic-bezier(.34, 1.15, .64, 1);
}

/* One line per project: the light face, and the dark face beside it. */
.next-card[data-card="clarusai"] { --card-img: url('assets/cards/clarusai.jpg'); --card-img-dark: url('assets/cards/clarusai-dark.jpg'); }
.next-card[data-card="povi"] { --card-img: url('assets/cards/povi.jpg'); --card-img-dark: url('assets/cards/povi-dark.jpg'); }
.next-card[data-card="puregym"] { --card-img: url('assets/cards/puregym.jpg'); --card-img-dark: url('assets/cards/puregym-dark.jpg'); }
.next-card[data-card="techatnyu"] { --card-img: url('assets/cards/techatnyu.jpg'); --card-img-dark: url('assets/cards/techatnyu-dark.jpg'); }
.next-card[data-card="the-dial"] { --card-img: url('assets/cards/the-dial.jpg'); --card-img-dark: url('assets/cards/the-dial-dark.jpg'); }

:root[data-theme="dark"] .next-card { background-image: var(--card-img-dark); }

.next-card:hover { transform: translateY(-11px); }

/* ---------- card → page transition ---------- */

/* The overlay card-transition.js flies from the clicked carousel card into
   this page's hero and title. Under the top bar rather than over it: the bar
   belongs to the page you have arrived at, not to the card you left. Its two
   children are placed and animated entirely from JS — everything measured, so
   nothing about the flight is written down here. */
#card-transition {
  position: fixed;
  inset: 0;
  z-index: 9;
  pointer-events: none;
}
#card-transition > * {
  position: fixed;
  margin: 0;
  transform-origin: 0 0;
  will-change: transform, opacity;
}
/* cover, not 100% 100%: the card's artwork and the hero are different shapes,
   and the flight scales uniformly, so the box has to crop rather than squash.
   See the fitting in card-transition.js's play(). */
.ct-media {
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  overflow: hidden;
}

/* While the flight is in the air the stand-ins *are* the hero and the title,
   so the real ones are held back rather than showing through underneath. The
   class goes on in <head>, before the first paint, and comes off the moment
   the stand-ins hand over — or on card-transition.js's failsafe, so a page
   that never gets its flight still ends up whole. */
:root.card-transition .case > :first-child,
:root.card-transition .case-head h1 { opacity: 0; }

/* Going the other way needs nothing here. The hero and the title used to
   shrink back into the card they came from, with everything else clearing out
   ahead of them; leaving is a short fade of the whole page now, which
   card-transition.js does on the <body> itself. */

/* The hero and the title are the two things that flew in from the card, so
   they are also the way back to it. On the home page they could not be links —
   the card is drawn in WebGL and there is no element there to be one — but
   here they are ordinary DOM, so they get to be ordinary links: focusable,
   keyboard-operable, right-clickable, and showing their destination in the
   status bar like anything else.

   They inherit everything, because the point is that they do not look like
   links. What they were before is exactly what the flight lands on, and a
   colour or an underline would be a difference the arrival has to explain.
   The affordance is the cursor pill and the focus ring instead. */
.to-cards {
  color: inherit;
  text-decoration: none;
  border-radius: 4px;
}
.to-cards:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 4px;
}
.hero-home {
  display: block;
  border-radius: inherit;
  /* The wrapper must not become a second box in the hero's layout: the flight
     measures the hero itself, and an anchor with a line-height of its own
     would leave a few stray pixels underneath the media it wraps. */
  font-size: 0;
  line-height: 0;
}

/* povi and clarusai are deliberately not wrapped. Their heroes are YouTube
   embeds, and an anchor around an <iframe> never receives the click — the
   player inside it does — so the link would be dead while also swallowing
   play, pause and scrub. Their titles still link, and so does Home. */

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

@media (max-width: 760px) {
  .meta { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 24px 20px; }
  .case { padding-top: 20px; }
  .case-head { margin-top: 32px; }
  .case-section { margin-top: 64px; }
  .stats { grid-template-columns: minmax(0, 1fr); gap: 28px; }
  /* stacking keeps the small clips legible rather than shrinking them further */
  .cards, .media-row { grid-template-columns: minmax(0, 1fr); }
  .split { grid-template-columns: minmax(0, 1fr); }
  .thanks { margin-top: 96px; }
  .next { margin-top: 112px; }
}

/* Two 205px cards, overlapped and tilted, come to ~373px across — wider than a
   360px phone. Shrink them rather than clipping the sides; the height follows
   from --card-w, so the same slice runs off the bottom here as on desktop.

   The pair is wider than the sum of the cards: tilting each one grows its
   footprint, and the overlap only wins 52px back, so the pair measures about
   2.13 × --card-w − 52. Below ~420px that outgrows the column, and since
   .next-cards clips, a fixed width would shave the two outer edges instead of
   fitting. Sizing off the viewport keeps the whole pair inside: 46.9vw is the
   widest card that still fits, and the 18px leaves a visible margin either
   side. It stops at 165px so the cards never grow past the desktop step. */
@media (max-width: 420px) {
  .next-cards { --card-w: min(165px, calc(46.9vw - 18px)); }
}

@media (prefers-reduced-motion: reduce) {
  .next-card { transition: none; }
  /* park the second arrow out of sight rather than sliding either of them */
  .frame-link svg { transition: none; }
}

/* ---------- dark mode ---------- */

/* site.css restates the palette tokens for [data-theme="dark"], and everything
   in this file that reads one follows for free — the pinned #toc included,
   since its resting, hover and .is-active colours are --caption, --ink and
   --accent. What is left is the handful of places where a literal was the
   right call in a light-only stylesheet and is now the only thing that would
   stay light on a dark page. Each one is restated rather than tokenised, so
   the light rendering is untouched. */
:root[data-theme="dark"] {
  /* the letterbox behind clips that do not fill their well */
  --frame-bg: rgb(30, 30, 32);
}

/* The lede is set a hair off --ink in light to sit under the h1. Dark needs the
   same relationship, not the same number. */
:root[data-theme="dark"] .case-head .lede { color: rgb(222, 222, 222); }

/* The label bar under each clip is a white shelf. On a dark page that is the
   brightest thing on screen, and it is decoration. .card.is-dark — the night
   half of the-dial's split panel — is already dark by design and stays put in
   both themes, which is the whole point of that panel. */
:root[data-theme="dark"] .card-label { background: rgb(28, 28, 30); }

/* Same reasoning for the two remaining light surfaces: the thanks card's
   near-white fill, and the white chip the ↗ arrow sits in over the hero. */
:root[data-theme="dark"] .thanks { background: rgb(26, 26, 28); }
:root[data-theme="dark"] .thanks-blurb { color: rgb(122, 122, 122); }
:root[data-theme="dark"] .frame-link { background: rgb(38, 38, 41); }
:root[data-theme="dark"] .frame-link svg { fill: rgba(255, 255, 255, 0.7); }

/* The source note's blue-grey is close enough to the light accent to read as a
   link. Against the dark page it also drops under 4.5:1, so it is lifted here
   rather than left as the one unreadable line on the page. */
:root[data-theme="dark"] .source { color: rgb(146, 154, 176); }
