/* ---------------------------------------------------
   Design tokens
   Bright, minimal palette (Tesla-inspired): white base,
   near-black text, one blue accent for interactive bits.
   Change these variables to retheme the whole site.
--------------------------------------------------- */
:root {
  --color-bg: #ffffff;
  --color-bg-alt: #f6f7f9;
  --color-text: #171a20;
  --color-text-muted: #5c5e62;
  --color-accent: #3e6ae1;
  --color-border: #e2e4e7;

  --font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;

  --space-xs: 0.5rem;
  --space-sm: 1rem;
  --space-md: 1.5rem;
  --space-lg: 2.5rem;
  --space-xl: 4rem;
  --space-2xl: 6rem;

  --nav-height: 72px;
  --max-width: 1100px;
}

/* ---------------------------------------------------
   Reset
--------------------------------------------------- */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  scroll-behavior: smooth;
  scroll-padding-top: var(--nav-height);
}

body {
  font-family: var(--font-sans);
  color: var(--color-text);
  background: var(--color-bg);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

a {
  color: inherit;
  text-decoration: none;
}

ol, ul {
  list-style: none;
}

img {
  max-width: 100%;
  display: block;
}

/* Respect users who've asked their system for less motion */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---------------------------------------------------
   Nav
   Fixed bar, translucent with a blur so content can
   scroll underneath it. Gains a visible border once
   the page has scrolled (class toggled in main.js).
--------------------------------------------------- */
/* Light frosted glass, always. This used to have a second dark state
   for sitting over the hero photograph; with that photo gone from
   every page, the dark variant was dead weight and, worse, it was the
   DEFAULT - so the bar rendered dark for the split second before
   JavaScript ran. Light is now the base state and nothing has to
   correct it. .nav--scrolled only adds the separating line. */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--nav-height);
  padding: 0 var(--space-lg);
  z-index: 100;
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(14px) saturate(130%);
  -webkit-backdrop-filter: blur(14px) saturate(130%);
  border-bottom: 1px solid transparent;
  transition: border-color 0.3s ease, box-shadow 0.3s ease;
}

.nav--scrolled {
  border-bottom-color: var(--color-border);
  box-shadow: 0 1px 20px rgba(0, 0, 0, 0.05);
}

/* The horizontal padding lives on .nav, not on .nav__inner, so this
   box has exactly the same geometry as .section + .section__inner:
   pad the full-width outer element, then centre a max-width inner one.
   Padding the inner box instead would push the logo 40px right of the
   hero text below it. Keep these two structures in step. */
.nav__inner {
  max-width: var(--max-width);
  height: 100%;
  margin: 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

/* Same size as the nav links now. Weight is left at 700, which is what
   still marks it out as the brand rather than a fifth menu item. */
.nav__logo {
  font-weight: 700;
  font-size: 0.9rem;
  letter-spacing: -0.01em;
  line-height: 1;
  color: var(--color-text);
}

.nav__links {
  display: flex;
  gap: var(--space-lg);
}

.nav__links a {
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--color-text-muted);
  transition: color 0.2s ease;
}

.nav__links a:hover {
  color: var(--color-text);
}

/* ---------------------------------------------------
   Full-height sections
   Every section is at least one viewport tall and
   vertically centers its content. svh (small viewport
   height) avoids the mobile-browser-chrome jump that
   plain vh causes.
--------------------------------------------------- */
.section {
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding: calc(var(--nav-height) + var(--space-xl)) var(--space-lg) var(--space-xl);
}

.section:nth-of-type(even) {
  background: var(--color-bg-alt);
}

.section__inner {
  max-width: var(--max-width);
  width: 100%;
  margin: 0 auto;
}

.section__eyebrow {
  font-size: 0.85rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--color-accent);
  margin-bottom: var(--space-sm);
}

/* 46ch rather than 30ch so a heading of this length stays on one
   line on desktop. It still wraps on narrow screens, which is correct:
   forcing one line there would overflow the viewport. */
.section__heading {
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: 700;
  letter-spacing: -0.01em;
  margin-bottom: var(--space-md);
  max-width: 46ch;
}

