/* =====================================================================
   Laith Wehbi — Personal Site
   ===================================================================== */

/* ---------- Reset ---------- */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; }
img { display: block; max-width: 100%; height: auto; }
a { color: inherit; text-decoration: none; }
ul, ol { list-style: none; }
button { background: none; border: none; cursor: pointer; color: inherit; font: inherit; }

/* ---------- Skip-to-content link ----------
   Sits at the top of the document, hidden off-screen until a keyboard
   user tabs to it. The first Tab on page load surfaces the link, the
   second activates the jump to #top. z-index sits above the fixed nav
   so the focused state is never clipped. */
.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  z-index: 1000;
  padding: 12px 20px;
  background: var(--c-secondary);
  color: var(--c-bg);
  font-family: var(--f-body);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border-radius: 0 0 var(--r-md) 0;
  transform: translateY(-120%);
  transition: transform 180ms ease-out;
}
.skip-link:focus,
.skip-link:focus-visible {
  transform: translateY(0);
  outline: 2px solid var(--c-ink);
  outline-offset: 2px;
}

/* ---------- Design tokens ---------- */
:root {
  /* brand */
  --c-primary:    #664814;   /* olive / oak */
  --c-secondary:  #c0745c;   /* terracotta */
  --c-accent:     #63223a;   /* wine */

  /* surfaces */
  --c-bg:         #0c0c0c;
  --c-bg-alt:     #141414;
  --c-bg-soft:    #1a1a1a;
  --c-surface:    #2a2b30;

  /* section accents */
  --c-wine:       #63223a;
  --c-wine-2:     #733c50;
  --c-teal:       #507986;
  --c-slate:      #454e55;
  --c-terracotta: #c0745c;
  --c-olive:      #664814;
  --c-purple:     #5a5196;

  /* text */
  --c-ink:        #ffffff;
  --c-ink-dim:    rgba(255, 255, 255, 0.72);
  --c-ink-muted:  rgba(255, 255, 255, 0.6);
  --c-ink-faint:  rgba(255, 255, 255, 0.32);
  --c-rule:       rgba(255, 255, 255, 0.14);

  /* fonts */
  --f-display:    "Spectral", "Times New Roman", Georgia, serif;
  --f-body:       "Roboto Condensed", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;

  /* spacing scale */
  --s-1: 4px;   --s-2: 8px;   --s-3: 16px;  --s-4: 26px;
  --s-5: 36px;  --s-6: 48px;  --s-7: 64px;  --s-8: 96px;
  --s-9: 120px; --s-10: 180px;

  /* radii + shadows */
  --r-sm: 4px;
  --r-md: 8px;
  --r-pill: 999px;
  --shadow-sm: 0 0 4px rgba(0, 0, 0, 0.2);
  --shadow-lg: 0 24px 60px -20px rgba(0, 0, 0, 0.7);

  /* layout */
  --container: 1240px;
  --gutter: clamp(24px, 5vw, 64px);

  /* motion */
  --ease: cubic-bezier(0.22, 1, 0.36, 1);
  --dur-fast: 200ms;
  --dur: 600ms;
}

/* ---------- Base ---------- */
body {
  font-family: var(--f-body);
  font-weight: 400;
  font-size: clamp(16px, 1.15vw, 19px);
  line-height: 1.6;
  color: var(--c-ink);
  background: var(--c-bg);
  overflow-x: hidden;
}

::selection {
  background: var(--c-secondary);
  color: var(--c-bg);
}

/* Hides text visually while keeping it available to screen readers.
   Used to give each abstract toggle a distinct accessible name. */
.visually-hidden {
  position: absolute !important;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Keeps a short phrase on one line, for example "VU–Pon" (which would
   otherwise break after the dash) and "AI & Optimization" in the hero
   label on narrow screens. */
.nowrap { white-space: nowrap; }

/* ---------- Utility: container + section ---------- */
/* Section padding governs the vertical breathing room between major
   blocks. Values tightened from 80–160 px to 60–120 px so adjacent
   sections feel connected without crowding. */
.section { padding: clamp(52px, 7.5vw, 100px) 0; position: relative; }
.section__inner {
  max-width: var(--container);
  margin: 0 auto;
  padding: 0 var(--gutter);
}

.section__eyebrow {
  font-family: var(--f-body);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--c-secondary);
  margin-bottom: var(--s-4);
}

.section__title {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: clamp(32px, 4vw, 56px);
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--c-ink);
  max-width: 22ch;
  /* Lines of similar length, so a heading never ends with one word
     alone. Browsers without support ignore it. */
  text-wrap: balance;
}

.section__title--display {
  font-size: clamp(40px, 6.5vw, 88px);
  line-height: 1.02;
  max-width: 18ch;
}

.section__header { margin-bottom: clamp(48px, 7vw, 96px); }

/* =====================================================================
   NAV
   ===================================================================== */
.nav {
  position: fixed;
  top: 0; left: 0; right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  padding: 20px var(--gutter);
  background: linear-gradient(to bottom, rgba(12, 12, 12, 0.85), rgba(12, 12, 12, 0));
  backdrop-filter: blur(0px);
  transition: background var(--dur) var(--ease), backdrop-filter var(--dur) var(--ease), padding var(--dur) var(--ease);
}
.nav.is-scrolled {
  background: rgba(12, 12, 12, 0.92);
  backdrop-filter: blur(10px);
  padding: 14px var(--gutter);
  border-bottom: 1px solid var(--c-rule);
}
/* Lion mark on the left of the nav, from the closing page of the PhD
   thesis. images/lion-mark.png is a mask, so the colour comes from the
   background and changes on hover. */
.nav__brand {
  margin-right: auto;
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  padding: 2px;
  border-radius: 4px;
  /* The mark is taller than the link text. Negative block margins let it
     sit in the nav padding, so the bar keeps the height it had before
     the lion was added. */
  margin-block: -10px;
}
.nav__brand-mark {
  display: block;
  width: 36px;
  height: 40px;
  background: var(--c-secondary);
  -webkit-mask: url("images/lion-mark.png") center / contain no-repeat;
          mask: url("images/lion-mark.png") center / contain no-repeat;
  transition: background var(--dur-fast) var(--ease);
}
.nav__brand:hover .nav__brand-mark { background: var(--c-ink); }
.nav__brand:focus-visible {
  outline: 2px solid var(--c-secondary);
  outline-offset: 4px;
}

.nav__links {
  display: flex;
  gap: clamp(20px, 2.2vw, 36px);
}
.nav__links a {
  font-size: 14px;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--c-ink-dim);
  position: relative;
  transition: color var(--dur-fast) var(--ease);
}
.nav__links a::after {
  content: "";
  position: absolute;
  left: 0; bottom: -6px;
  width: 0; height: 1px;
  background: var(--c-secondary);
  transition: width var(--dur) var(--ease);
}
.nav__links a:hover { color: var(--c-ink); }
.nav__links a:hover::after { width: 100%; }

/* Active section: the link corresponding to the section in view glows
   and gets a persistent underline so you always know where you are. */
.nav__links a.is-active {
  color: var(--c-secondary);
  text-shadow: 0 0 18px rgba(192, 116, 92, 0.55);
}
.nav__links a.is-active::after {
  width: 100%;
  background: var(--c-secondary);
  box-shadow: 0 0 8px rgba(192, 116, 92, 0.7);
}

/* Between tablet and small laptop widths the eight links fill most of
   the bar, so they sit a little closer and the lion keeps clear space.
   Placed after the base link rules so it overrides them. */
@media (min-width: 768px) and (max-width: 1023px) {
  .nav { gap: var(--s-4); }
  .nav__links { gap: 14px; }
  .nav__links a { letter-spacing: 0.08em; }
}

