/* ============================================================================
   main.css — components and sections.
   Order follows the document: background art → header → hero → about →
   expertise → contact → footer.
   ========================================================================== */

/* ==========================================================================
   1. Background art
   The brief was that a flat black page felt blank. Two things fix that
   without adding weight: a slow aurora drift behind the content, and a
   fine grain over everything so large dark areas have texture instead of
   reading as an empty void. Both are pure CSS. Zero images, zero JS.
   ========================================================================== */

/* Film grain — an inline SVG feTurbulence, so it costs no network request.
   Fixed to the viewport so it never scrolls with content (a moving grain
   reads as a rendering bug). Very low opacity: it should be felt, not seen. */
body::after {
  content: '';
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0.05;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}

.aurora {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.aurora__blob {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  opacity: 0.5;
  will-change: transform;
}

.aurora__blob--1 {
  width: 46vw;
  height: 46vw;
  min-width: 380px;
  min-height: 380px;
  top: -12%;
  left: -8%;
  background: radial-gradient(circle, rgba(168, 85, 247, 0.72), transparent 66%);
  animation: drift-a 34s var(--ease) infinite alternate;
}

.aurora__blob--2 {
  width: 40vw;
  height: 40vw;
  min-width: 320px;
  min-height: 320px;
  top: 34%;
  right: -6%;
  background: radial-gradient(circle, rgba(99, 102, 241, 0.62), transparent 66%);
  animation: drift-b 41s var(--ease) infinite alternate;
}

.aurora__blob--3 {
  width: 34vw;
  height: 34vw;
  min-width: 260px;
  min-height: 260px;
  bottom: -14%;
  left: 32%;
  background: radial-gradient(circle, rgba(34, 211, 238, 0.26), transparent 68%);
  animation: drift-c 47s var(--ease) infinite alternate;
}

/* Long, prime-ish durations so the three never visibly sync up and the
   loop point is impossible to spot. transform + opacity only — these run
   on the compositor and never trigger layout or paint.                    */
@keyframes drift-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(14vw, 9vh, 0) scale(1.18); }
}

@keyframes drift-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-12vw, -11vh, 0) scale(0.92); }
}

@keyframes drift-c {
  from { transform: translate3d(0, 0, 0) scale(0.95); opacity: 0.4; }
  to   { transform: translate3d(-9vw, -7vh, 0) scale(1.25); opacity: 0.72; }
}

/* Every section sits above the aurora. */
.section > .wrap,
.hero__inner {
  position: relative;
  z-index: 2;
}

/* ==========================================================================
   2. Header
   ========================================================================== */
.header {
  position: fixed;
  inset-inline: 0;
  top: 0;
  z-index: 100;
  height: var(--header-h);
  display: flex;
  align-items: center;
  border-bottom: 1px solid transparent;
  transition: background-color var(--dur-mid) var(--ease),
              border-color var(--dur-mid) var(--ease),
              backdrop-filter var(--dur-mid) var(--ease);
}

/* .is-stuck is toggled from js/main.js once the page scrolls past the
   hero fold — the glass panel only appears when there's content behind it. */
.header.is-stuck {
  background: var(--c-glass);
  backdrop-filter: blur(14px) saturate(140%);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
  border-bottom-color: var(--c-line);
}

.header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  width: 100%;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  font-family: var(--f-display);
  font-weight: 700;
  font-size: 1rem;
  letter-spacing: -0.01em;
  color: var(--c-ink);
  text-decoration: none;
}

.brand__mark {
  display: grid;
  place-items: center;
  /* Width is intrinsic rather than a fixed 34px square: "PRG" is three
     glyphs and would have crowded the chip that "PG" fitted. */
  min-width: 40px;
  height: 34px;
  padding-inline: 7px;
  border-radius: 10px;
  background: var(--g-fill);
  color: var(--c-on-fill);
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  box-shadow: 0 6px 18px -8px rgba(168, 85, 247, 0.9);
}