/* ---------------------------------------------------
   About Me section

   Portrait left, text right, on plain white. This replaced a
   full-bleed photo hero with a dark scrim, so every light-on-dark
   override that used to live here is gone: the text is ordinary
   dark-on-white now.
--------------------------------------------------- */
.about-section .section__inner {
  /* Wider than the shared 1100px so the 1.25x scale-up below has room.
     Scoped to this section only; every other section keeps --max-width. */
  max-width: 1375px;
}

.about-section {
  /* Anchor for the scroll arrow, which is positioned against the
     whole section rather than the text column. */
  position: relative;
  /* Extra room at the bottom so the arrow always clears the photo.
     The arrow sits 40px up and is 30px tall, so it needs 70px; the
     standard 64px section padding was 6px short, and the photo
     overlapped it on viewports under about 745px tall. */
  padding-bottom: calc(var(--space-xl) + var(--space-lg));
}

/* Text column is given the larger share. The photo is capped at 460px
   anyway, so the extra width all goes to the words - enough that the
   longest sentence sits on one line instead of wrapping. */
.about {
  display: grid;
  /* 0.85/1.15 rather than 0.9/1.1, with a tighter gap. Together those
     take the text column from 583px to 619px, which is what lets the
     intro run at 1rem instead of 0.95 and still hold one line. The photo
     column lands at 457px against its 460px cap, so the photo itself
     loses 3px - not visible. Pushing the ratio to 0.8/1.2 would allow
     1.05rem, but that costs the photo a real 30px. */
  /* Both columns size to their content and the pair is centred, rather
     than fr columns splitting a fixed width. With fr columns the photo
     is capped by viewport height, so its column kept a chunk of dead
     space beside it - which read as exactly the white space this section
     was criticised for. */
  grid-template-columns: auto auto;
  justify-content: center;
  gap: 60px;
  align-items: center;
  width: 100%;
}

.about__photo {
  width: 100%;
  /* Capped, because the frame is 4:5 - every pixel of width costs 1.25
     of height, and an uncapped photo would grow taller than the
     viewport on a wide screen. */
  /* 1.25x of the old 460px, but bounded by what the viewport can show:
     this section is meant to fit one screen, and a 4:5 photo 575px wide
     stands 719px tall, which overflows a 900px-tall window once the nav
     and section padding are counted. On a short screen the photo grows
     as far as it can; on a tall one it reaches the full 575px. */
  /* Three caps, smallest wins: the 1.25x target, what the viewport
     height can show without breaking the one-screen layout, and a share
     of the viewport width. That last one exists so the text column keeps
     enough room to hold one sentence per line as the window narrows -
     without it the photo hogs the width and the intro wraps. */
  width: min(575px, calc((100svh - 260px) * 0.8), 34vw);
  border-radius: 24px;
  overflow: hidden;
  background: var(--color-bg-alt);
  /* Fixed shape so a real photograph of any proportion drops in
     without shifting the layout beside it. */
  aspect-ratio: 4 / 5;
}

/* Radius repeated on the image itself, not just clipped by the parent.
   Belt and braces: a clipping parent alone can leave hairline square
   corners showing at some zoom levels. */
.about__photo img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 24px;
}

/* Left-aligned and flush to the left of its column. Wide enough that
   the longest line of the intro fits without wrapping. */
.about__text {
  text-align: left;
  /* 619px is what the grid column resolves to at the 1100px page
     max-width; this cap sits just above it so the column decides. */
  max-width: 800px;
}

.about__heading {
  font-size: clamp(2.375rem, 4.5vw, 3.75rem);
  font-weight: 700;
  line-height: 1.12;
  letter-spacing: -0.02em;
  color: var(--color-text);
  margin-bottom: var(--space-md);
}

/* Three separate lines with a full blank line between them, so it
   reads as a short stanza rather than a block of prose. */
.about__intro {
  display: flex;
  flex-direction: column;
  gap: 1.15em;
  /* Sized so the two 70-character sentences each sit on one line.
     The vw term keeps that true as the column narrows; the 0.85rem
     floor stops it shrinking to something unreadable, which means the
     lines do wrap again below roughly a 1000px viewport. */
  /* Caps at the requested 1.25rem, but tracks viewport width below that
     so each sentence keeps to one line. Holds to roughly a 1000px
     window; narrower than that the two-column layout cannot fit 70
     characters at a readable size, and it wraps. */
  font-size: clamp(0.95rem, 1.45vw, 1.25rem);
  color: var(--color-text);
}