.nav__toggle {
  display: none;
  flex-direction: column;
  gap: 5px;
  /* The hit area is at least 44 x 44 px, the iOS and WCAG recommended
     minimum for touch targets. The three lines stay centred in it. */
  padding: 12px;
  min-width: 44px;
  min-height: 44px;
  justify-content: center;
  align-items: center;
  z-index: 101;
}
.nav__toggle span { display: block; width: 24px; height: 1px; background: var(--c-ink); transition: transform var(--dur-fast), opacity var(--dur-fast); }
.nav__toggle.is-open span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.nav__toggle.is-open span:nth-child(2) { opacity: 0; }
.nav__toggle.is-open span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* =====================================================================
   HERO
   ===================================================================== */
.hero {
  position: relative;
  min-height: 100vh;
  min-height: 100svh;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  padding: var(--s-7) var(--gutter) clamp(48px, 8vw, 96px);
  isolation: isolate;
}
.hero__image {
  position: absolute;
  inset: 0;
  z-index: -2;
}
.hero__image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Landscape stage shot: centre-right bias keeps Laith plus the bike
     and MOVE signage visible on wider screens, and lands on Laith + the
     bike when the viewport narrows to mobile portrait. */
  object-position: 60% 55%;
  filter: contrast(1.05) brightness(0.85);
  transform: scale(1.02);
}
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Combined diagonal + bottom fade. Text sits in the dark lower-left;
     the portrait stays visible in the upper-right. */
  background:
    linear-gradient(
      to right,
      rgba(12, 12, 12, 0.92) 0%,
      rgba(12, 12, 12, 0.78) 28%,
      rgba(12, 12, 12, 0.4)  55%,
      rgba(12, 12, 12, 0.05) 80%,
      rgba(12, 12, 12, 0)   100%),
    linear-gradient(
      to bottom,
      rgba(12, 12, 12, 0.35) 0%,
      rgba(12, 12, 12, 0.05) 22%,
      rgba(12, 12, 12, 0.0)  45%,
      rgba(12, 12, 12, 0.55) 82%,
      rgba(12, 12, 12, 0.98) 100%);
}
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(
    ellipse at 78% 35%,
    rgba(192, 116, 92, 0.18) 0%,
    rgba(99, 34, 58, 0.08)   35%,
    rgba(12, 12, 12, 0)      65%);
  pointer-events: none;
}

.hero__content {
  max-width: 900px;
  animation: heroFadeIn 1.2s var(--ease) both;
}
.hero__eyebrow {
  font-size: 14px;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--c-secondary);
  margin-bottom: var(--s-4);
  /* Subtle dark halo so the eyebrow stays legible if it lands over a
     bright section of the photo on narrow mobile crops, where the
     gradient is softer than on desktop. */
  text-shadow: 0 0 2px rgba(12, 12, 12, 0.9), 0 1px 10px rgba(12, 12, 12, 0.8);
}
.hero__title {
  font-family: var(--f-display);
  font-weight: 400;
  /* Two-line stack lets us drop the per-line length so the name stays
     in the dark left column without reaching across into the photo
     subject. The max cap is lower than the single-line version for the
     same reason. */
  font-size: clamp(56px, 9.5vw, 140px);
  line-height: 0.94;
  letter-spacing: -0.02em;
  margin-bottom: var(--s-4);
  /* Soft dark halo for edge contrast where photo highlights poke past
     the gradient overlay. Two layers: a close-in bleed and a further
     ambient wash. */
  text-shadow:
    0 2px 12px rgba(12, 12, 12, 0.5),
    0 0 40px rgba(12, 12, 12, 0.3);
}
.hero__title-line {
  display: block;
}
/* Short windows, such as a laptop browser that is not full height or a
   phone held sideways. The name is also limited by the window height and
   the vertical padding shrinks, so the eyebrow, name, tagline and meta
   line fit between the nav and the bottom of the first screen. */
@media (min-width: 768px) and (max-height: 760px) {
  .hero { padding-top: 88px; padding-bottom: clamp(28px, 6vh, 96px); }
  .hero__title { font-size: min(clamp(56px, 9.5vw, 140px), 18vh); }
  .hero__eyebrow { margin-bottom: var(--s-3); }
  .hero__tagline { margin-bottom: var(--s-4); }
}
/* Very short windows. The name goes on one line. */
@media (min-width: 768px) and (max-height: 500px) {
  .hero__title-line { display: inline; }
  .hero__title { font-size: min(clamp(56px, 9.5vw, 140px), 20vh); margin-bottom: var(--s-3); }
  .hero__tagline { max-width: none; }
}
.hero__tagline {
  font-family: var(--f-display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(20px, 2.4vw, 32px);
  line-height: 1.3;
  color: var(--c-ink-dim);
  max-width: 28ch;
  margin-bottom: var(--s-5);
  text-shadow: 0 1px 6px rgba(12, 12, 12, 0.5);
}
.hero__meta {
  font-size: 15px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--c-ink-muted);
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}
.hero__meta .dot { color: var(--c-secondary); }

@keyframes heroFadeIn {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* =====================================================================
   HIGHLIGHT — marker-stroke effect on key words.
   We use a vertical gradient so the color only fills the middle band of
   the text line. That keeps big display headings readable and stops the
   highlight from overflowing into adjacent lines, while the horizontal
   animation (background-size 0% -> 100%) gives the "highlighter drawing
   across the word" effect. Triggered by .is-drawn added by JS.
   ===================================================================== */
.highlight {
  display: inline;
  padding: 0 0.18em;
  border-radius: 2px;
  background-image: linear-gradient(
    to bottom,
    transparent 0%,
    transparent 14%,
    var(--c-secondary) 14%,
    var(--c-secondary) 92%,
    transparent 92%,
    transparent 100%);
  background-size: 0% 100%;
  background-repeat: no-repeat;
  background-position: left center;
  color: var(--c-ink);
  transition: background-size 1.4s cubic-bezier(0.22, 1, 0.36, 1);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}
.highlight.is-drawn {
  background-size: 100% 100%;
}
/* Stagger the two highlights in the hero tagline so the second word
   starts drawing after the first has had a moment to register. Sweep
   speed itself inherits the site-wide 1.4s default for a uniform
   highlight cadence across every section. */
.hero__tagline .highlight:nth-of-type(2) {
  transition-delay: 0.35s;
}

/* =====================================================================
   ABOUT
   ===================================================================== */
.section--about { background: var(--c-bg); }

.about {
  display: grid;
  grid-template-columns: 1.1fr 0.9fr;
  gap: clamp(48px, 6vw, 96px);
  align-items: center;
}
.about__text .section__title { margin-bottom: var(--s-5); }
.about__body {
  font-size: clamp(17px, 1.25vw, 20px);
  line-height: 1.7;
  color: var(--c-ink-dim);
  margin-bottom: var(--s-4);
  max-width: 56ch;
}
.about__body + .about__body { margin-top: var(--s-4); }
.about__image {
  position: relative;
  aspect-ratio: 3 / 4;
  border-radius: var(--r-sm);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
}
.about__image::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(
    135deg,
    rgba(192, 116, 92, 0.0) 0%,
    rgba(99, 34, 58, 0.18) 100%);
  pointer-events: none;
}
.about__image img {
  width: 100%; height: 100%;
  object-fit: cover;
  object-position: center 30%;
  transition: transform var(--dur) var(--ease);
}
.about__image:hover img { transform: scale(1.03); }