.brand:hover {
  color: var(--c-violet);
}

.nav {
  display: flex;
  align-items: center;
  gap: var(--s-6);
}

.nav__list {
  display: flex;
  align-items: center;
  gap: var(--s-6);
  margin: 0;
}

.nav__link {
  position: relative;
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--c-ink-2);
  text-decoration: none;
}

/* Underline grows from the left rather than fading in — cheaper to animate
   (scaleX only) and reads more deliberately than an opacity change.        */
.nav__link::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: -6px;
  width: 100%;
  height: 2px;
  border-radius: 2px;
  background: var(--g-brand);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-mid) var(--ease);
}

.nav__link:hover,
.nav__link[aria-current='true'] {
  color: var(--c-ink);
}

.nav__link:hover::after,
.nav__link[aria-current='true']::after {
  transform: scaleX(1);
}

/* ==========================================================================
   3. Buttons
   ========================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 0.72rem 1.35rem;
  font-family: var(--f-body);
  font-size: var(--t-small);
  font-weight: 600;
  line-height: 1;
  white-space: nowrap;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  cursor: pointer;
  transition: transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-mid) var(--ease),
              background-color var(--dur-mid) var(--ease),
              border-color var(--dur-mid) var(--ease),
              color var(--dur-mid) var(--ease);
}

.btn:active {
  transform: translateY(1px);
}

.btn--primary {
  background: var(--g-fill);
  color: var(--c-on-fill);
  font-weight: 700;
  box-shadow: 0 10px 30px -12px rgba(168, 85, 247, 0.9);
}

.btn--primary:hover {
  color: var(--c-on-fill);
  transform: translateY(-2px);
  box-shadow: 0 16px 40px -12px rgba(168, 85, 247, 1);
}

.btn--ghost {
  background: rgba(255, 255, 255, 0.03);
  border-color: var(--c-line);
  color: var(--c-ink);
}

.btn--ghost:hover {
  color: var(--c-ink);
  border-color: var(--c-line-hi);
  background: rgba(168, 85, 247, 0.1);
  transform: translateY(-2px);
}

.btn .icon {
  width: 16px;
  height: 16px;
}

/* ==========================================================================
   4. Hero
   ========================================================================== */
.hero {
  /* These are named rather than inlined because .hero__portrait subtracts
     them to work out how tall it is allowed to be. Change a padding here
     and the portrait re-fits itself. */
  --hero-pad-top: calc(var(--header-h) + var(--s-7));
  --hero-pad-bottom: var(--s-8);
  --hero-cue: 4.75rem;

  position: relative;
  display: flex;
  align-items: center;
  min-height: 100svh;
  padding-top: var(--hero-pad-top);
  padding-bottom: var(--hero-pad-bottom);
  overflow: hidden;
}

/* Fades the hero into the section beneath it so there is no hard seam. */
.hero::after {
  content: '';
  position: absolute;
  inset-inline: 0;
  bottom: 0;
  height: 180px;
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to bottom, transparent, var(--c-bg));
}

.hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  align-items: center;
  gap: clamp(1.5rem, 4vw, 4rem);
  width: 100%;
}

.hero__copy {
  max-width: 34rem;
}

/* ---- Eyebrow ---------------------------------------------------------- */
.eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--s-3);
  margin-bottom: var(--s-5);
  font-family: var(--f-display);
  font-size: var(--t-eyebrow);
  font-weight: 700;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--c-violet);
}

.eyebrow::before {
  content: '';
  width: 34px;
  height: 2px;
  border-radius: 2px;
  background: var(--g-rule);
}

/* ---- Name -------------------------------------------------------------
   Rendered as real text at full size on first paint. The previous site
   typed the name in with JS, which meant the largest element on screen
   started empty — Largest Contentful Paint cannot fire on text that isn't
   there yet. The typewriter moved to the line beneath instead, where it
   costs nothing.                                                          */
