/* ================================================================
   COMPONENT: ABOUT + EXPERIENCE
   ================================================================ */


/* ── Bio ──────────────────────────────────────────────────────── */
.about__bio {
  font-family: var(--type-display-font);
  font-size: var(--type-display-size);
  line-height: var(--type-display-line);
  color: var(--text-body);
}

.about__bio p + p {
  margin-top: var(--sp-5);
}

/* The bio's company names (Clover / Fiserv / TikTok) are plain <em> and
   take the shared prose emphasis device — `css/components/emphasis.css`.
   `.about__bio-highlight` used to give them --accent + 500, which made
   this the only teal in body copy on the site and a third emphasis
   language alongside the case pages' <em> and <strong>. */


/* ── The record ───────────────────────────────────────────────── */
/* NO PLATE ON EITHER BLOCK. Experience and Education were two identical
   `--surface-alt` cards; Education came out first, and now Experience has too.
   What is left is a plain editorial record: label, then rows.

   THE ROWS READ AT THE WORK'S RANK. With the plate gone, `.exp-item__title`
   and `.edu-entry` returned to the 24px serif h4 tier and are now held
   deliberately identical to `.project-card__title` — see the title rule below.
   The other three devices are still doing their share: the act heading above
   ("Where I've been", 64px serif), the uppercase mono label on each block, and
   the ink — title in `--text-primary` against its date in `--text-tertiary`.
   The record and the work are the same person's two columns; they read level.

   NO TOP MARGIN ON THE FIRST BLOCK. `.exp-block` carried `margin-top: 40px`
   from when it sat *below the bio* inside `#about`. Once Record became its own
   act the block became the FIRST child of the right column, and that margin
   turned into a 40px drop — the right column started 40px below its own
   heading while About's started level with it. The margin now lives on the
   sibling combinator only, where it is a gap between blocks rather than an
   offset from the top of the column. */
.exp-block {
  padding: 0;
}

/* The uppercase mono label STAYS — it is the block's rank marker and, with the
   plate gone, one of the three things holding the hierarchy up. Tracking moved
   from `--tracking-wide` (0.06em) to the house `--tracking-label` (0.08em),
   which is what every other uppercase mono label on the site uses; it was the
   last one still on its own value. */
.exp-block__label {
  font-family: var(--type-caption-font);
  font-size: var(--type-caption-size);
  line-height: var(--type-caption-line);
  font-weight: var(--weight-medium);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  /* 14px teal label — the display accent is 2.7:1 on paper, so the label
     tier takes the ink-weight teal (4.9:1). Same hue, AA-legible. */
  color: var(--text-accent-ink);
  margin-bottom: var(--sp-4);
}

/* `.exp-block__rule` is GONE — see the row note below. A label and the list
   under it are separated by space, not by a line. */


/* ── Experience Item (accordion row) ─────────────────────────── */
/* NO LINES INSIDE THE PLATE. There were five in this section — one under
   each block label, one under each experience row — and the plate they sat
   in already does the containing. The separation ladder is explicit about
   this: crease between acts, TONE between components, SPACE between items.
   Rows are items. Five drawn rules stacked inside two cards made the record
   the heaviest block on a page whose thesis is subtraction.

   What separates the rows now: air, and the two-column read of the row
   itself (title in primary ink at the left edge, date in tertiary at the
   right). That horizontal structure is stronger than a hairline ever was —
   the rules were drawing a table the type had already built, the same
   mistake the fold-compare rows made. Do not put them back. */
/* ROW RHYTHM IS SHARED WITH EDUCATION, and grouping it is the point.
   The two blocks are the same shape — label, then rows — so the gap from a
   label to its first row has to be identical or the two labels read as
   different levels. It was not: `.exp-item` carried 24px of top padding and
   `.edu-entry` carried none, so EXPERIENCE sat 40px above its first title
   (16px label margin + 24px row padding) while EDUCATION sat 16px above its
   entry. Same label, same tier, 24px apart. The padding now belongs to
   whatever a row is in this section, not to the accordion specifically. */
.exp-item,
.edu-entry {
  padding: var(--sp-6) 0;   /* was sp-5 — with no plate, air does the work */
}