/* =====================================================================
   BUTTONS
   ===================================================================== */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-2);
  padding: 14px 30px;
  border-radius: var(--r-pill);
  font-size: 15px;
  font-weight: 400;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  transition: all var(--dur) var(--ease);
  margin-top: var(--s-4);
  border: 1px solid transparent;
  white-space: nowrap;
}
.btn--primary {
  background: var(--c-ink);
  color: var(--c-bg);
  border-color: var(--c-ink);
}
.btn--primary:hover {
  background: var(--c-secondary);
  border-color: var(--c-secondary);
  color: var(--c-bg);
}
.btn--outline {
  border-color: var(--c-ink);
  color: var(--c-ink);
}
.btn--outline:hover {
  background: var(--c-ink);
  color: var(--c-bg);
}

/* =====================================================================
   JOURNEY / WORLD MAP
   Dotted world map with animated career-path arcs.
   The base map is a static SVG file (images/world-dots.svg). The arcs
   and city markers are static markup in the overlay SVG, which shares
   the map's 126 x 60 viewBox. Animation triggers when .journey-map gets
   .is-animating, added by script.js when the map scrolls into view.
   ===================================================================== */
.section--journey { background: var(--c-bg); }

.section__lede {
  font-family: var(--f-display);
  font-weight: 400;
  font-style: italic;
  font-size: clamp(18px, 1.4vw, 22px);
  line-height: 1.55;
  color: var(--c-ink-dim);
  max-width: 58ch;
  margin-top: var(--s-4);
}

.journey-map {
  position: relative;
  width: 100%;
  aspect-ratio: 2 / 1;
  margin-bottom: var(--s-7);
  overflow: hidden;
  border-radius: var(--r-sm);
  background:
    radial-gradient(ellipse at center, rgba(192, 116, 92, 0.05), transparent 70%),
    var(--c-bg-alt);
}

.journey-map__world,
.journey-map__world img,
.journey-map__arcs {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* object-fit: contain matches the xMidYMid meet scaling of the arcs
   layer, so dots and arcs stay aligned at every container size. */
.journey-map__world img {
  object-fit: contain;
  opacity: 0.9;
}
.journey-map__arcs { z-index: 2; }

/* Arcs use pathLength=1 so the drawing animation is viewBox-agnostic. */
.journey-arc {
  stroke-dasharray: 1;
  stroke-dashoffset: 1;
  transition: stroke-dashoffset 2.4s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: var(--delay, 0s);
  filter: drop-shadow(0 0 4px rgba(192, 116, 92, 0.45));
}
.is-animating .journey-arc {
  stroke-dashoffset: 0;
}

/* Endpoint groups fade in after their arc starts drawing. */
.journey-point {
  opacity: 0;
  transition: opacity 0.6s ease;
  transition-delay: var(--delay, 0s);
}
.is-animating .journey-point {
  opacity: 1;
}
.journey-point__dot {
  fill: var(--c-secondary);
  filter: url(#pointGlow);
}
.journey-point__pulse {
  fill: var(--c-secondary);
  opacity: 0;
  transform-box: fill-box;
  transform-origin: center;
}
.is-animating .journey-point__pulse {
  animation: journeyPulse 2.8s ease-out infinite;
  animation-delay: var(--delay, 0s);
}
@keyframes journeyPulse {
  0%   { opacity: 0.7; transform: scale(1); }
  80%  { opacity: 0;   transform: scale(5); }
  100% { opacity: 0;   transform: scale(5); }
}
.journey-point__label {
  /* All sizes in user units of the 126x60 viewBox. */
  font-family: var(--f-body);
  font-size: 1.9px;
  font-weight: 400;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  fill: #fff;
  paint-order: stroke;
  stroke: rgba(12, 12, 12, 0.9);
  stroke-width: 0.5px;
}

/* =====================================================================
   EDUCATION TIMELINE (horizontal)
   Three "stations" joined by a thin horizontal line, year above each dot,
   degree as the visual title below.
   ===================================================================== */
.edu-timeline {
  list-style: none;
  margin: clamp(24px, 4vw, 48px) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(24px, 3vw, 48px);
  position: relative;
}
/* The connecting line sits across the row of dots. Left/right insets
   are overridden from JS via --rail-start-x / --rail-end-x so the line
   starts at the centre of the first dot and ends at the centre of the
   last, rather than at the CSS-computed 1/6 inset (which can drift
   slightly off dot centres due to grid gaps). The rail draws in
   left-to-right as --timeline-progress climbs from 0 to 1, tied to
   scroll position by JS. Shrinks back when you scroll upwards. Uses
   the same muted rule colour as the Experience rail so the two
   timelines read as a matched pair. */
.edu-timeline::before {
  content: "";
  position: absolute;
  top: 42px;
  left: var(--rail-start-x, calc(100% / 6));
  right: var(--rail-end-x, calc(100% / 6));
  height: 1px;
  background: var(--c-rule);
  z-index: 0;
  transform: scaleX(var(--timeline-progress, 0));
  transform-origin: left center;
  will-change: transform;
}

.edu-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  position: relative;
}

.edu-step__year {
  font-family: var(--f-display);
  font-size: 13px;
  color: var(--c-secondary);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  margin-bottom: var(--s-3);
}

.edu-step__dot {
  display: block;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--c-bg);
  border: 2px solid var(--c-secondary);
  box-shadow: 0 0 0 6px var(--c-bg); /* masks the line behind the dot */
  position: relative;
  z-index: 1;
  margin-bottom: var(--s-5);
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* Hover nudge only scales the dot. The background fill is reserved
   for the scroll-linked .is-reached state, so the two states read
   distinctly and hovering over an edu-step does not look like the
   line has already reached it. */
.edu-step:hover .edu-step__dot {
  transform: scale(1.15);
}
/* Dot fills with terracotta once the rail's draw-in has reached it.
   Paired with the scroll-linked driver in script.js, each dot flips on
   the moment the line crosses its fractional position along the rail. */
.edu-step.is-reached .edu-step__dot {
  background: var(--c-secondary);
  box-shadow: 0 0 0 6px var(--c-bg), 0 0 12px rgba(192, 116, 92, 0.55);
}

.edu-step__degree {
  font-family: var(--f-display);
  font-weight: 500;
  font-size: clamp(20px, 1.8vw, 28px);
  line-height: 1.2;
  letter-spacing: -0.005em;
  color: var(--c-ink);
  margin: 0 0 var(--s-2);
  max-width: 22ch;
}
/* In the three-column layout every degree title reserves two lines, so
   the school and country lines start at the same height in each column
   whether the title wraps or not. */
@media (min-width: 768px) {
  .edu-step__degree { min-height: 2.4em; }
}

.edu-step__school {
  font-family: var(--f-body);
  font-size: clamp(14px, 1.05vw, 16px);
  color: var(--c-ink-dim);
  line-height: 1.45;
}

.edu-step__place {
  margin-top: var(--s-1);
  font-size: 11px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--c-ink-muted);
}

/* Thesis title under the PhD, set in the display italic. */
.edu-step__thesis {
  margin-top: var(--s-3);
  font-family: var(--f-display);
  font-style: italic;
  font-size: clamp(15px, 1.1vw, 17px);
  line-height: 1.4;
  color: var(--c-ink-dim);
  max-width: 30ch;
  text-wrap: balance;
}