.hero__name {
  font-size: var(--t-hero);
  line-height: 0.94;
  letter-spacing: -0.035em;
  margin-bottom: var(--s-4);
}

.hero__name span {
  display: block;
}

.hero__name .accent {
  background: var(--g-brand);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

/* ---- Rotating role line ------------------------------------------------ */
.hero__role {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.4em;
  min-height: 1.9em;
  margin-bottom: var(--s-5);
  font-family: var(--f-display);
  font-size: var(--t-lead);
  font-weight: 500;
  color: var(--c-ink);
}

.hero__role-static {
  color: var(--c-ink-3);
}

.hero__role-rotator {
  color: var(--c-violet);
}

.caret {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  translate: 0 0.14em;
  background: var(--c-violet);
  animation: blink 1.05s steps(2, start) infinite;
}

@keyframes blink {
  to { opacity: 0; }
}

.hero__lead {
  max-width: 33rem;
  margin-bottom: var(--s-6);
  font-size: var(--t-body);
  color: var(--c-ink-2);
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-3);
}

/* ---- Portrait ---------------------------------------------------------
   The source is now a background-free cutout with a real alpha channel, so
   there is no rectangle to hide any more — he stands directly in the aurora.
   That changes the whole treatment. The old two-axis edge masks are gone;
   what's left is lighting:

     ::before  a broad violet key light behind him, sized to his silhouette
     ::after   a tighter magenta rim low and left, echoing the studio gel
               that lights the real photograph's left side
     img       one fade at the base only, because the cutout ends in a flat
               horizontal cut across his torso and that edge must dissolve

   The glows sit at z-index 0 with the image at 1, all inside a stacking
   context — a negative z-index would drop them behind the hero's own
   background and disappear.                                              */
/* Sized by HEIGHT, not width. Width-driven it came out 721px tall against a
   563px copy column, which made the hero 901px — taller than a 1366x768 or
   1280x720 laptop viewport. That one fact pushed the scroll cue off-screen
   and, on phones, the buttons with it.

   The cap is the smaller of "about the height of the copy column" and
   "whatever is left of the viewport once the hero's own padding and the
   scroll cue are accounted for", so the hero can never outgrow the screen.
   aspect-ratio keeps the box exactly the image's shape, which matters
   because the glow pseudo-elements are positioned against that box. */
.hero__portrait {
  position: relative;
  z-index: 2;
  isolation: isolate;
  justify-self: end;
  align-self: center;
  aspect-ratio: 1351 / 2073;
  height: min(
    34rem,
    calc(100svh - var(--hero-pad-top) - var(--hero-pad-bottom) - var(--hero-cue))
  );
  width: auto;
  max-width: 100%;
}

/* Key light — broad, soft, centred on his upper body. */
.hero__portrait::before {
  content: '';
  position: absolute;
  inset: 6% -18% 2% -18%;
  z-index: 0;
  border-radius: 50% 50% 46% 46% / 42% 42% 58% 58%;
  background:
    radial-gradient(54% 42% at 54% 30%, rgba(168, 85, 247, 0.72), transparent 70%),
    radial-gradient(66% 58% at 46% 58%, rgba(99, 102, 241, 0.46), transparent 74%),
    radial-gradient(38% 34% at 82% 44%, rgba(34, 211, 238, 0.24), transparent 70%);
  filter: blur(48px);
  animation: portrait-breathe 12s var(--ease) infinite alternate;
}

/* Rim light — small, magenta, low-left, matching the gel in the photo. */
.hero__portrait::after {
  content: '';
  position: absolute;
  left: -8%;
  bottom: 12%;
  width: 56%;
  aspect-ratio: 1;
  z-index: 0;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(236, 72, 153, 0.58), transparent 64%);
  filter: blur(52px);
  animation: portrait-breathe 15s var(--ease) infinite alternate-reverse;
}

/* A slow scale pulse, not a position drift. Because it only touches
   transform it stays on the compositor and costs nothing to run. */