.exp-item {
  position: relative;
  cursor: pointer;
  /* Contains the hover frame's `z-index: -1`. `.project-card` gets its
     stacking context for free from `will-change: transform`; this row has no
     such property, so without `isolate` the frame would drop behind the
     section's own background and never be seen. */
  isolation: isolate;
}

/* THE HOVER FRAME — the same gesture as the card above, adapted to a row.
   `.project-card::before` is a `--surface-alt` mat that expands outward from
   `inset: 0` and fades in over `--dur-normal`/`--ease-out`; this is that mat,
   on the same tokens and the same timing, so the record answers the work
   instead of inventing a second hover language.

   HORIZONTAL ONLY. The card expands on all four sides because it stands alone
   in a grid cell. Rows are stacked flush — vertical expansion would put each
   frame under its neighbours. Sideways it has somewhere to go: the row's own
   24px of vertical padding is already the frame's height, and the bleed runs
   into the column margin, which is exactly what the card's does.

   This is NOT the plate coming back. The plate was a resting container and it
   stays gone (see the block note above); this surface exists only under the
   pointer. Same distinction the card draws.

   IT EXPANDS AND FADES — IT DOES NOT FILL. This ran the corner-origin
   `clip-path` fill for a while, on the argument that one hover gesture
   everywhere is more coherent than several. It is not, because the site has
   two hover FAMILIES and they are not the same thing:

     · **Chrome you press** — `.btn-pill`, `.social-link`, `.navbar__link`.
       Ink sweeps in from a corner and the label inverts. A button fills.
     · **Paper you point at** — `.project-card`, this row. A mat grows out
       past the object's edges and fades up. Paper lifts and catches light;
       it does not fill like a button.

   A record row belongs to the second family: its sibling is the project card,
   not the footer CTA. So the tokens here are the card's, exactly —
   `--surface-alt`, `--dur-normal`, `--ease-out`, `inset` growing outward
   alongside `opacity`. **`--dur-fill` (900ms) is deliberately NOT used**: it
   is the button family's length, and a 900ms mat on a row you are scanning
   past reads as lag. If this ever wants to be slower, `.project-card` has to
   move with it — the whole point is that the two are one gesture.

   HORIZONTAL ONLY, on the same reasoning as the static inset above: the card
   grows on all four sides because it stands alone in a grid cell, while rows
   are stacked flush and a vertical bleed would slide each mat under its
   neighbours. `--sp-4` sideways, `--gutter / 2` at ≤768. */
.exp-item::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: var(--radius-card);
  corner-shape: squircle;
  background: var(--surface-alt);
  opacity: 0;
  transition:
    inset   var(--dur-normal) var(--ease-out),
    opacity var(--dur-normal) var(--ease-out);
}

/* Keyboard gets the frame too. The `:focus-visible` ring (focus.css) lands on
   the inner button, so the two compose rather than collide. */
.exp-item:hover::before,
.exp-item:has(.exp-item__header:focus-visible)::before {
  inset: 0 calc(-1 * var(--sp-4));
  opacity: 1;
}

/* A real <button> — see `js/modules/accordion.js` for why. Everything below
   the display rules is un-styling the UA button so it reads as a row of the
   record, not as a control. `font: inherit` matters: without it the button
   would drop to the UA's 13px system font and the title tier would break. */
.exp-item__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: inherit;
  text-align: left;
}

/* THE RECORD READS AT THE SAME RANK AS THE WORK (Karl's call, 2026-08-12).
   This sat at 16px body for a stretch — the argument was that a job title is
   a record, not a destination, and that 24px PP Editorial New belonged to
   `.project-card__title` alone. It is back to the h4 tier, and it is now
   deliberately IDENTICAL to `.project-card__title`: same tokens, same
   `--tracking-tight`, same `--weight-regular`. If one of the two moves, move
   both — "accidentally similar" was the original complaint, and matching them
   on purpose is the answer to it, not diverging again.

   The ink still separates the title from its date (primary 13.9:1 vs tertiary
   5.1:1); that device did not go away, the serif joined it. `--weight-regular`
   is correct here and is NOT the body-weight violation — PP Editorial New has
   no 350 cut (CLAUDE.md §Typography).

   GROUPED WITH `.edu-entry__title` ON PURPOSE. Education is a one-line record
   and Experience is a list you open, but a row is a row — same tier, same ink.
   Grouping the selector is what makes "if one moves, move both" structural
   instead of a note somebody has to read. Same for the two `__date` rules. */