/* The rotating word. Width is animated by main.js after it measures
   the next word's pixel width, so the surrounding text reflows
   smoothly instead of jumping. Accent blue now that it sits on white
   rather than over a dark photo. */
.word-rotate {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  border-bottom: 3px solid var(--color-accent);
  transition: width 0.4s ease-in-out;
  white-space: nowrap;
  color: var(--color-accent);
}

/* Stack only on genuinely narrow screens, photo first.

   This used to break at 860px, which was far too eager: the Lavish
   review frame, a split-screen window, or any browser under about
   900px would stack the layout, which is why the text appeared BELOW
   the photo instead of beside it. At 280px for the photo there is
   still room for a sensible text column well below that, so the
   breakpoint moves down to 620px. */
@media (max-width: 620px) {
  .about {
    grid-template-columns: 1fr;
    gap: var(--space-lg);
  }

  .about__photo {
    max-width: 240px;
    margin: 0 auto;
    aspect-ratio: 1 / 1;
  }
}

/* Downward arrow inviting the reader on. Pinned to the bottom centre
   of the section rather than sitting in the text flow. */
.scroll-cue {
  position: absolute;
  left: 50%;
  bottom: var(--space-lg);
  transform: translateX(-50%);
  z-index: 2;
  display: flex;
  color: var(--color-accent);
  transition: color 0.2s ease;
}

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

/* The bounce lives on the inner svg, not the anchor. The anchor is
   already using transform for its horizontal centring, and a second
   transform there would cancel it out. */
.scroll-cue svg {
  animation: scroll-arrow-bounce 1.8s ease-in-out infinite;
}

@keyframes scroll-arrow-bounce {
  0%, 100% { transform: translateY(0); opacity: 0.72; }
  50% { transform: translateY(9px); opacity: 1; }
}

/* ---------------------------------------------------
   Experience timeline

   Each role is a two-column row: a left rail holding
   dates and location, and a card holding the detail.
   The vertical line is drawn on the <ol> itself, and
   each role punches a dot onto it with ::before.
--------------------------------------------------- */
/* ---------------------------------------------------
   Pull quote
   Outlined box, oversized quotation marks straddling the
   border, handwriting face. Modelled on the reference card.
--------------------------------------------------- */

.quote-card {
  position: relative;
  max-width: 44rem;
  /* Centred, and narrower than the paragraphs around it, so the box
     reads as a deliberate interruption rather than another paragraph. */
  margin: var(--space-md) auto;
  padding: var(--space-lg) var(--space-md);
  border: 3px solid var(--color-text);
  text-align: center;
}

.quote-card__text {
  margin: 0;
  /* Comic Sans MS ships on macOS and Windows. Chalkboard SE covers
     older Macs, Comic Neue covers Linux and Android, and cursive is
     the last resort. */
  font-family: 'Comic Sans MS', 'Comic Sans', 'Chalkboard SE', 'Comic Neue', cursive;
  /* 2x the 1.05rem body size of this block. Floored with clamp so it
     stays inside the box on a phone rather than overflowing it. */
  font-size: clamp(1.3rem, 4vw, 2.1rem);
  line-height: 1.3;
  font-weight: 700;
  color: var(--color-text);
  /* Justification off. At this size it would tear visible holes in
     the lines rather than tidying them. */
  text-align: center;
  hyphens: manual;
  -webkit-hyphens: manual;
}

.quote-card__cite {
  margin-top: var(--space-sm);
  /* Same face as the quote so the card reads as one object. */
  font-family: 'Comic Sans MS', 'Comic Sans', 'Chalkboard SE', 'Comic Neue', cursive;
  font-size: 1rem;
  color: var(--color-text);
  text-align: center;
}

/* The marks sit on the border and mask it, as in the reference. Their
   background has to match whatever is behind the card: Professional is
   an even section, so it is --color-bg-alt. Reorder the sections and
   this one value has to change with them. */
.quote-card::before,
.quote-card::after {
  position: absolute;
  font-family: 'Comic Sans MS', 'Comic Sans', 'Chalkboard SE', 'Comic Neue', cursive;
  font-size: 4.5rem;
  line-height: 1;
  font-weight: 700;
  color: var(--color-text);
  background: var(--color-bg-alt);
  padding: 0 0.08em;
}