@keyframes portrait-breathe {
  from { transform: scale(1); opacity: 0.82; }
  to   { transform: scale(1.09); opacity: 1; }
}

.hero__portrait img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: contain;
  /* Single fade at the base. Long and late — starting it at 72% keeps his
     folded arms fully solid and only dissolves the flat cut below them. */
  -webkit-mask-image: linear-gradient(to bottom, #000 0 72%, rgba(0, 0, 0, 0.55) 88%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 0 72%, rgba(0, 0, 0, 0.55) 88%, transparent 100%);
}

/* ---- Scroll cue -------------------------------------------------------- */
.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: var(--s-5);
  z-index: 3;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-2);
  translate: -50% 0;
  font-family: var(--f-display);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--c-ink-3);
  text-decoration: none;
}

.scroll-cue:hover {
  color: var(--c-violet);
}

.scroll-cue__line {
  width: 1px;
  height: 46px;
  background: linear-gradient(to bottom, var(--c-violet), transparent);
  transform-origin: top;
  animation: cue 2.4s var(--ease) infinite;
}

@keyframes cue {
  0%, 100% { transform: scaleY(0.35); opacity: 0.4; }
  50%      { transform: scaleY(1);    opacity: 1; }
}

/* ==========================================================================
   5. Section headers
   The old design set an enormous ghost word behind each title at 10% opacity.
   It was a 2019 template tic, it broke on narrow screens, and it read as
   noise to a screen reader. Replaced with an eyebrow + rule + title.
   ========================================================================== */
.section__head {
  max-width: var(--w-prose);
  margin-bottom: clamp(2.5rem, 1.5rem + 3vw, 4.5rem);
}

.section__title {
  font-size: var(--t-h2);
  margin-bottom: var(--s-4);
}

.section__intro {
  font-size: var(--t-lead);
  color: var(--c-ink-2);
}

/* ==========================================================================
   6. About
   ========================================================================== */
.about__grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 4.5rem);
  align-items: start;
}

.about__prose p + p {
  margin-top: var(--s-4);
}

.about__prose strong {
  color: var(--c-ink);
  font-weight: 600;
}

.about__pull {
  margin-top: var(--s-6);
  padding-left: var(--s-5);
  border-left: 2px solid transparent;
  border-image: var(--g-brand) 1;
  font-family: var(--f-display);
  font-size: var(--t-h3);
  font-weight: 500;
  line-height: 1.45;
  color: var(--c-ink);
}

/* ---- Fact list --------------------------------------------------------- */
.facts {
  padding: var(--s-6);
  background: var(--c-raised);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-card);
}

.facts__item {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-block: var(--s-4);
  border-bottom: 1px solid var(--c-line);
}

.facts__item:first-child {
  padding-top: 0;
}

.facts__item:last-child {
  padding-bottom: 0;
  border-bottom: 0;
}

.facts__key {
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-ink-3);
}

.facts__val {
  font-family: var(--f-display);
  font-size: 1rem;
  font-weight: 500;
  color: var(--c-ink);
}

.facts__val a {
  text-decoration: none;
}

/* ---- Tech chips -------------------------------------------------------- */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-top: var(--s-6);
}

.chips__item {
  padding: 0.32rem 0.72rem;
  font-size: 0.76rem;
  font-weight: 500;
  color: var(--c-ink-2);
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid var(--c-line);
  border-radius: var(--r-pill);
  transition: color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease);
}

.chips__item:hover {
  color: var(--c-ink);
  border-color: var(--c-line-hi);
  background: rgba(168, 85, 247, 0.12);
}

/* ==========================================================================
   7. Expertise cards
   ========================================================================== */
/* Explicit 2x2 rather than auto-fit. With four cards, auto-fit resolved to
   three across and one stranded on its own row at common desktop widths —
   an orphan that reads as a mistake. Four items want a square. */
.cards {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s-5);
}