.exp-item__title,
.edu-entry__title {
  font-family: var(--type-h4-font);
  font-size: var(--type-h4-size);
  line-height: var(--type-h4-line);
  font-weight: var(--weight-regular);
  letter-spacing: var(--tracking-tight);
  color: var(--text-primary);
}

/* THE DATE SITS IN THE ROW, NOT IN THE BODY.
   It used to live inside `.exp-item__body-inner`, so a collapsed list showed
   four job titles and four `+` glyphs and nothing else — a record you had to
   click to read. Now it is the right-hand column of every row: the list is
   scannable shut, and the row gains the horizontal structure that replaced
   the rule. `margin-left: auto` rather than `justify-content: space-between`
   so the toggle stays pinned to the right of it. */
.exp-item__date,
.edu-entry__date {
  margin-left: auto;
  flex-shrink: 0;
  font-family: var(--type-body2-font);
  font-size: var(--type-body2-size);
  line-height: var(--type-body1-line);
  color: var(--text-tertiary);
}

/* SIZED OFF THE TITLE, not off a number — but 4/3 of it, not 1:1.
   A `+` is a small mark inside a large em box. Measured: at 24px its ink is
   11px tall, against the title's 19px cap height at the same size — so
   "matched font-size" reads as visibly smaller at the other end of the row.
   At 32px the mark inks 15px, which lands between the title's x-height (13)
   and its cap height (19). That is the optical match, and it is also the
   pairing this row had originally (32px toggle against a 24px h4 title),
   before the title was briefly demoted to body copy and the toggle followed it
   down to 20px.

   Expressed as a ratio so it tracks the title through every breakpoint:
   32px desktop/tablet, 24px at ≤768. Still a state indicator and not a control
   — `--weight-light` keeps it lighter than the serif it is matched to, and the
   whole row is the button. */
.exp-item__toggle {
  font-size: calc(var(--type-h4-size) * 4 / 3);
  font-weight: var(--weight-light);
  color: var(--text-primary);
  line-height: 1;
  flex-shrink: 0;
  transition: transform var(--dur-normal) var(--ease-out),
              color     var(--dur-normal) var(--ease-out);
  user-select: none;
}

/* Rotate to × when open */
.exp-item.is-open .exp-item__toggle {
  transform: rotate(45deg);
  /* Non-text UI mark — WCAG 1.4.11 wants 3:1. --accent is 2.7:1 on paper. */
  color: var(--accent-dk);
}

/* Accordion body — opens on `grid-template-rows: 0fr → 1fr`.
   This replaces a `max-height: 0 → 300px` transition. That number was a
   guess that had to exceed the tallest body, so a short row travelled only
   a third of the declared distance: on an ease-out curve it reached its real
   height in the first ~100ms and then sat still for the rest of the 320ms,
   and closing began with the same stretch of nothing happening. Rows of
   different lengths also opened at visibly different speeds. `1fr` resolves
   to the content's ACTUAL height, so the curve maps to the real distance and
   every row opens at the same rate. */
.exp-item__body {
  display: grid;
  grid-template-rows: 0fr;
  overflow: hidden;
  opacity: 0;
  transition:
    grid-template-rows var(--dur-normal) var(--ease-out),
    opacity            var(--dur-fast)   var(--ease-out);
}

/* Required for the `0fr` row to actually collapse: a grid item's automatic
   minimum size would otherwise hold the track open at its content height. */
.exp-item__body > * {
  min-height: 0;
  overflow: hidden;
}

.exp-item.is-open .exp-item__body {
  grid-template-rows: 1fr;
  opacity: 1;
}

/* The padding belongs to the OPEN state. `overflow: hidden` clips this item's
   CONTENT to nothing when the row collapses, but its own padding is box
   spacing, not content — it still contributes to the track's minimum, which
   left every closed row sitting 24px tall. Carrying the padding on the same
   toggle collapses it with the row and keeps the two in step. */
