/* ==========================================================================
   Page-level composition: hero, page heads, prose, project detail, gallery
   ========================================================================== */

/* ==========================================================================
   Scroll reveal + view transitions
   ========================================================================== */

[data-reveal] {
  opacity: 0;
  transform: translateY(14px);
  transition:
    opacity var(--dur-reveal) var(--ease-out),
    transform var(--dur-reveal) var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
}

[data-reveal].is-revealed { opacity: 1; transform: none; }

/* Soft navigation: the content is already in the DOM, so it appears at once
   rather than fading up. Anything below the fold still animates on scroll. */
[data-reveal].is-instant,
[data-reveal].is-instant .split-word__inner { transition: none; }

/* If JS never runs, nothing should stay invisible. */
.no-js [data-reveal] { opacity: 1; transform: none; }

/* Cross-document/soft navigation transition. Progressive: browsers without
   support just swap instantly, which is still fast. */
@view-transition { navigation: auto; }

::view-transition-old(root) { animation: vt-out 160ms var(--ease-out) both; }
::view-transition-new(root) { animation: vt-in 260ms var(--ease-out) both; }

@keyframes vt-out { to { opacity: 0; transform: translateY(-6px); } }
@keyframes vt-in { from { opacity: 0; transform: translateY(8px); } }

/* The router marks the shell while a page is being fetched. */
.main.is-navigating { opacity: 0.55; transition: opacity var(--dur-fast) linear; }

/* ==========================================================================
   Hero
   --------------------------------------------------------------------------
   Deliberately asymmetric. The text column is much wider than the portrait
   column and the two are aligned to the top rather than centred, so the
   headline runs long and the portrait hangs below the fold of the eye line.
   At wide sizes the portrait is pulled up into the header's whitespace and
   the name plate overlaps its bottom edge — the grid gets broken in exactly
   one place, which is more effective than breaking it everywhere.
   ========================================================================== */

.hero {
  display: grid;
  grid-template-columns: minmax(0, 1.45fr) minmax(0, 0.55fr);
  gap: clamp(2rem, 5vw, 4rem);
  align-items: start;
  padding-block: clamp(0.5rem, 3vw, 2rem) clamp(2.5rem, 6vw, 4.5rem);
}

@media (max-width: 56rem) {
  .hero { grid-template-columns: 1fr; }
  .hero__portrait { order: -1; justify-self: start; }
}

.hero__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.hero__pulse {
  position: relative;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

.hero__pulse::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--accent);
  animation: pulse 2.6s var(--ease-out) infinite;
}

@keyframes pulse {
  0%   { transform: scale(1); opacity: 0.7; }
  70%  { transform: scale(3.4); opacity: 0; }
  100% { transform: scale(3.4); opacity: 0; }
}

/* The one piece of type on the site that is allowed to be enormous. Fraunces
   with WONK on, optical sizing picking the display cut, and negative tracking
   to pull the words into a solid block. */
.hero__headline {
  margin-block-start: var(--space-m);
  max-width: 17ch;
  font-size: var(--step-6);
  font-weight: 500;
  line-height: 0.94;
  letter-spacing: var(--tracking-display);
}

.hero__lede {
  margin-block-start: var(--space-m);
  max-width: 54ch;
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--ink-2);
}

.hero__lede code {
  padding: 0.12em 0.4em;
  border: var(--rule) solid var(--border);
  background: var(--surface-2);
  color: var(--accent);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.7rem;
  margin-block-start: var(--space-l);
}

/* ---- word-by-word headline entrance -------------------------------------- */
.split-word { display: inline-block; overflow: hidden; vertical-align: bottom; }

.split-word__inner {
  display: inline-block;
  transform: translateY(105%);
  transition: transform 820ms var(--ease-out);
  transition-delay: var(--word-delay, 0ms);
}

[data-split].is-revealed .split-word__inner { transform: translateY(0); }

/* ==========================================================================
   Portrait
   ========================================================================== */

.hero__portrait { position: relative; display: grid; justify-items: stretch; }

@media (min-width: 56.0625rem) {
  /* The one grid-breaking move: pull the plate up past the top of the text
     column so it sits against the header. */
  .hero__portrait { margin-block-start: -1.5rem; }
}

.portrait {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border: var(--rule) solid var(--ink);
  background: var(--surface-2);
}

.portrait--sm { width: min(16rem, 55vw); }

/* object-position is written by JS from the `data-focus` value in site.json,
   so the crop stays configurable without a CSP-blocked style attribute.
   This value is the fallback if that never runs. */