.quote-card::before {
  content: '\201C';
  top: -0.42em;
  left: var(--space-md);
}

.quote-card::after {
  content: '\201D';
  bottom: -0.62em;
  right: var(--space-md);
}

.roles {
  margin-top: var(--space-xl);
  padding-left: var(--space-lg);
  /* The timeline spine. Fading it out at the bottom stops
     the line from ending in a hard stub after the last role. */
  background: linear-gradient(
    to bottom,
    var(--color-border) 0%,
    var(--color-border) 88%,
    transparent 100%
  );
  background-size: 2px 100%;
  background-repeat: no-repeat;
}

.role {
  display: grid;
  grid-template-columns: 190px 1fr;
  gap: var(--space-lg);
  position: relative;
  padding-bottom: var(--space-xl);
}

.role:last-child {
  padding-bottom: 0;
}

/* The dot on the spine. Sits half outside the line so it
   reads as sitting on top of it rather than beside it. */
.role::before {
  content: '';
  position: absolute;
  left: calc(var(--space-lg) * -1 - 6px);
  top: 6px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--color-bg);
  border: 2px solid var(--color-border);
  transition: border-color 0.3s ease, transform 0.3s ease;
}

/* The current role gets a filled accent dot with a soft ring. */
.role:first-child::before {
  border-color: var(--color-accent);
  box-shadow: 0 0 0 5px rgba(62, 106, 225, 0.12);
}

.role:hover::before {
  border-color: var(--color-accent);
  transform: scale(1.15);
}

/* --- left rail: dates, location, current badge --- */
.role__rail {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.35rem;
  padding-top: 0.1rem;
}

.role__dates {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--color-text);
  font-variant-numeric: tabular-nums;
}

.role__dash {
  color: var(--color-text-muted);
  margin: 0 0.15rem;
}

.role__place {
  font-size: 0.85rem;
  color: var(--color-text-muted);
}

/* --- right side: the card --- */
.role__body {
  border: 1px solid var(--color-border);
  border-radius: 14px;
  padding: var(--space-md) var(--space-md) var(--space-sm);
  background: var(--color-bg);
  transition: border-color 0.3s ease, box-shadow 0.3s ease, transform 0.3s ease;
}

.role:hover .role__body {
  border-color: #cdd4e4;
  box-shadow: 0 10px 30px rgba(23, 26, 32, 0.07);
  transform: translateY(-2px);
}

/* The header used to be a flex row holding the logo beside the
   company name. The logo now lives in the left rail, so this
   goes back to a simple stack of company name over job title. */
.role__header {
  margin-bottom: 0.4rem;
}

/* Company logo, sitting in the left rail under the date block.
   left object-position keeps them all flush to the rail edge. */
.role__logo {
  margin-top: 0.6rem;
  width: auto;
  max-width: 150px;
  object-fit: contain;
  object-position: left center;
  flex-shrink: 0;
}

/* Logos have to be balanced by eye, not by a shared height.
   These four range from square (Tesla, Stanford) to a 5.3:1
   wordmark (Blue Current). At one shared height the wide marks
   become far bigger in area and visually shout over the square
   ones, so each is tuned individually: square marks sit taller,
   wordmarks shorter and wider, so all four carry equal weight
   down the column. */
.role__logo[src*="tesla"] {
  height: 34px;
}

.role__logo[src*="stanford"] {
  height: 43px;
}

.role__logo[src*="joby"] {
  height: 26px;
}

.role__logo[src*="blue-current"] {
  height: 22px;
}

.role__company {
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
}

.role__title {
  font-size: 0.98rem;
  font-weight: 600;
  color: var(--color-accent);
  margin-top: 0.15rem;
}

/* Company name links out to the employer. The arrow is decorative
   and hidden from screen readers; the link text carries the meaning. */
.role__link {
  color: inherit;
  transition: color 0.2s ease;
}

.role__link:hover,
.role__link:focus-visible {
  color: var(--color-accent);
}