@media (max-width: 767px) {
  /* Vertical timeline on mobile. The dots and a thin terracotta rail
     anchor the three degrees as milestones in order, mirroring the
     Experience section's pattern so the two timelines feel related. */
  .edu-timeline {
    grid-template-columns: 1fr;
    gap: var(--s-6);
    padding-left: 32px;
    position: relative;
  }
  /* Reinstate the connecting line, now vertical, running top-to-bottom
     along the left edge. Top/bottom insets are overridden from JS via
     --rail-start-y / --rail-end-y so the line starts at the centre of
     the first dot and ends at the centre of the last. Scroll-linked
     draw-in: scaleY(0->1) as --timeline-progress fills, origin at top. */
  .edu-timeline::before {
    display: block;
    top: var(--rail-start-y, 0);
    bottom: var(--rail-end-y, 0);
    left: 11px;
    right: auto;
    width: 1px;
    height: auto;
    background: var(--c-rule);
    transform: scaleY(var(--timeline-progress, 0));
    transform-origin: top center;
  }
  .edu-step {
    align-items: flex-start;
    text-align: left;
  }
  /* Dot jumps out of the flow and lands on the rail. The box-shadow on
     the base rule still masks a small disc of background so the rail
     does not bleed through the dot. */
  .edu-step__dot {
    position: absolute;
    left: -26px;
    top: 6px;
    margin-bottom: 0;
  }
  .edu-step__year {
    margin-bottom: var(--s-1);
  }
  .edu-step__degree {
    max-width: none;
  }
  .journey-point__label { display: none; }
}

/* =====================================================================
   STATS GRID — "By the numbers"
   6-card grid. First card (feature) spans 2 columns on desktop with a
   larger value treatment. The rest are equal-weight cards in a clean grid.
   ===================================================================== */
.section--stats { background: var(--c-bg); }

.stats-grid {
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: clamp(16px, 2vw, 24px);
}

/*
  Layout, 6 columns, no empty trailing cells:
  [ F F F F C2 C2 ]    feature spans 4 cols and 2 rows
  [ F F F F C3 C3 ]    two small cards stack on the right of the feature
  [ C4 C4 C5 C5 C6 C6 ]  three small cards fill the final row
*/

.stat-card {
  grid-column: span 2;
  position: relative;
  padding: clamp(28px, 3vw, 44px);
  /* Flat panel background, uniform across feature and small cards.
     Visual interest lives in the hover glow layer (::before), not
     baked into the resting state of the card. */
  background: var(--c-bg-alt);
  border: 1px solid var(--c-rule);
  /* Softer radius than the rest of the site (4 px) so the impact cards
     read as deliberate panels rather than angular tiles. */
  border-radius: 14px;
  transition: border-color var(--dur) var(--ease), background var(--dur) var(--ease), transform var(--dur) var(--ease);
  overflow: hidden;
  isolation: isolate;
  /* Cards are <a> elements linking to the paper or in-page anchor where
     the claim is made. Keep inherited typography, add pointer cursor via
     the <a> default, and surface a keyboard-focus ring for accessibility. */
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}
.stat-card:focus-visible {
  outline: 2px solid var(--c-secondary);
  outline-offset: 4px;
}
.stat-card::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  /* Default glow: a quiet radial wash anchored to the top-right
     corner. This is the touch-friendly baseline — mobile and tablet
     get this stable, understated highlight. The cursor-tracked
     variant is layered on top inside a hover-capable media query. */
  background: radial-gradient(
    ellipse at top right,
    rgba(192, 116, 92, 0.08) 0%,
    rgba(192, 116, 92, 0) 50%);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  /* Desktop-only cursor-tracked spotlight. --mx / --my are written
     from script.js on every mousemove frame, so the glow centre sits
     under the pointer instead of anchored to a fixed corner. Gated
     behind (hover: hover) and (pointer: fine) so touch devices never
     attempt to emulate mouse tracking; they keep the corner wash
     defined above. Explicit 320 px circle gives consistent reach
     across the small cards and the larger feature card. */
  .stat-card::before {
    background: radial-gradient(
      320px circle at var(--mx, 50%) var(--my, 50%),
      rgba(192, 116, 92, 0.16) 0%,
      rgba(192, 116, 92, 0) 70%);
  }
}
.stat-card:hover {
  border-color: rgba(192, 116, 92, 0.5);
  background: var(--c-bg-soft);
  transform: translateY(-2px);
}
.stat-card:hover::before { opacity: 1; }

.stat-card--feature {
  grid-column: span 4;
  grid-row: span 2;
  /* The feature card keeps its size advantage (spans 4 cols, 2 rows)
     but inherits the same tint and border as the small cards. What
     sets it apart is its scale, not its colour. As in the small cards,
     the value, label and context sit at the top and the source link is
     pinned to the bottom (.stat-card__source has margin-top: auto). */
  min-height: 280px;
}
.stat-card--feature .stat-card__value {
  font-size: clamp(72px, 13vw, 200px);
}
/* From 992 px the feature card is two rows tall, set by the two cards
   stacked beside it. The value is sized from the card's own width (cqw)
   so the large figure fills the card instead of leaving a third of it
   empty. */
@media (min-width: 992px) {
  .stat-card--feature { container-type: inline-size; }
  .stat-card--feature .stat-card__value { font-size: clamp(160px, 40cqw, 300px); }
}
.stat-card--feature .stat-card__label {
  font-size: clamp(20px, 1.8vw, 28px);
  padding-top: var(--s-2);
}
.stat-card--feature .stat-card__context {
  font-size: 16px;
  max-width: 40ch;
}

.stat-card__value {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: clamp(48px, 5.5vw, 80px);
  line-height: 1;
  color: var(--c-secondary);
  letter-spacing: -0.02em;
  margin-bottom: var(--s-3);
  display: inline-flex;
  align-items: baseline;
  /* Tabular numbers prevent digit jitter during the count-up animation.
     Without this, proportional fonts reflow as each digit is replaced. */
  font-variant-numeric: tabular-nums;
}
.stat-card__number {
  display: inline-block;
  min-width: 1ch;
}
.stat-card__prefix,
.stat-card__suffix {
  font-size: 0.55em;
  color: var(--c-secondary);
  opacity: 0.85;
}
.stat-card__prefix { margin-right: 0.05em; }
.stat-card__suffix { margin-left: 0.02em; }
/* Word prefix such as "Up to". Same size and colour as the currency
   prefix, with a word space before the number. */
.stat-card__prefix--text {
  margin-right: 0.25em;
  letter-spacing: 0;
  white-space: nowrap;
}

.stat-card__label {
  text-wrap: balance;
  font-family: var(--f-display);
  font-weight: 400;
  font-size: clamp(18px, 1.4vw, 22px);
  line-height: 1.25;
  color: var(--c-ink);
  margin-bottom: var(--s-2);
}
.stat-card__context {
  font-size: 14px;
  line-height: 1.5;
  color: var(--c-ink-muted);
  letter-spacing: 0.02em;
}

/* Source line. Sits at the bottom of every card, pushing down via
   margin-top: auto so short small cards and the tall feature card both
   align their source pill to the bottom edge. Reveal colour on hover to
   signal that the whole card is the click target. */