.portrait__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 22%;
  transition: transform var(--dur-slow) var(--ease-out);
}

.hero__portrait:hover .portrait__img { transform: scale(1.03); }

.portrait__ring { display: none; }
.portrait__glow { display: none; }

/* A name plate, riveted to the bottom edge of the photograph and overlapping
   it. Solid ink so it reads as a physical label rather than a caption. */
.portrait__badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  justify-self: start;
  margin-block-start: -1.4rem;
  margin-inline-start: -0.5rem;
  padding: 0.55rem 0.9rem;
  background: var(--ink);
  color: var(--bg);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  position: relative;
  z-index: 2;
}

.portrait__badge-dot {
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--accent);
  flex: none;
}

/* ==========================================================================
   Page heads
   ========================================================================== */

.page-head {
  padding-block: clamp(0.5rem, 3vw, 1.5rem) clamp(1.5rem, 4vw, 2.75rem);
  max-width: 60ch;
}

.page-head--center { max-width: 54ch; margin-inline: auto; text-align: center; }

.page-head--split {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 0.5fr);
  gap: var(--space-xl);
  align-items: start;
  max-width: none;
}

@media (max-width: 56rem) {
  .page-head--split { grid-template-columns: 1fr; }
  .page-head__portrait { order: -1; }
}

.page-head__eyebrow {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
}

.page-head__eyebrow::before {
  content: "";
  width: 1.6rem;
  height: 2px;
  background: var(--accent);
  flex: none;
}

.page-head--center .page-head__eyebrow { justify-content: center; }

.page-head__title {
  margin-block-start: var(--space-s);
  font-size: var(--step-5);
  font-weight: 500;
  line-height: 0.98;
  letter-spacing: var(--tracking-display);
}

.page-head__lede {
  margin-block-start: var(--space-m);
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--ink-2);
}

.page-head__lede a { color: var(--accent); }

.page-head__actions { margin-block-start: var(--space-l); }

/* ==========================================================================
   Prose
   --------------------------------------------------------------------------
   Set to a strict measure with the heading numbers hanging in the left
   margin at wide sizes, the way a numbered section head sits in a journal.
   ========================================================================== */

.section--prose { max-width: var(--measure); }

.prose { display: grid; gap: var(--space-xl); counter-reset: para; }

.prose__section { display: grid; gap: 0.9rem; }

.prose__heading {
  position: relative;
  font-size: var(--step-2);
  font-weight: 600;
  line-height: 1.15;
  scroll-margin-top: 6rem;
}

/* The hanging number. At narrow widths there is no margin to hang into, so
   it sits inline above the heading instead of overlapping the text. */
.prose__heading::before {
  counter-increment: para;
  content: "§" counter(para, decimal-leading-zero);
  display: block;
  margin-block-end: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  color: var(--accent);
}

@media (min-width: 64rem) {
  .prose__heading::before {
    position: absolute;
    inset-inline-start: -4.5rem;
    inset-block-start: 0.45em;
    margin-block-end: 0;
  }
}

.prose p { color: var(--ink-2); }

.prose p a { color: var(--accent); }

.ref-list {
  display: grid;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.72rem;
  line-height: 1.6;
  color: var(--ink-3);
}

.ref-list li { padding-inline-start: 1rem; border-inline-start: 2px solid var(--border); }

/* ==========================================================================
   Skills
   --------------------------------------------------------------------------
   A specification table, not a set of cards: hairline-ruled rows with the
   group name in the left column.
   ========================================================================== */

.skill-grid { display: grid; border-block-start: var(--rule) solid var(--border); }

.skill-group {
  display: grid;
  grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
  gap: var(--space-m);
  align-items: baseline;
  padding-block: var(--space-m);
  border-block-end: var(--rule) solid var(--border);
}

@media (max-width: 46rem) {
  .skill-group { grid-template-columns: 1fr; gap: 0.8rem; }
}

.skill-group__title {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--accent);
}

/* ==========================================================================
   CTA
   --------------------------------------------------------------------------
   An inverted block — ink background, paper text. The only full-bleed colour
   inversion on the site, so it lands hard at the bottom of the page.
   ========================================================================== */

.cta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-m);
  padding: clamp(1.75rem, 4vw, 3rem);
  background: var(--ink);
  color: var(--bg);
  transform:
    perspective(1200px)
    rotateX(var(--tilt-x, 0deg))
    rotateY(var(--tilt-y, 0deg));
  transition: transform var(--dur-base) var(--ease-out);
}

