/* ==========================================================================
   Components
   --------------------------------------------------------------------------
   Interactive state is driven by custom properties that JavaScript writes with
   style.setProperty() — --mx/--my for pointer position, --tilt-x/--tilt-y for
   the 3D tilt. That keeps every animation in the compositor and keeps the
   markup free of style attributes, which the CSP does not allow.

   The vocabulary is consistent across every component here:

     surface   flat, hairline-bordered, square. No blur, no rounding.
     label     mono, uppercase, letterspaced. Always.
     value     Fraunces, large, optically sized. Numbers are the point.
     accent    a 2px rule that appears on hover — a state change, not a glow.
   ========================================================================== */

/* ==========================================================================
   Buttons
   --------------------------------------------------------------------------
   Square, mono, uppercase. The primary fills with the accent; the ghost is a
   hairline box that fills on hover. Both wipe rather than fade — a wipe reads
   as mechanical, which is the register this design is in.
   ========================================================================== */

.button {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1.35rem;
  border: var(--rule) solid transparent;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  isolation: isolate;
  transform: translate3d(var(--magnet-x, 0), var(--magnet-y, 0), 0);
  transition: transform var(--dur-base) var(--ease-spring),
              border-color var(--dur-base) var(--ease-out),
              color var(--dur-base) var(--ease-out);
}

.button svg { width: 0.95rem; height: 0.95rem; transition: transform var(--dur-base) var(--ease-spring); }
.button:hover svg { transform: translateX(4px); }

.button--sm { padding: 0.6rem 1rem; }

/* Primary: solid accent, inverted on hover to the ink block. A hard swap of
   two flat colours rather than a tint shift. */
.button--primary {
  color: var(--accent-ink);
  background: var(--accent);
}

.button--primary::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--ink);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-base) var(--ease-out);
}

.button--primary:hover { color: var(--bg); }
.button--primary:hover::before { transform: scaleX(1); }
.button--primary:active { transform: translate3d(var(--magnet-x, 0), calc(var(--magnet-y, 0px) + 1px), 0); }

.button--ghost {
  color: var(--ink);
  border-color: var(--border-strong);
  background: transparent;
}

.button--ghost::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--accent-soft);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-base) var(--ease-out);
}

.button--ghost:hover { border-color: var(--accent); color: var(--ink); }
.button--ghost:hover::before { transform: scaleX(1); }

/* ==========================================================================
   Cards
   --------------------------------------------------------------------------
   Flat paper panels. The old design floated frosted glass over a gradient;
   these sit on the page like a pasted-in plate, and lift by 3px on hover.
   ========================================================================== */

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  border: var(--rule) solid var(--border);
  background: var(--surface);
  overflow: hidden;
  isolation: isolate;
  transform-style: preserve-3d;
  transform:
    perspective(900px)
    rotateX(var(--tilt-x, 0deg))
    rotateY(var(--tilt-y, 0deg))
    translate3d(0, var(--lift, 0px), 0);
  transition: transform var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

/* Hover marks the card with a solid accent rule along its top edge — a
   definite state change rather than a soft glow following the cursor. */
.card::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  z-index: 3;
  height: 2px;
  background: var(--card-accent, var(--accent));
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-base) var(--ease-out);
  pointer-events: none;
}

.card:hover,
.card:focus-within {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-m);
  --lift: -3px;
}

.card:hover::before,
.card:focus-within::before { transform: scaleX(1); }

/* The whole card is clickable via a stretched link, so the text stays
   selectable and the accessible name comes from the heading. */
.card__link { position: absolute; inset: 0; z-index: 4; }
.card__link:focus-visible { outline-offset: -4px; }

.card__media {
  position: relative;
  overflow: hidden;
  background: var(--surface-2);
  border-bottom: var(--rule) solid var(--border);
}

.card__image {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  /* Overridden per project from `hero.focus` in the content JSON, applied by
     PortraitFocus.js so no style attribute is needed. */
  object-position: center;
  filter: var(--card-image-filter);
  transition: transform var(--dur-slow) var(--ease-out), filter var(--dur-slow) var(--ease-out);
}