.stat-card__source {
  margin-top: auto;
  padding-top: var(--s-5);
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--f-body);
  font-size: 11px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--c-ink-muted);
  transition: color var(--dur-fast) var(--ease);
}
.stat-card:hover .stat-card__source,
.stat-card:focus-visible .stat-card__source {
  color: var(--c-secondary);
}
.stat-card__source-label {
  /* The abbreviated journal name sits next to the icon. On narrow small
     cards this still fits on one line because the label is brief. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 100%;
}
.stat-card__source-icon {
  width: 12px;
  height: 12px;
  flex-shrink: 0;
  transition: transform var(--dur-fast) var(--ease);
}
/* Subtle lift on the external-link chevron. The anchor arrow gets a
   slight push-right instead, matching its forward-pointing shape. */
.stat-card:hover .stat-card__source-icon {
  transform: translate(1.5px, -1.5px);
}
.stat-card:hover .stat-card__source-icon--anchor {
  transform: translateX(2px);
}
.stat-card--feature .stat-card__source {
  font-size: 12px;
}

.stats-caption {
  text-wrap: balance;
  margin-top: var(--s-6);
  font-size: 13px;
  color: var(--c-ink-muted);
  font-style: italic;
  font-family: var(--f-display);
  text-align: center;
  max-width: 72ch;
  margin-left: auto;
  margin-right: auto;
}

@media (max-width: 991px) {
  /* Tablet: collapse the 6-col grid to 2 equal columns. Feature card
     returns to a single-row rectangle across the top; the five small
     cards stack underneath, two per row, with the final card taking a
     full-width slot so there is no trailing empty cell. */
  .stats-grid { grid-template-columns: repeat(2, 1fr); }
  .stat-card { grid-column: span 1; }
  .stat-card--feature { grid-column: span 2; grid-row: span 1; min-height: 220px; }
  .stat-card:last-child { grid-column: span 2; }
}
@media (max-width: 600px) {
  .stats-grid { grid-template-columns: 1fr; }
  .stat-card, .stat-card--feature { grid-column: span 1 !important; grid-row: span 1; min-height: 0; }
}

/* =====================================================================
   PULL QUOTE
   ===================================================================== */
.pullquote {
  padding: clamp(68px, 9.5vw, 132px) 0;
  background: linear-gradient(
    180deg,
    var(--c-bg) 0%,
    var(--c-wine) 50%,
    var(--c-bg) 100%);
  position: relative;
}
.pullquote::before {
  content: "";
  position: absolute;
  left: 50%;
  /* Anchor the decorative accent to a fixed pixel distance from the
     top of the section and let its height scale with the viewport.
     A percentage-based top kept pulling it into the quote text on
     narrow screens where the section was short; fixed top + clamp()
     height guarantees clearance from the first line at every width. */
  top: clamp(20px, 4vw, 44px);
  transform: translateX(-50%);
  width: 1px;
  height: clamp(28px, 5vw, 60px);
  background: var(--c-secondary);
}
.pullquote blockquote p {
  text-wrap: balance;
  font-family: var(--f-display);
  font-style: italic;
  font-weight: 400;
  font-size: clamp(26px, 3.5vw, 48px);
  line-height: 1.25;
  text-align: center;
  max-width: 24ch;
  margin: 0 auto;
  color: var(--c-ink);
  letter-spacing: -0.005em;
}

/* =====================================================================
   PUBLICATIONS
   ===================================================================== */
.section--publications { background: var(--c-bg); }

/* Two groups, Published then Under review, each with a small label
   heading above its list. */
.pub-group + .pub-group { margin-top: var(--s-7); }
.pub-group__title {
  font-family: var(--f-body);
  font-size: 13px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--c-ink-dim);
  margin-bottom: var(--s-3);
}

.pub-list { display: flex; flex-direction: column; }
.pub {
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: var(--s-6);
  padding: var(--s-6) 0;
  border-top: 1px solid var(--c-rule);
  transition: background var(--dur) var(--ease);
}
.pub:last-child { border-bottom: 1px solid var(--c-rule); }
.pub:hover { background: rgba(255, 255, 255, 0.02); }

.pub__year {
  font-family: var(--f-display);
  font-size: clamp(28px, 3vw, 44px);
  font-weight: 400;
  color: var(--c-secondary);
  line-height: 1;
  padding-top: 6px;
}
.pub__title {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: clamp(20px, 1.7vw, 26px);
  line-height: 1.25;
  margin-bottom: var(--s-2);
  max-width: 52ch;
  text-wrap: pretty;
}
.pub__authors {
  color: var(--c-ink-dim);
  font-size: 15px;
  margin-bottom: 6px;
}
.pub__venue {
  color: var(--c-ink-muted);
  font-family: var(--f-display);
  font-style: italic;
  font-size: 15px;
  letter-spacing: 0.01em;
}
/* DOI link stays upright so the URL does not look slanted, which
   would read as an error rather than a design choice. */
.pub__venue a {
  font-family: var(--f-body);
  font-style: normal;
  color: var(--c-secondary);
  border-bottom: 1px solid transparent;
  transition: border-color var(--dur-fast) var(--ease);
  /* A DOI is one long word. Let it break on very narrow screens
     (320 px) instead of pushing the page wider than the viewport. */
  overflow-wrap: anywhere;
  /* Each link sits on its own line under the journal, so it is in the
     same place in every entry and is not split from the venue line. */
  display: block;
  width: fit-content;
  max-width: 100%;
}
.pub__venue a:hover { border-color: var(--c-secondary); }
.pub__venue a:focus-visible {
  outline: 2px solid var(--c-secondary);
  outline-offset: 3px;
  border-radius: 2px;
}
.pub__note {
  color: var(--c-ink-muted);
  font-size: 14px;
  margin-bottom: 6px;
}

/* =====================================================================
   EXPERIENCE / TIMELINE
   ===================================================================== */
.section--experience { background: var(--c-bg); }

.timeline {
  display: flex;
  flex-direction: column;
  gap: var(--s-7);
  position: relative;
}
/* Rail draws top-to-bottom as --timeline-progress fills. Same colour
   and treatment as the Education rail so the two timelines share a
   visual language. Top/bottom insets come from JS via --rail-start-y
   / --rail-end-y so the line begins at the first dot's centre and
   ends at the last dot's centre instead of bleeding past either end. */
.timeline::before {
  content: "";
  position: absolute;
  left: 140px;
  top: var(--rail-start-y, 0);
  bottom: var(--rail-end-y, 0);
  width: 1px;
  background: var(--c-rule);
  transform: scaleY(var(--timeline-progress, 0));
  transform-origin: top center;
  will-change: transform;
}

.timeline__item {
  display: grid;
  /* 140 px fits the longest date range, "2026 — Present" (about 123 px),
     on one line with a clear gap before the dot, and leaves the rest of
     the width to the role text. A long location such as "Amsterdam,
     Netherlands" wraps after the comma. The rail (left: 140px) and the
     dot (left: 134px) follow this width. */
  grid-template-columns: 140px 1fr;
  gap: var(--s-7);
  position: relative;
}
.timeline__item::before {
  content: "";
  position: absolute;
  left: 134px; top: 10px;
  width: 13px; height: 13px;
  border-radius: 50%;
  background: var(--c-bg);
  border: 2px solid var(--c-secondary);
  z-index: 1;
  transition: background var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}
/* Same reach-fill behaviour as the education dots: terracotta fill +
   a soft glow arrives once the scroll-linked rail reaches the dot. */
.timeline__item.is-reached::before {
  background: var(--c-secondary);
  box-shadow: 0 0 12px rgba(192, 116, 92, 0.55);
}

.timeline__meta {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding-top: 6px;
}
.timeline__dates { white-space: nowrap; }
.timeline__dates {
  font-family: var(--f-display);
  font-size: 17px;
  color: var(--c-secondary);
  font-weight: 500;
  letter-spacing: 0.02em;
}
.timeline__location {
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--c-ink-muted);
}
.timeline__role {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: clamp(22px, 2vw, 30px);
  line-height: 1.2;
  margin-bottom: 4px;
}
.timeline__org {
  color: var(--c-ink-dim);
  font-family: var(--f-display);
  font-style: italic;
  font-weight: 400;
  font-size: 18px;
  letter-spacing: 0.005em;
  margin-bottom: var(--s-3);
}
/* Role heading with the employer logo on its left. The logo sits in a
   round badge, filled with the page black and outlined in terracotta.
   The mark itself is a PNG mask from images/, painted terracotta. The
   badge is decorative because the employer is named in the text. */