@media (min-width: 720px) {
  .cards {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

.card {
  position: relative;
  padding: var(--s-6);
  background: var(--c-raised);
  border: 1px solid var(--c-line);
  border-radius: var(--r-lg);
  overflow: hidden;
  transition: transform var(--dur-mid) var(--ease),
              border-color var(--dur-mid) var(--ease),
              background-color var(--dur-mid) var(--ease),
              box-shadow var(--dur-mid) var(--ease);
}

/* A soft light that follows the pointer. --mx/--my are written by
   js/main.js on pointermove; if JS never runs the gradient simply sits at
   its 50%/0% default and the card still looks finished.                   */
.card::before {
  content: '';
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
  background: radial-gradient(
    420px circle at var(--mx, 50%) var(--my, 0%),
    rgba(168, 85, 247, 0.16),
    transparent 62%
  );
  transition: opacity var(--dur-mid) var(--ease);
}

.card:hover {
  transform: translateY(-4px);
  background: var(--c-raised-hi);
  border-color: var(--c-line-hi);
  box-shadow: var(--shadow-glow);
}

.card:hover::before {
  opacity: 1;
}

.card__icon {
  display: grid;
  place-items: center;
  width: 46px;
  height: 46px;
  margin-bottom: var(--s-5);
  border-radius: var(--r-md);
  background: rgba(168, 85, 247, 0.12);
  border: 1px solid rgba(168, 85, 247, 0.24);
  color: var(--c-violet);
}

.card__icon .icon {
  width: 22px;
  height: 22px;
}

.card__title {
  font-size: var(--t-h3);
  margin-bottom: var(--s-3);
}

.card__body {
  font-size: var(--t-small);
  line-height: 1.72;
  color: var(--c-ink-2);
  margin: 0;
}

.card > * {
  position: relative;
  z-index: 1;
}

/* ==========================================================================
   8. Contact
   ========================================================================== */
.contact__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 210px), 1fr));
  gap: var(--s-4);
  margin-bottom: var(--s-7);
}

.contact-card {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  padding: var(--s-5);
  background: var(--c-raised);
  border: 1px solid var(--c-line);
  border-radius: var(--r-md);
  text-decoration: none;
  color: var(--c-ink);
  transition: transform var(--dur-mid) var(--ease),
              border-color var(--dur-mid) var(--ease),
              background-color var(--dur-mid) var(--ease);
}

.contact-card:hover {
  color: var(--c-ink);
  transform: translateY(-3px);
  background: var(--c-raised-hi);
  border-color: var(--c-line-hi);
}

.contact-card .icon {
  width: 22px;
  height: 22px;
  color: var(--c-violet);
  flex-shrink: 0;
  transition: transform var(--dur-mid) var(--ease);
}

.contact-card:hover .icon {
  transform: scale(1.12);
}

.contact-card__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.contact-card__label {
  font-family: var(--f-display);
  font-size: 0.94rem;
  font-weight: 500;
}

.contact-card__meta {
  font-size: 0.76rem;
  color: var(--c-ink-3);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ---- Company callout --------------------------------------------------- */
.callout {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-5);
  padding: clamp(1.5rem, 1rem + 2vw, 2.5rem);
  border: 1px solid var(--c-line-hi);
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 160% at 0% 0%, rgba(168, 85, 247, 0.16), transparent 62%),
    var(--c-raised);
}

.callout__title {
  font-size: var(--t-h3);
  margin-bottom: var(--s-2);
}

.callout__body {
  max-width: 46ch;
  font-size: var(--t-small);
  color: var(--c-ink-2);
  margin: 0;
}

/* ==========================================================================
   9. Footer
   ========================================================================== */
.footer {
  padding-block: var(--s-6);
  border-top: 1px solid var(--c-line);
}

.footer__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-4);
  font-size: var(--t-small);
  color: var(--c-ink-3);
}

.footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-5);
}

.footer a {
  color: var(--c-ink-3);
  text-decoration: none;
}