.cta__title {
  font-size: var(--step-3);
  font-weight: 500;
  line-height: 1.02;
  letter-spacing: var(--tracking-display);
}

.cta__lede {
  margin-block-start: 0.6rem;
  color: color-mix(in oklab, var(--bg) 72%, transparent);
  max-width: 44ch;
}

/* Inside the inverted block the primary button flips to paper-on-ink. */
.cta .button--primary { background: var(--accent); color: var(--accent-ink); }
.cta .button--primary::before { background: var(--bg); }
.cta .button--primary:hover { color: var(--ink); }

/* ==========================================================================
   Project detail
   ========================================================================== */

.project__head { max-width: 66ch; padding-block: clamp(0.5rem, 3vw, 1.5rem) 0; }

.project__breadcrumb {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-3);
}

.project__breadcrumb a { color: var(--card-accent, var(--accent)); text-decoration: none; }

.project__title {
  margin-block-start: var(--space-s);
  font-size: var(--step-5);
  font-weight: 500;
  line-height: 0.98;
  letter-spacing: var(--tracking-display);
}

.project__subtitle {
  margin-block-start: var(--space-m);
  font-size: var(--step-1);
  line-height: 1.55;
  color: var(--ink-2);
}

/* The fact strip reads as the specification block at the head of a datasheet:
   ruled top and bottom, mono throughout. */
.project__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s) var(--space-xl);
  margin-block-start: var(--space-l);
  padding-block: var(--space-s);
  border-block: var(--rule) solid var(--border);
}

.project__fact dt {
  font-family: var(--font-mono);
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--ink-3);
}

.project__fact dd {
  margin: 0.35rem 0 0;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--ink);
}

.project__head .chip-row { margin-block-start: var(--space-m); }
.project__links { display: flex; flex-wrap: wrap; gap: 0.6rem; margin-block-start: var(--space-m); }

/* ---- highlights ---------------------------------------------------------- */
.section--highlights { margin-block-start: clamp(2.5rem, 6vw, 4rem); }

.highlight-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  border-block-start: var(--rule) solid var(--border);
}

.highlight {
  position: relative;
  padding: var(--space-m) var(--space-m) var(--space-m) 0;
  border-block-end: var(--rule) solid var(--border);
}

.highlight + .highlight {
  padding-inline-start: var(--space-m);
  border-inline-start: var(--rule) solid var(--border);
}

@media (max-width: 46rem) {
  .highlight { padding-inline: 0; }
  .highlight + .highlight { padding-inline-start: 0; border-inline-start: none; }
}

.highlight__value {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-variation-settings: var(--wonk-on);
  font-size: var(--step-4);
  font-weight: 500;
  line-height: 0.95;
  letter-spacing: var(--tracking-display);
  color: var(--ink);
}

.highlight__unit {
  margin-inline-start: 0.25rem;
  font-family: var(--font-mono);
  font-size: 0.4em;
  font-weight: 500;
  letter-spacing: 0.05em;
  color: var(--card-accent, var(--accent));
}

.highlight__label {
  margin-block-start: 0.8rem;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-2);
  line-height: 1.5;
}

.highlight__detail {
  margin-block-start: 0.4rem;
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--ink-3);
}

/* ---- figures & gallery --------------------------------------------------- */
.project__hero, .project__figure { margin: 0; }

.project__hero-img, .project__figure-img {
  width: 100%;
  border: var(--rule) solid var(--border);
  background: var(--surface-2);
}

/* Figure captions are set like plate captions in a paper: mono, small, with
   the rule above tying them to the image. */
.project__hero figcaption,
.project__figure figcaption,
.gallery__item figcaption {
  margin-block-start: 0.7rem;
  padding-block-start: 0.6rem;
  border-block-start: var(--rule) solid var(--border);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  line-height: 1.6;
  color: var(--ink-3);
}

.gallery {
  display: grid;
  gap: var(--space-m);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
  align-items: start;
}

.gallery__item {
  margin: 0;
  transform:
    perspective(900px)
    rotateX(var(--tilt-x, 0deg))
    rotateY(var(--tilt-y, 0deg));
  transition: transform var(--dur-base) var(--ease-out);
}

.gallery__img {
  width: 100%;
  border: var(--rule) solid var(--border);
  background: var(--surface-2);
}

/* ==========================================================================
   Page-specific tweaks
   ========================================================================== */

.page-home .main { padding-block-start: clamp(3.5rem, 7vw, 5.5rem); }

.section--chart { margin-block-start: clamp(2.5rem, 6vw, 4rem); }