.timeline__head {
  display: flex;
  align-items: center;
  gap: var(--s-4);
  margin-bottom: var(--s-3);
}
.timeline__head .timeline__org { margin-bottom: 0; }
.org-badge {
  flex-shrink: 0;
  display: grid;
  place-items: center;
  width: 60px;
  height: 60px;
  border-radius: 50%;
  background: var(--c-bg);
  border: 1px solid var(--c-secondary);
}
.org-badge__mark {
  width: 64%;
  height: 64%;
  background: var(--c-secondary);
  -webkit-mask: var(--org-logo) center / contain no-repeat;
          mask: var(--org-logo) center / contain no-repeat;
}
.org-badge--tilburg   { --org-logo: url("images/logo-tilburg.png"); }
.org-badge--pon       { --org-logo: url("images/logo-pon.png"); }
.org-badge--vu        { --org-logo: url("images/logo-vu.png"); }
.org-badge--liverpool { --org-logo: url("images/logo-liverpool.png"); }
.org-badge--reckitt   { --org-logo: url("images/logo-reckitt.png"); }
/* The Pon wordmark is wide and thin and the Tilburg emblem has open
   space between its diamonds, so both get a little more of the badge. */
.org-badge--pon .org-badge__mark,
.org-badge--tilburg .org-badge__mark { width: 74%; height: 74%; }

/* One-line description of the role, directly under the employer and
   above the bullets. Same style for every entry. */
.timeline__summary {
  color: var(--c-ink-muted);
  font-size: 16px;
  line-height: 1.6;
  margin-bottom: var(--s-3);
}
.timeline__summary:last-child { margin-bottom: 0; }
.timeline__points {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}
.timeline__points li {
  position: relative;
  padding-left: 22px;
  color: var(--c-ink-dim);
  font-size: 16px;
  line-height: 1.6;
}
/* Editorial diamond marker in terracotta. A rotated square reads as
   more considered than a dash or a disc while keeping the mark quiet
   enough not to compete with the prose. */
.timeline__points li::before {
  content: "";
  position: absolute;
  left: 2px; top: 10px;
  width: 7px; height: 7px;
  background: var(--c-secondary);
  transform: rotate(45deg);
  transition: transform var(--dur) var(--ease), background var(--dur) var(--ease);
}
.timeline__points li:hover::before {
  transform: rotate(45deg) scale(1.25);
}

/* =====================================================================
   TALKS
   ===================================================================== */
.section--talks {
  background: linear-gradient(180deg, var(--c-bg), var(--c-bg-alt));
}
.talks { display: flex; flex-direction: column; }
.talk {
  display: grid;
  grid-template-columns: 100px 1fr;
  gap: var(--s-5);
  padding: var(--s-5) 0;
  border-top: 1px solid var(--c-rule);
  align-items: baseline;
  transition: transform var(--dur) var(--ease);
}
.talk:last-child { border-bottom: 1px solid var(--c-rule); }
.talk:hover {
  transform: translateX(8px);
  background: rgba(255, 255, 255, 0.02);
}
.talk__year {
  font-family: var(--f-display);
  font-size: 22px;
  color: var(--c-secondary);
}
.talk__title {
  font-family: var(--f-display);
  font-weight: 400;
  font-size: clamp(18px, 1.5vw, 22px);
  line-height: 1.3;
  margin-bottom: 4px;
  max-width: 60ch;
  text-wrap: pretty;
}
.talk__venue {
  font-family: var(--f-display);
  font-style: italic;
  font-size: 15px;
  color: var(--c-ink-muted);
  letter-spacing: 0.01em;
}

/* =====================================================================
   CONTACT
   ===================================================================== */
.section--contact {
  background: var(--c-bg);
  padding-bottom: clamp(52px, 7vw, 88px);
}
.contact {
  display: grid;
  grid-template-columns: 0.8fr 1fr;
  gap: clamp(40px, 6vw, 96px);
  align-items: center;
}
.contact__image {
  aspect-ratio: 4 / 5;
  border-radius: var(--r-sm);
  overflow: hidden;
  box-shadow: var(--shadow-lg);
  position: relative;
  transition: box-shadow var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.contact__image::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(135deg, rgba(192,116,92,0) 40%, rgba(99,34,58,0.25) 100%);
  pointer-events: none;
  transition: background var(--dur) var(--ease);
  z-index: 1;
}
.contact__image img {
  width: 100%; height: 100%;
  object-fit: cover;
  /* Studio portrait, already cropped to 4/5 with a small margin above
     the head, so no further crop or zoom is needed. */
  object-position: center 30%;
  transform-origin: center 35%;
  transition: transform var(--dur) var(--ease), filter var(--dur) var(--ease);
}
.contact__image:hover img {
  transform: scale(1.04);
  filter: brightness(1.04);
}
.contact__image:hover {
  transform: translateY(-4px);
  box-shadow: 0 32px 70px -18px rgba(0, 0, 0, 0.8);
}
.contact__image:hover::after {
  background: linear-gradient(135deg, rgba(192,116,92,0.05) 30%, rgba(99,34,58,0.35) 100%);
}
.contact__text .section__title { margin-bottom: var(--s-5); max-width: 20ch; }

/* Contact action stack. Primary email button sits full-width on top,
   secondary LinkedIn and CV actions split the row below. This reads as
   a clear hierarchy of one main call-to-action plus two supporting
   actions, rather than three buttons of equal weight fighting for the
   eye. */
.contact__links {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
  margin-top: var(--s-4);
  max-width: 460px;
}
.contact__links .btn {
  margin-top: 0;
  width: 100%;
  justify-content: center;
}
.contact__links-row {
  display: grid;
  /* minmax(0, 1fr) keeps the two buttons the same width even when one
     label is longer. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s-3);
}
.contact__links-row .btn { padding-inline: 16px; }
.btn__icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
}

@media (max-width: 400px) {
  /* Very narrow viewports: stack the secondary actions too so the
     labels keep their full readable width. */
  .contact__links-row { grid-template-columns: 1fr; }
}

/* =====================================================================
   FOOTER
   ===================================================================== */
.footer {
  padding: var(--s-5) 0;
  background: var(--c-surface);
  border-top: 1px solid var(--c-rule);
}
.footer__inner {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--s-3);
  font-size: 14px;
  color: var(--c-ink-muted);
}
.footer__meta { font-style: italic; font-family: var(--f-display); text-wrap: balance; }

/* =====================================================================
   REVEAL ON SCROLL
   Default is fully visible. JS explicitly adds .reveal-pending to
   elements that are out of view on page load, which applies the hidden
   state. The IntersectionObserver then clears .reveal-pending as they
   scroll into view.
   ===================================================================== */
.reveal {
  opacity: 1;
  transform: none;
  transition: opacity 800ms var(--ease), transform 800ms var(--ease);
  will-change: opacity, transform;
}
.reveal.reveal-pending {
  opacity: 0;
  transform: translateY(28px);
}
.reveal.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* stagger within a grid */
.reveal.is-visible:nth-child(1) { transition-delay: 0ms; }
.reveal.is-visible:nth-child(2) { transition-delay: 80ms; }
.reveal.is-visible:nth-child(3) { transition-delay: 160ms; }
.reveal.is-visible:nth-child(4) { transition-delay: 240ms; }
.reveal.is-visible:nth-child(5) { transition-delay: 320ms; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .reveal { opacity: 1; transform: none; }
  *, *::before, *::after {
    animation-duration: 0.001s !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001s !important;
  }
}

/* =====================================================================
   SKILLS MARQUEE
   Infinite, horizontally scrolling carousel of software skills. The track
   is the full skill list duplicated so translating it by -50% produces a
   seamless loop. Hover pauses it. Under prefers-reduced-motion the first
   set wraps onto several rows and the aria-hidden copy is hidden.
   ===================================================================== */