/* Hovering brings the screenshot back to full strength — the damping is a
   resting state, not a permanent alteration of the image.

   No zoom: these thumbnails are screenshots and charts with text near the
   edges, and scaling them up crops the axis labels right off. */
.card:hover .card__image,
.card:focus-within .card__image { filter: none; }

.card__sheen { display: none; }

.card__body {
  position: relative;
  z-index: 2;
  display: flex;
  flex-direction: column;
  gap: 0.65rem;
  padding: var(--space-m);
  flex: 1;
}

.card__eyebrow {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  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);
}

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

.card__title {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-variation-settings: var(--wonk-on);
  font-size: var(--step-3);
  font-weight: 600;
  line-height: 1.05;
}

.card__title--sm { font-size: var(--step-1); font-variation-settings: var(--wonk-off); }

.card__subtitle { color: var(--ink-2); font-size: var(--step-0); }
.card__summary { color: var(--ink-3); font-size: var(--step--1); }

.card__role {
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--card-accent, var(--accent));
}

.card__cta {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  margin-block-start: auto;
  padding-block-start: 0.6rem;
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--card-accent, var(--accent));
}

.card__cta svg { width: 0.9rem; height: 0.9rem; transition: transform var(--dur-base) var(--ease-spring); }
.card:hover .card__cta svg { transform: translateX(5px); }

/* ---- repo cards ---------------------------------------------------------- */
.card--repo { gap: 0.65rem; padding: var(--space-m); }

.card__repo-head { display: flex; align-items: center; gap: 0.6rem; flex-wrap: wrap; }

.card__repo-icon {
  display: grid;
  place-items: center;
  width: 1.7rem; height: 1.7rem;
  background: var(--card-accent, var(--accent));
  color: var(--accent-ink);
  flex: none;
}

.card__repo-icon svg { width: 0.95rem; height: 0.95rem; }

.card__repo-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem 0.9rem;
  margin-block-start: auto;
  padding-block-start: 0.7rem;
  border-top: var(--rule) solid var(--border);
  font-family: var(--font-mono);
  font-size: 0.68rem;
  letter-spacing: 0.05em;
  color: var(--ink-3);
}

.meta-item { display: inline-flex; align-items: center; gap: 0.35rem; }

.lang-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--card-accent, var(--accent));
}

.meta-item--go {
  margin-inline-start: auto;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--card-accent, var(--accent));
}

.meta-item--go svg { width: 0.8rem; height: 0.8rem; transition: transform var(--dur-base) var(--ease-spring); }
.card:hover .meta-item--go svg { transform: translateX(3px); }

.badge {
  padding: 0.18rem 0.45rem;
  border: var(--rule) solid currentColor;
  font-family: var(--font-mono);
  font-size: 0.6rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.badge--case { color: var(--card-accent, var(--accent)); }
.badge--muted { color: var(--ink-3); }

/* ==========================================================================
   Chips
   --------------------------------------------------------------------------
   Component labels off a parts bin: mono, square, hairline.
   ========================================================================== */

.chip-row { display: flex; flex-wrap: wrap; gap: 0.35rem; }

.chip {
  padding: 0.28rem 0.6rem;
  border: var(--rule) solid var(--border);
  background: transparent;
  font-family: var(--font-mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  color: var(--ink-2);
  white-space: nowrap;
}

.chip--sm { font-size: 0.64rem; padding: 0.2rem 0.5rem; }

.chip--interactive {
  cursor: default;
  transition: border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}

.chip--interactive:hover {
  color: var(--accent-ink);
  border-color: var(--accent);
  background: var(--accent);
}

/* ==========================================================================
   Stat tiles
   --------------------------------------------------------------------------
   The signature element. Four measured values, set enormous in Fraunces with
   the optical-size axis doing the work, over a mono label. They read as
   gauges on a panel — which is exactly what they are: the numbers that came
   out of the benchmark.

   The tiles are divided by hairlines rather than boxed, so the row reads as
   one instrument with four readouts instead of four separate cards.
   ========================================================================== */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  border-block: var(--rule) solid var(--border);
}

.stat {
  position: relative;
  padding: var(--space-m) var(--space-m) var(--space-m) 0;
  transition: background-color var(--dur-base) var(--ease-out);
}

/* Hairline dividers between tiles, drawn on the element rather than as a grid
   gap so the outer edges stay flush with the text column. */
.stat + .stat {
  padding-inline-start: var(--space-m);
  border-inline-start: var(--rule) solid var(--border);
}

.stat::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 2px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-slow) var(--ease-out);
}