.exp-item__body-inner {
  padding: 0;
  transition: padding var(--dur-normal) var(--ease-out);
  font-family: var(--type-body1-font);
  font-size: var(--type-body1-size);
  line-height: var(--type-body1-line);
  color: var(--text-secondary);
}

.exp-item.is-open .exp-item__body-inner {
  padding: var(--sp-4) 0 var(--sp-2);
}


/* ── Education ────────────────────────────────────────────────── */
/* NO PLATE. Education is ONE line; Experience is a list you open. They were
   two identical `--surface-alt` shells stacked on each other, which is what
   made the section read as stiff — same container, same label, same radius,
   twice, for two things that are not alike. A plate hosts something you work
   through; a single fact does not need one.

   It sits at the plate's OUTER edge (no side padding), so the step out of the
   container is deliberate rather than a near-miss of the text inside it. */
/* BLOCK GAP HAS TO OUTRANK ROW GAP, and it stopped doing so once Education
   picked up the shared row padding. Rows inside Experience sit 48px apart
   (24 + 24 of padding). At `--sp-12` the block break measured 24 + 48 = 72px
   from the last row to the EDUCATION label — only 1.5× a row gap, for a
   boundary between two different kinds of record. `--sp-16` puts it at 88px,
   which is comfortably a level up. This is the "space between items, air
   between beats" ladder doing its job one rung higher. */
.edu-block {
  margin-top: var(--sp-16);   /* block gap, not card gap */
}

/* IT IS A COLLAPSED EXPERIENCE ROW. Same flex, same two columns, same tier and
   ink on each — it just has no body to open, so it has no toggle. The type and
   ink live on the grouped `.exp-item__title` / `.exp-item__date` rules above;
   all this rule does is build the row.

   THE INSTITUTION LEADS. It used to trail the degree, 77 characters in:
   *"BS. Integrated Design & Media, Minor in Computer Science — New York
   University"*. That was the only record ordered that way — every experience
   row leads with the org ("Clover, Senior Product Designer"), and so does the
   resume and the facts list in content-design.md. Trailing it also put the
   strongest fact where the line breaks, which is what stranded "University" and
   needed a `white-space: nowrap` patch on the name to hold it together. Both
   the patch and the wound are gone: a subject at the head of a line cannot be
   orphaned at the end of one.

   `align-items: center` matches `.exp-item__header`, so the date sits centred
   against a title that wraps — the same thing every row does at ≤768. */
.edu-entry {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
}

/* The reset's orphan control matches TAGS, not intent: `p, li, dd, dt,
   figcaption, blockquote` and the headings. This is a `<span>` in a `<div>`,
   so it inherits no policy and has to declare its own.

   `pretty`, not `balance`. It was `balance` while this was ONE run of text —
   there it set 183/183/179 at ≤768 against `pretty`'s short/long/short. The
   structural break below took that job over, and `balance` then had only the
   degree half to even out, which it did by breaking after the em dash:
   *"New York University —"* (158) over *"BS. Integrated Design & Media,"*
   (208), a hanging dash on a short line. With the break in the markup, `pretty`
   returns 183/183/179 and desktop is untouched (each half is one line there,
   so the policy has nothing to decide). **A wrap policy and a structural break
   solve the same problem; running both leaves the policy optimising a fragment
   rather than the phrase.** */
.edu-entry__title {
  text-wrap: pretty;
}

/* THE MINOR TAKES ITS OWN LINE. The break after "Integrated Design & Media" is
   structural, not a wrap that happened to land well: the entry is a degree and
   a minor, and that is where the sense divides. Two blocks rather than a `<br>`
   — the break then belongs to the markup's structure instead of being a
   character wedged into a sentence, and either half can still wrap on its own.

   It costs nothing at any measure the layout produces. The full string is
   ~776px and the widest title column is 761 (tablet, single-column) — it never
   fit on one line anyway, so this only fixes WHERE the line broke. At ≤768 the
   degree half wraps once more and the entry sets three lines, which is what it
   already did.

   NOT the banned `&nbsp;` patch, and not the same thing as orphan control: it
   states a division in the content, at every width, rather than nudging one
   break at one width. */