.role__ext {
  display: inline-block;
  margin-left: 0.3rem;
  font-size: 0.7em;
  font-weight: 500;
  transform: translateY(-0.15em);
  transition: transform 0.2s ease;
}

.role__link:hover .role__ext {
  transform: translate(0.15em, -0.3em);
}

/* The summary is now the whole body of the card - the bullet list it
   used to introduce is gone - so the dividing rule under it goes too. */
/* The title line is already accent-coloured, so colour alone cannot mark
   these two as links. An underline does the work instead. */
.role__title a {
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
}

.role__title a:hover,
.role__title a:focus-visible {
  color: var(--color-text);
}

.role__summary {
  color: var(--color-text-muted);
  font-size: 1rem;
  margin-bottom: var(--space-md);
  max-width: 68ch;
  /* Justified to match the intro paragraphs above, so the whole section
     reads as a set of boxes rather than a mix of boxed and ragged. */
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
}

/* Same reasoning as the intro block: justification needs long enough
   lines to absorb the spacing, and a phone does not have them. */
@media (max-width: 640px) {
  .role__summary {
    text-align: left;
    hyphens: manual;
    -webkit-hyphens: manual;
  }
}

/* Personal section intro. Same justified treatment as the Professional
   intro so both sections open the same way. It sits directly under the
   heading, which already carries a bottom margin, so no top margin here.
   Capped at 68ch: justification behaves far better on a short measure. */
.personal-intro {
  width: 100%;
  margin-bottom: var(--space-lg);
  font-size: 1.05rem;
  color: var(--color-text-muted);
  text-align: justify;
  hyphens: auto;
  -webkit-hyphens: auto;
}

@media (max-width: 640px) {
  .personal-intro {
    text-align: left;
    hyphens: manual;
    -webkit-hyphens: manual;
  }
}

/* Skill tags under each role. */
.role__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.role__tags li {
  font-size: 0.78rem;
  font-weight: 500;
  color: var(--color-accent);
  background: rgba(62, 106, 225, 0.09);
  border-radius: 999px;
  padding: 0.3rem 0.75rem;
}

/* ---------------------------------------------------
   Focus area cards
--------------------------------------------------- */
.focus-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: var(--space-md);
}

.focus-card {
  padding: var(--space-md);
  border: 1px solid var(--color-border);
  border-radius: 12px;
  background: var(--color-bg);
}

/* The inline SVGs in each card use stroke="currentColor", so whatever
   `color` is set here is the color they draw in. Swap the line below for
   var(--color-text) if you'd rather they were monochrome than blue. */
.focus-card__icon {
  display: block;
  width: 30px;
  height: 30px;
  margin-bottom: var(--space-sm);
  color: var(--color-accent);
}

/* No description text under these any more, so the card is just icon
   and title. margin-bottom would leave a dangling gap. */
.focus-card h3 {
  font-size: 1.05rem;
  font-weight: 600;
}

/* ---------------------------------------------------
   Contact
--------------------------------------------------- */

/* ---------------------------------------------------
   Footer
--------------------------------------------------- */
.footer {
  text-align: center;
  padding: var(--space-lg);
  color: var(--color-text-muted);
  font-size: 0.85rem;
}