.stat:hover::after { transform: scaleX(1); }

/* Proportional figures on purpose: tabular-nums makes a large number look
   loose at display sizes. Tabular is reserved for the chart's table view. */
.stat__value {
  font-family: var(--font-display);
  font-optical-sizing: auto;
  font-variation-settings: var(--wonk-on);
  font-size: var(--step-5);
  font-weight: 500;
  line-height: 0.92;
  letter-spacing: var(--tracking-display);
  color: var(--ink);
  font-variant-numeric: lining-nums;
}

.stat__label {
  margin-block-start: 0.85rem;
  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;
}

.stat__detail {
  margin-block-start: 0.4rem;
  font-size: 0.8rem;
  line-height: 1.55;
  color: var(--ink-3);
  max-width: 30ch;
}

@media (max-width: 46rem) {
  .stat { padding-inline: 0; }
  .stat + .stat {
    padding-inline-start: 0;
    padding-block-start: var(--space-m);
    border-inline-start: none;
    border-block-start: var(--rule) solid var(--border);
  }
}

/* ==========================================================================
   Section headings
   --------------------------------------------------------------------------
   Numbered like sections in a paper. The counter is incremented here rather
   than in the markup, so adding or reordering a section on a page renumbers
   the rest automatically and no template has to hardcode "02".
   ========================================================================== */

.section-head { margin-block-end: var(--space-l); max-width: 54ch; }

.section-head__eyebrow {
  display: inline-flex;
  align-items: baseline;
  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;
  margin-block-end: 0.9rem;
}

.section-head__eyebrow::before {
  counter-increment: sec;
  content: counter(sec, decimal-leading-zero);
  color: var(--accent);
  font-weight: 700;
  letter-spacing: 0.04em;
}

.section-head__title {
  font-size: var(--step-4);
  font-weight: 600;
  line-height: 1.02;
}

.section-head__lede {
  margin-block-start: 0.9rem;
  color: var(--ink-2);
  font-size: var(--step-0);
  max-width: 52ch;
}

.section-more { margin-block-start: var(--space-l); }

.section-note {
  margin-block-start: var(--space-m);
  font-family: var(--font-mono);
  font-size: 0.7rem;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}

/* ==========================================================================
   Card grids
   ========================================================================== */

.card-grid { display: grid; gap: var(--space-m); }

.card-grid--feature { grid-template-columns: repeat(auto-fit, minmax(min(100%, 25rem), 1fr)); }

/* A single card in a feature grid (the "next project" slot) would otherwise
   stretch edge to edge and blow its thumbnail up to full width. */
.card-grid--feature:has(> .card:only-child) { max-width: 34rem; }
.card-grid--repos { grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr)); }

/* ==========================================================================
   Command palette
   ========================================================================== */

.palette { position: fixed; inset: 0; z-index: 300; display: grid; place-items: start center; }

.palette__scrim {
  position: absolute;
  inset: 0;
  background: color-mix(in oklab, var(--bg-deep) 78%, transparent);
  backdrop-filter: blur(5px);
  -webkit-backdrop-filter: blur(5px);
  animation: fade-in var(--dur-fast) linear;
}

@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.palette__panel {
  position: relative;
  width: min(34rem, calc(100vw - 2rem));
  margin-block-start: clamp(3rem, 14vh, 8rem);
  border: var(--rule) solid var(--ink);
  background: var(--surface);
  box-shadow: var(--shadow-l);
  overflow: hidden;
  animation: palette-in var(--dur-base) var(--ease-spring);
}

@keyframes palette-in {
  from { opacity: 0; transform: translateY(-14px) scale(0.97); }
  to   { opacity: 1; transform: translateY(0) scale(1); }
}

.palette__field {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  padding: 0.9rem 1rem;
  border-bottom: var(--rule) solid var(--border);
}

.palette__icon { width: 1rem; height: 1rem; color: var(--ink-3); flex: none; }