.edu-entry__degree,
.edu-entry__minor {
  display: block;
}

/* THE PROGRAM NAME IS ONE TOKEN, on the same grounds NYU was before the flip:
   a proper noun does not break. It is what stops "Integrated Design & Media"
   splitting after the ampersand, and it is why the line sets as whole clauses
   at every measure — institution+degree / program / minor. Measured 267px at
   24px and 189px at 17px, so it fits the narrowest column the layout produces
   (251px) with room to spare. Do NOT convert this to `&nbsp;`: that hard-codes
   one break for one width; this states a property of the phrase. */
.edu-entry__program {
  white-space: nowrap;
}

/* -- Responsive ------------------------------------------------- */
@media (max-width: 768px) {
  .about__bio {
    font-size: 16px;
    line-height: 24px;
  }

  .about__bio p + p {
    margin-top: 16px;
  }

  /* Was 24px, which sat BELOW the 36px row gap here — the block boundary read
     as tighter than the rows it was separating. 36px keeps it a level up on a
     breakpoint where every gap is compressed. */
  .edu-block {
    margin-top: 36px;
  }

  .exp-item,
  .edu-entry {
    padding: 18px 0;
  }

  /* Half the side spacing, centred in the gutter — the card's own mobile rule.
     The desktop 16px would put the frame's edge exactly on the screen edge
     here, because `--gutter` IS 16px at this breakpoint. */
  .exp-item:hover::before,
  .exp-item:has(.exp-item__header:focus-visible)::before {
    inset: 0 calc(var(--gutter) / -2);
  }

  /* 17/22, not the token's 18/24 — `.project-card__title` overrides itself to
     17/22 here, and these two are held identical on purpose (see the desktop
     rule). Matching the token instead of the card would break the pairing at
     exactly one breakpoint. */
  .exp-item__title,
  .edu-entry__title {
    font-size: 17px;
    line-height: 22px;
  }

  /* No `.exp-item__toggle` override here — the toggle reads `--type-h4-size`,
     which layout.css already steps to 18px at this breakpoint. A literal 18
     would be the same number written twice and free to drift. */

  /* Must match the OPEN state selector, not the base — the base padding is 0
     so the collapsed row can reach zero height (see the desktop rule). */
  .exp-item.is-open .exp-item__body-inner {
    padding: 12px 0 6px;
  }

  .exp-block__label {
    margin-bottom: 8px;
  }

  /* `.edu-entry` needs no size rule of its own — it is a flex row now, and the
     type sits on `.edu-entry__title`, grouped with `.exp-item__title` above. */
}


/* ================================================================
   PHOTO CAROUSEL — auto-scrolling portrait images
   ================================================================ */

.photo-carousel {
  margin-top: var(--sp-10);
  width: 100%;
  height: 420px;              /* the squeeze band (panels fill this height) */
  overflow: hidden;
  user-select: none;
  -webkit-user-select: none;
}

.photo-carousel__track {
  display: flex;
  gap: 0;
  height: 100%;
  width: 100%;                       /* panels are normalised by JS to fill exactly */
  will-change: contents;
}

.photo-carousel__item {
  flex: none;
  position: relative;               /* crops the centred full-width image */
  height: 100%;
  width: 25%;                        /* placeholder; carousel.js drives the squeeze width */
  border-radius: var(--radius-card);
  corner-shape: squircle;            /* Apple continuous corner */
  overflow: hidden;
}

/* Internal expansion: the image is rendered at the FULL showcase width
   (set by carousel.js) and centred, so a narrow strip shows its centre and
   widening it opens the image out from the middle — not a re-crop. */
.photo-carousel__item img {
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  height: 100%;
  width: auto;                       /* carousel.js sets this to the full width */
  max-width: none;
  object-fit: cover;
  display: block;
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
}

/* -- Responsive ------------------------------------------------- */
@media (max-width: 1024px) {
  .photo-carousel { height: 340px; }
}

@media (max-width: 768px) {
  .photo-carousel {
    height: 240px;
    margin-top: 28px;
    /* Aligns with the section gutter on both sides (no full-bleed) — the old
       negative margins only shifted it left, leaving it misaligned. */
  }
}