/* ---------------------------------------------------
   Scroll-triggered fade-ins
   Starting state here; main.js adds .is-visible via
   IntersectionObserver as each element enters the viewport.
--------------------------------------------------- */
.fade-in {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.fade-in.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* Stagger the role cards so they arrive one after another
   instead of all at once. The delay only applies on the way
   in, which is why it sits on the .is-visible state. */
.role.fade-in.is-visible {
  transition-delay: 0.05s;
}

.role:nth-child(2).fade-in.is-visible {
  transition-delay: 0.13s;
}

.role:nth-child(3).fade-in.is-visible {
  transition-delay: 0.21s;
}

.role:nth-child(4).fade-in.is-visible {
  transition-delay: 0.29s;
}

/* ---------------------------------------------------
   Small screens
--------------------------------------------------- */
/* "Professional Experience" and "Unprofessional Experience" are long
   labels. Measured out, the bar needs about 830px, so it runs out of
   room a little under 900px - well before the phone breakpoint. This
   tier buys back the difference before anything collides. */
@media (max-width: 900px) {

  .nav__links {
    gap: var(--space-sm);
  }

  .nav__links a {
    font-size: 0.82rem;
  }
}

@media (max-width: 640px) {
  /* Padding moves with .nav, matching the section padding at this
     width so the logo stays aligned with the hero text. */
  .nav {
    padding: 0 var(--space-md);
  }

  .nav__logo {
    flex-shrink: 0;
  }

  /* Four labels this long genuinely cannot fit a phone on one line at
     any readable size. Until there is a proper menu, they scroll
     sideways so every link stays reachable, rather than overflowing
     the page and forcing the whole document to pan. */
  .nav__links {
    gap: 0.9rem;
    margin-left: var(--space-sm);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .nav__links::-webkit-scrollbar {
    display: none;
  }

  .nav__links a {
    font-size: 0.78rem;
    white-space: nowrap;
  }

  .section {
    padding: calc(var(--nav-height) + var(--space-lg)) var(--space-md) var(--space-lg);
  }

  /* Below this width the two-column role layout is too tight,
     so the rail stacks above the card and turns into a single
     horizontal line of dates, location, and badge. */
  .role {
    grid-template-columns: 1fr;
    gap: var(--space-sm);
    padding-bottom: var(--space-lg);
  }

  .role__rail {
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.5rem;
  }

  /* The rail is a horizontal row here, so the logo's vertical
     offset would push it out of line. Sit it inline instead.
     Heights are left alone: the per-logo sizes above are already
     small, and they use attribute selectors, which outrank a
     plain .role__logo rule no matter where it appears. */
  .role__logo {
    margin-top: 0;
  }

  .role__company {
    font-size: 1.25rem;
  }

}

/* ---------------------------------------------------
   Rotating gallery (Unprofessional Experience)

   Slides are stacked on top of each other and cross-faded, rather
   than laid side by side and slid. Fading needs no width maths, so
   it cannot drift out of alignment when the viewport changes.
--------------------------------------------------- */
/* Fills the content column rather than sitting at 760px inside 1100px,
   which left a band of dead space down the right. margin auto keeps it
   centred if the cap is ever lowered again. */
.carousel {
  margin: var(--space-lg) auto 0;
  width: 100%;
}

.carousel__viewport {
  position: relative;
  border-radius: 16px;
  overflow: hidden;
  border: 1px solid var(--color-border);
  /* 16:9 rather than the old 8:5. At full column width 8:5 would stand
     687px tall, which pushes the section past one screen once the
     heading and bars are counted; 16:9 comes in at 619px and still
     matches what a phone camera produces, so real photos will not be
     cropped much. Reserving the shape also stops the section jumping
     as the images load. */
  aspect-ratio: 16 / 9;
  background: var(--color-bg-alt);
}

/* Every slide sits in the same place. Only the active one is visible.
   visibility (not just opacity) keeps hidden slides out of the tab
   order and away from screen readers mid-fade. */
.carousel__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.7s ease, visibility 0s linear 0.7s;
}

.carousel__slide.is-active {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.7s ease, visibility 0s linear 0s;
}

.carousel__slide img,
.carousel__slide video {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.carousel__slide figcaption {
  position: absolute;
  left: var(--space-md);
  bottom: var(--space-md);
  padding: 0.35rem 0.85rem;
  border-radius: 999px;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--color-text);
  background: rgba(255, 255, 255, 0.85);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
}

/* Progress bars. Buttons rather than divs so they are keyboard
   reachable and announce themselves. */
.carousel__bars {
  display: flex;
  justify-content: center;
  gap: 0.25rem;
  margin-top: var(--space-sm);
}

/* Dots rather than thin bars. The button is deliberately far bigger
   than the dot it draws: the visible dot is 10px but the hit target is
   32px square, because a 4px-tall bar was fiddly to hit with a mouse
   and close to impossible on a touch screen. The dot itself is drawn
   by ::before so the padding stays clickable. */
.carousel__bar {
  width: 32px;
  height: 32px;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  display: grid;
  place-items: center;
}

.carousel__bar::before {
  content: '';
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--color-border);
  transition: background 0.3s ease, transform 0.2s ease;
}

.carousel__bar:hover::before {
  background: #c2c8d4;
  transform: scale(1.25);
}

.carousel__bar.is-active::before {
  background: var(--color-accent);
}