.section--skills {
  background: var(--c-bg);
  overflow: hidden;
  /* Tight bottom so the marquee flows into Publications without an
     obvious dead zone, while the Publications eyebrow still has room
     to breathe on its top padding. */
  padding-bottom: clamp(28px, 4vw, 56px);
}

.marquee {
  position: relative;
  width: 100%;
  overflow: hidden;
  padding: clamp(16px, 2vw, 24px) 0;
  /* Soft fade on both edges so tiles appear and disappear gracefully
     rather than being sharply clipped by the viewport. */
  -webkit-mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
          mask-image: linear-gradient(to right, transparent, black 6%, black 94%, transparent);
}

@keyframes marquee {
  0%   { transform: translate3d(0, 0, 0); }
  100% { transform: translate3d(-50%, 0, 0); }
}

.marquee__track {
  display: flex;
  align-items: stretch;
  gap: clamp(16px, 2vw, 28px);
  width: max-content;
  padding-left: clamp(16px, 2vw, 28px);
  /* translate3d in the keyframes pushes the animation onto the GPU
     compositor, which makes it resilient against the per-tab frame
     throttling browsers sometimes apply. Without the 3d nudge the
     transform stays on the main thread and can stutter or stop under
     load. */
  animation: marquee 60s linear infinite;
  animation-play-state: running;
  backface-visibility: hidden;
  transform: translateZ(0);
}
.marquee:hover .marquee__track {
  animation-play-state: paused;
}

.skill-tile {
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 14px;
  min-width: 160px;
  height: 150px;
  padding: 22px 18px;
  /* Flat panel background, matching the Impact cards. The hover glow
     layer (::before) is where the visual lift lives. */
  background: var(--c-bg-alt);
  border: 1px solid var(--c-rule);
  border-radius: 14px;
  position: relative;
  overflow: hidden;
  isolation: isolate;
  transition: transform var(--dur) var(--ease),
              border-color var(--dur) var(--ease);
}
/* Default glow on tile: same corner-anchored wash as the Impact
   cards' touch baseline. Cursor-tracked variant is layered inside
   the hover-capable media query below. */
.skill-tile::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  background: radial-gradient(
    ellipse at top right,
    rgba(192, 116, 92, 0.1) 0%,
    rgba(192, 116, 92, 0) 55%);
  opacity: 0;
  transition: opacity var(--dur) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  /* Desktop cursor-tracked spotlight on tiles. Smaller radius than
     the stat cards because the tiles themselves are ~150 px tall, so
     a 320 px spotlight would wash the whole tile; 150 px keeps the
     highlight tightly bound to the cursor. */
  .skill-tile::before {
    background: radial-gradient(
      150px circle at var(--mx, 50%) var(--my, 50%),
      rgba(192, 116, 92, 0.18) 0%,
      rgba(192, 116, 92, 0) 70%);
  }
}
.skill-tile:hover {
  transform: translateY(-4px);
  border-color: rgba(192, 116, 92, 0.5);
}
.skill-tile:hover::before { opacity: 1; }

.skill-tile__icon {
  width: 44px;
  height: 44px;
  opacity: 0.9;
  transition: opacity var(--dur) var(--ease);
}
.skill-tile:hover .skill-tile__icon {
  opacity: 1;
}

.skill-tile__label {
  font-family: var(--f-body);
  font-size: 12px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--c-ink-dim);
  white-space: nowrap;
  transition: color var(--dur) var(--ease);
}
.skill-tile:hover .skill-tile__label {
  color: var(--c-ink);
}

@media (max-width: 767px) {
  .skill-tile {
    min-width: 128px;
    height: 128px;
    padding: 18px 14px;
    gap: 12px;
  }
  .skill-tile__icon { width: 36px; height: 36px; }
  .skill-tile__label { font-size: 11px; }
  .marquee__track { animation-duration: 45s; }
}

/* Reduced motion. No movement, so every skill has to be visible at
   once. The first set of tiles wraps onto several centred rows inside
   the page container, the aria-hidden copy is hidden and the edge fade
   is removed. Placed after the mobile
   rule so it also overrides the mobile animation duration. */
@media (prefers-reduced-motion: reduce) {
  .marquee {
    -webkit-mask-image: none;
            mask-image: none;
  }
  .marquee__track {
    animation: none;
    transform: none;
    flex-wrap: wrap;
    justify-content: center;
    width: auto;
    max-width: var(--container);
    margin: 0 auto;
    padding: 0 var(--gutter);
  }
  .marquee__track > .skill-tile[aria-hidden="true"] { display: none; }
}

/* =====================================================================
   PUBLICATION ABSTRACTS
   Collapsible <details>/<summary> blocks beneath each publication.
   Native HTML so a11y and keyboard behaviour come for free; the custom
   summary marker reads as a small eyebrow-style toggle consistent with
   the rest of the site's micro-typography.
   ===================================================================== */
.pub__details {
  margin-top: var(--s-3);
}
.pub__details summary {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  cursor: pointer;
  padding: 6px 0;
  font-family: var(--f-body);
  font-size: 12px;
  font-weight: 400;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--c-secondary);
  list-style: none;
  user-select: none;
  transition: color var(--dur-fast) var(--ease);
}
.pub__details summary::-webkit-details-marker { display: none; }
.pub__details summary::marker { content: ""; }
/* A single horizontal rule that grows into an arrow hint. Open state
   rotates it 90° so it reads as a "closed chevron into open chevron"
   hint without needing a glyph that might render poorly across fonts. */
.pub__details summary .pub__details-arrow {
  display: inline-block;
  width: 14px; height: 14px;
  position: relative;
  transition: transform var(--dur) var(--ease);
}
.pub__details summary .pub__details-arrow::before,
.pub__details summary .pub__details-arrow::after {
  content: "";
  position: absolute;
  background: currentColor;
  transition: transform var(--dur) var(--ease), opacity var(--dur-fast) var(--ease);
}
.pub__details summary .pub__details-arrow::before {
  left: 1px; right: 1px;
  top: 50%;
  height: 1px;
  transform: translateY(-50%);
}
.pub__details summary .pub__details-arrow::after {
  top: 1px; bottom: 1px;
  left: 50%;
  width: 1px;
  transform: translateX(-50%);
}
.pub__details[open] summary .pub__details-arrow::after {
  transform: translateX(-50%) scaleY(0);
  opacity: 0;
}
.pub__details[open] summary {
  color: var(--c-ink);
  margin-bottom: var(--s-3);
}
.pub__details summary:hover {
  color: var(--c-ink);
}
.pub__details summary:focus-visible {
  color: var(--c-ink);
  outline: 2px solid var(--c-secondary);
  outline-offset: 4px;
  border-radius: 2px;
}
.pub__details[open] > p {
  animation: pubAbstractIn 380ms var(--ease);
}
.pub__details p {
  color: var(--c-ink-dim);
  font-size: 16px;
  line-height: 1.7;
  max-width: 62ch;
  margin: 0;
}
@keyframes pubAbstractIn {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* =====================================================================
   FOOTER SOCIALS
   Icon + label pills aligned right of the footer text block. ORCID,
   LinkedIn, Email.
   ===================================================================== */
.footer__inner {
  align-items: center;
}
.footer__text {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.footer__socials {
  display: flex;
  gap: var(--s-4);
  align-items: center;
  margin: 0;
  padding: 0;
}
.footer__social {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--c-ink-dim);
  padding: 8px 4px;
  /* 44 px is the recommended minimum height for a touch target. */
  min-height: 44px;
  transition: color var(--dur-fast) var(--ease);
}
.footer__social:hover {
  color: var(--c-secondary);
}
.footer__social:focus-visible {
  color: var(--c-secondary);
  outline: 2px solid var(--c-secondary);
  outline-offset: 4px;
  border-radius: 2px;
}
.footer__icon {
  width: 18px;
  height: 18px;
  fill: currentColor;
  transition: fill var(--dur-fast) var(--ease);
}

/* =====================================================================
   RESPONSIVE
   ===================================================================== */
@media (max-width: 991px) {
  .about { grid-template-columns: 1fr; }
  .about__image { max-width: 520px; aspect-ratio: 4/5; }

  .contact { grid-template-columns: 1fr; }
  .contact__image { max-width: 480px; aspect-ratio: 4/5; }

  .timeline::before { left: 0; }
  .timeline__item { grid-template-columns: 1fr; gap: var(--s-3); padding-left: 24px; }
  /* top: 13px centres the dot on the date line. */
  .timeline__item::before { left: -6px; top: 13px; }
  /* If the date and location do not fit side by side, the location
     moves to its own line instead of either label breaking. */
  .timeline__meta { flex-direction: row; flex-wrap: wrap; column-gap: var(--s-3); row-gap: 2px; align-items: baseline; }
  .timeline__location { white-space: nowrap; }
  .org-badge { width: 48px; height: 48px; }
  .timeline__head { gap: var(--s-3); }

  .footer__inner {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-4);
  }
  .footer__socials { flex-wrap: wrap; gap: var(--s-3); margin-left: -4px; }
}