.palette__input {
  flex: 1;
  border: none;
  background: none;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  outline: none;
}

.palette__input::placeholder { color: var(--ink-3); }

.palette__esc {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  padding: 0.14rem 0.4rem;
  border: var(--rule) solid var(--border);
  color: var(--ink-3);
}

.palette__results { max-height: min(24rem, 50vh); overflow-y: auto; }

.palette__result {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  padding: 0.7rem 1rem;
  border-block-end: var(--rule) solid var(--border);
  text-align: start;
  text-decoration: none;
  color: var(--ink-2);
  cursor: pointer;
}

.palette__result[aria-selected="true"] {
  background: var(--accent);
  color: var(--accent-ink);
}

.palette__result-title { font-weight: 550; font-size: var(--step--1); }

.palette__result-kind {
  margin-inline-start: auto;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  opacity: 0.7;
}

.palette__empty {
  padding: 1.6rem;
  text-align: center;
  color: var(--ink-3);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  letter-spacing: 0.05em;
}

/* ==========================================================================
   Contact
   ========================================================================== */

.contact-grid {
  display: grid;
  gap: var(--space-m);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
}

.contact-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  padding: var(--space-l);
  border: var(--rule) solid var(--border);
  background: var(--surface);
  overflow: hidden;
  isolation: isolate;
  transform:
    perspective(900px)
    rotateX(var(--tilt-x, 0deg))
    rotateY(var(--tilt-y, 0deg));
  transition: transform var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              box-shadow var(--dur-base) var(--ease-out);
}

/* Same treatment as the project cards: an accent rule, not a glow. */
.contact-card::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: 0;
  z-index: 1;
  height: 2px;
  background: var(--card-accent, var(--accent));
  transform: scaleX(0);
  transform-origin: 0 50%;
  transition: transform var(--dur-base) var(--ease-out);
}

.contact-card:hover,
.contact-card:focus-within {
  border-color: var(--border-strong);
  box-shadow: var(--shadow-m);
}

.contact-card:hover::before, .contact-card:focus-within::before { transform: scaleX(1); }

.contact-card__icon {
  display: grid;
  place-items: center;
  width: 2.4rem; height: 2.4rem;
  margin-block-end: 0.4rem;
  background: var(--card-accent, var(--accent));
  color: var(--accent-ink);
}

.contact-card__icon svg { width: 1.15rem; height: 1.15rem; }

.contact-card__title {
  font-size: var(--step-2);
  font-weight: 600;
}

.contact-card__value {
  font-family: var(--font-mono);
  font-size: var(--step--1);
  color: var(--ink-2);
  word-break: break-word;
}

.contact-card__link {
  position: relative;
  z-index: 5;
  font-family: var(--font-mono);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--card-accent, var(--accent));
  text-decoration: none;
  border-bottom: var(--rule) solid color-mix(in oklab, var(--card-accent, var(--accent)) 55%, transparent);
}

.contact-card__note { font-size: 0.8rem; color: var(--ink-3); }

.contact-card__copy {
  position: relative;
  z-index: 5;
  align-self: start;
  margin-block-start: 0.6rem;
  padding: 0.42rem 0.8rem;
  border: var(--rule) solid var(--border);
  font-family: var(--font-mono);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  color: var(--ink-2);
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}

.contact-card__copy:hover { color: var(--ink); border-color: var(--border-strong); }
.contact-card__copy.is-copied { color: var(--card-accent, var(--accent)); border-color: currentColor; }

/* ==========================================================================
   Toast (copy confirmation, etc.)
   ========================================================================== */

.toast {
  position: fixed;
  left: 50%;
  bottom: 1.75rem;
  z-index: 400;
  padding: 0.7rem 1.1rem;
  border: var(--rule) solid var(--ink);
  background: var(--surface);
  box-shadow: var(--shadow-m);
  font-family: var(--font-mono);
  font-size: var(--step--2);
  font-weight: 500;
  letter-spacing: var(--tracking-wide);
  text-transform: uppercase;
  transform: translate(-50%, 140%);
  transition: transform var(--dur-base) var(--ease-spring);
}

.toast.is-visible { transform: translate(-50%, 0); }