.footer a:hover {
  color: var(--c-violet);
}

/* ==========================================================================
   10. Responsive
   Only three breakpoints are needed — everything else is fluid via clamp()
   and auto-fit grids.
   ========================================================================== */

/* Tablet: hero and about stack. */
/* Short viewports — a 1366x768 or 1280x720 laptop. The hero headline is
   sized off viewport WIDTH, so on a wide-but-short screen it renders at its
   full 6.4rem and the copy column alone overflows the fold. Type and padding
   both have to answer to height, not only width. */
@media (min-width: 901px) and (max-height: 820px) {
  :root {
    --t-hero: clamp(2.6rem, 1.1rem + 4.6vw, 4.6rem);
  }

  .hero {
    --hero-pad-top: calc(var(--header-h) + var(--s-5));
    --hero-pad-bottom: var(--s-6);
  }

  .hero__lead {
    margin-bottom: var(--s-5);
  }
}

@media (max-width: 900px) {
  .hero {
    --hero-pad-top: calc(var(--header-h) + var(--s-4));
    --hero-pad-bottom: var(--s-6);
    min-height: 100svh;
  }

  /* A flex column, so the portrait absorbs whatever height is left once the
     copy has taken what it needs. The hero then fills the screen exactly at
     any phone size — no dead band under the buttons, no scrollbar for the
     last 20px — instead of being sized to a guess. */
  .hero__inner {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: var(--s-5);
    min-height: calc(100svh - var(--hero-pad-top) - var(--hero-pad-bottom));
  }

  /* Photo above the copy on narrow screens: the face is the hook, and a
     portrait pushed below three paragraphs is a portrait nobody sees. */
  .hero__portrait {
    order: -1;
    flex: 1 1 auto;
    min-height: 150px;
    align-self: center;
    width: min(320px, 76vw);
    max-width: none;
    height: auto;
    aspect-ratio: auto;
    margin-bottom: 0;
  }

  .hero__portrait img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    /* `contain`, never `cover`. cover crops to fill, and on a short screen
       the thing it crops first is the top of his head. contain guarantees the
       whole frame survives at every viewport; the asset's own 130px of
       headroom keeps it from looking tight. */
    object-fit: contain;
    /* Anchored to the bottom, so he stands on the copy and any slack opens
       up above him rather than between him and the text. */
    object-position: 50% 100%;
    /* The fade lives in the asset's alpha channel on phones — see index.html.
       Masking here would put the image on its own composited layer. */
    -webkit-mask-image: none;
    mask-image: none;
  }

  .hero__copy {
    flex: 0 0 auto;
    max-width: none;
  }

  .hero__role {
    margin-bottom: var(--s-4);
  }

  .hero__lead {
    margin-bottom: var(--s-5);
  }

  .about__grid {
    grid-template-columns: 1fr;
  }

  .scroll-cue {
    display: none;
  }
}

/* Short phones — 360x740 and similar. One notch off the portrait so the call
   to action still clears the fold. */
@media (max-width: 900px) and (max-height: 790px) {
  .hero__portrait {
    width: min(265px, 68vw);
  }
}

/* Very short phones — iPhone SE at 375x667 is the case that forced this.
   Even at 240px the buttons landed 25px under the fold. */
@media (max-width: 900px) and (max-height: 700px) {
  .hero__portrait {
    width: min(215px, 58vw);
  }

  .hero__lead {
    margin-bottom: var(--s-4);
  }
}

/* Phone: collapse the nav links, keep the CTA. */
@media (max-width: 640px) {
  .nav__list {
    display: none;
  }

  .hero__actions .btn {
    flex: 1 1 auto;
  }

  .callout {
    flex-direction: column;
    align-items: flex-start;
  }
}

/* Coarse pointers get no hover-lift — it only ever fires as a sticky
   stuck-hover state on touch. */
@media (hover: none) {
  .card:hover,
  .contact-card:hover,
  .btn:hover {
    transform: none;
  }
}