@media (max-width: 767px) {
  .nav__toggle { display: flex; }
  .nav__links {
    position: fixed;
    top: 0; right: 0;
    height: 100vh;
    /* dvh follows the visible height when the phone's toolbar shows. */
    height: 100dvh;
    overflow-y: auto;
    width: min(360px, 100%);
    background: var(--c-bg);
    border-left: 1px solid var(--c-rule);
    flex-direction: column;
    /* safe: on a short screen the list starts at the top and scrolls
       instead of being cut off at both ends. */
    justify-content: safe center;
    align-items: flex-start;
    padding: var(--s-7) var(--s-6);
    gap: var(--s-5);
    transform: translateX(100%);
    /* The closed drawer is hidden from the keyboard and from screen
       readers. visibility switches only after the slide-out finishes,
       and at once when the drawer opens. */
    visibility: hidden;
    transition: transform var(--dur) var(--ease), visibility 0s linear var(--dur);
  }
  .nav__links.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--dur) var(--ease), visibility 0s linear 0s;
  }
  .nav__links a {
    font-size: 18px;
    color: var(--c-ink);
    /* Pad each drawer link so the tap area is ~44 px tall. Keeps the
       text visually the same while giving thumbs room to miss slightly. */
    padding: 10px 0;
    display: block;
  }

  /* Original mobile hero composition restored: a full-height hero that
     lets Laith land tightly next to the name. The stage copy above
     him is partially cropped by design — the photo reads as a
     portrait crop rather than a landscape documentary shot. */
  .hero { min-height: 70vh; align-items: flex-end; }
  /* Object-position X shifted (22% -> 12%) so Laith's figure sits
     well right of the name column. A small scale + translateY lifts
     the photo upward in the frame so more of his upper body shows
     above the meta line instead of being overlaid by it. */
  .hero__image img {
    object-position: 12% 50%;
    filter: contrast(1.02) brightness(0.95);
    transform: scale(1.12) translateY(-7%);
  }
  /* Two-layer gradient that matches the desktop treatment on mobile:
     a left-to-right wash darkens the column where the name, tagline
     and meta live so the copy stays legible over whatever part of
     the photo is behind, while a vertical fade at the top and bottom
     blends the hero into the near-black nav above and the About
     section below. Laith's figure sits in the clear zone on the right. */
  .hero::before {
    background:
      linear-gradient(
        to right,
        rgba(12, 12, 12, 0.88) 0%,
        rgba(12, 12, 12, 0.7)  35%,
        rgba(12, 12, 12, 0.3)  62%,
        rgba(12, 12, 12, 0)    92%),
      linear-gradient(
        to bottom,
        rgba(12, 12, 12, 0.85) 0%,
        rgba(12, 12, 12, 0.4)  10%,
        rgba(12, 12, 12, 0.08) 24%,
        rgba(12, 12, 12, 0)    42%,
        rgba(12, 12, 12, 0.35) 62%,
        rgba(12, 12, 12, 0.82) 82%,
        rgba(12, 12, 12, 1)    100%);
  }


  .pub { grid-template-columns: 60px 1fr; gap: var(--s-3); }

  .talk { grid-template-columns: 60px 1fr; gap: var(--s-3); }
}

@media (max-width: 479px) {
  .hero__title { font-size: clamp(44px, 14vw, 64px); }
  .hero__tagline { font-size: 18px; }
  .section__title--display { font-size: clamp(32px, 9vw, 44px); }
}

/* =====================================================================
   RESPONSIVE REFINEMENTS
   Placed last so they override the breakpoint blocks above.
   ===================================================================== */

/* Phones. The open menu covers the whole width below 480 px. From 480
   px the 360 px drawer stays and the page behind it is dimmed. On short
   screens the links sit closer together so all eight fit. */
@media (max-width: 767px) {
  .nav__links.is-open { box-shadow: 0 0 0 100vmax rgba(12, 12, 12, 0.7); }
  .edu-step__thesis { max-width: 46ch; }
}
@media (max-width: 479px) {
  .nav__links { width: 100%; border-left: 0; }
  .nav__links.is-open { box-shadow: none; }
}
@media (max-width: 767px) and (max-height: 780px) {
  .nav__links { gap: var(--s-3); padding-block: 72px 24px; }
}

/* Very narrow phones. The publication year moves above the title so
   the title, authors and links get the full width. */
@media (max-width: 400px) {
  .pub { grid-template-columns: 1fr; gap: var(--s-2); }
  .pub__year { padding-top: 0; }
}
@media (max-width: 359px) {
  .footer__socials { column-gap: 10px; }
}

/* Tablets, 768 to 991 px. About and Contact keep their two columns
   instead of a single narrow column with an empty band beside it. */
@media (min-width: 768px) and (max-width: 991px) {
  .about { grid-template-columns: 1.1fr 0.9fr; }
  .about__image { max-width: none; aspect-ratio: 3 / 4; }
  .contact { grid-template-columns: 0.8fr 1fr; }
  .contact__image { max-width: none; aspect-ratio: 4 / 5; }
}

/* Upright tablets. The hero photo uses the phone framing, so the
   speaker stands to the right of the name instead of behind it. */
@media (min-width: 768px) and (max-width: 1023px) and (orientation: portrait) {
  .hero__image img {
    object-position: 12% 50%;
    transform: scale(1.12) translateY(-7%);
  }
}

/* Short landscape screens, such as a phone held sideways. The About and
   Contact photos are limited by the screen height so each fits on one
   screen. The widths follow from their 3/4 and 4/5 aspect ratios. */
@media (orientation: landscape) and (max-height: 500px) {
  .about__image { max-width: min(100%, 63vh); justify-self: center; }
  .contact__image { max-width: min(100%, 68vh); justify-self: center; }
}

/* Touch screens wide enough for the inline nav, such as a phone held
   sideways. Each link gets a 44 px tall tap area without moving the
   layout, and the underline keeps its place under the text. */
@media (pointer: coarse) and (min-width: 768px) {
  .nav__links a { padding: 11px 0; margin: -11px 0; }
  .nav__links a::after { bottom: 5px; }
}