.carousel__bar:focus-visible {
  outline: 2px solid var(--color-accent);
  outline-offset: -4px;
  border-radius: 50%;
}

/* ---------------------------------------------------
   Compact section + contact icons
--------------------------------------------------- */

/* Most sections fill the viewport. Contact holds four links, so a
   100svh floor just manufactured a screen of empty space. The bottom
   padding is deliberately tighter than the top so the block settles
   close to the footer rather than floating between the two. */
.section--compact {
  min-height: auto;
  padding-top: var(--space-2xl);
  padding-bottom: var(--space-lg);
}

/* Contact is a deliberate mix: the CONTACT label sits left like every
   other section's label, while the heading and the icon row are centred
   so they line up with the footer underneath. Centring the whole
   container instead would have dragged the label across with them. */
#contact .section__heading {
  text-align: center;
  max-width: none;
}

/* Centred to match the heading above it and the footer below. */
.contact-icons {
  display: flex;
  justify-content: center;
  gap: var(--space-sm);
  margin-top: var(--space-md);
}

/* Generous hit area: the icon is 22px but the target is 48px, which is
   the minimum a finger can reliably hit on a phone. */
.contact-icons a {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 1px solid var(--color-border);
  border-radius: 12px;
  color: var(--color-text-muted);
  background: var(--color-bg);
  transition: color 0.2s ease, border-color 0.2s ease, transform 0.2s ease;
}

.contact-icons a:hover,
.contact-icons a:focus-visible {
  color: var(--color-accent);
  border-color: var(--color-accent);
  transform: translateY(-2px);
}

.contact-icons svg {
  width: 22px;
  height: 22px;
}

/* ---------------------------------------------------
   Utilities
--------------------------------------------------- */

/* ---------------------------------------------------
   Copy confirmation toast
--------------------------------------------------- */

/* Bottom centre, above everything, and not clickable - it is a message,
   not a control. visibility (not just opacity) keeps it out of the
   accessibility tree while hidden. */
.toast {
  position: fixed;
  left: 50%;
  bottom: var(--space-lg);
  z-index: 200;
  transform: translate(-50%, 12px);
  padding: 0.7rem 1.25rem;
  border-radius: 999px;
  background: var(--color-text);
  color: #ffffff;
  font-size: 0.9rem;
  font-weight: 500;
  white-space: nowrap;
  box-shadow: 0 8px 28px rgba(23, 26, 32, 0.22);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0s linear 0.25s;
}

.toast.is-visible {
  opacity: 1;
  visibility: visible;
  transform: translate(-50%, 0);
  transition: opacity 0.25s ease, transform 0.25s ease, visibility 0s linear 0s;
}

/* On a narrow screen the message would run off both edges, so let it
   wrap and sit inside the viewport instead. */
@media (max-width: 480px) {
  .toast {
    white-space: normal;
    max-width: calc(100vw - 2 * var(--space-md));
    text-align: center;
  }
}

/* Two paragraphs bridging the focus grid and the work history, spanning
   the full content column to match the grid above. See the note in the
   chat about line length at this width. */
.professional-intro {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
  width: 100%;
  /* Was --space-2xl (6rem), which read as a hole between the focus cards
     and this block. --space-lg still separates them clearly while keeping
     the two halves of the section visibly related. */
  margin-top: var(--space-md);
  font-size: 1.05rem;
  /* Full-strength near-black rather than the muted grey used elsewhere.
     This is the section that carries the most weight, so it gets the
     highest contrast on the page. */
  color: var(--color-text);
  /* Justified, so each paragraph resolves to a clean rectangle rather
     than a ragged right edge. */
  text-align: justify;
  /* Hyphenation is not optional here. Justifying without it stretches
     word spacing to fill each line, which opens vertical "rivers" of
     white space down the paragraph. Allowing breaks lets the spacing
     stay even. Relies on lang="en" being set on <html>, which it is. */
  hyphens: auto;
  -webkit-hyphens: auto;
}

/* Justification only helps when lines are long enough to absorb the
   spacing. On a phone it makes short lines look gappy, so it comes off. */
@media (max-width: 640px) {
  .professional-intro {
    text-align: left;
    hyphens: manual;
    -webkit-hyphens: manual;
  }
}
