/* ============================================
   1. RESET & BASE
   ============================================ */
* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

html {
  /* This one line is what makes clicking a nav link glide to the
     section instead of jumping instantly. */
  scroll-behavior: smooth;
  /* Without this, phones in system dark mode render the page's
     unstyled canvas (the overscroll/bounce area beyond the body, and
     anything before the stylesheet paints) black instead of this
     site's light design — color-scheme tells the browser this page is
     light-only, so it stops following the OS setting there. The
     <meta name="color-scheme"> tag in each page's <head> does the same
     thing before the CSS has even loaded. */
  color-scheme: light;
  background-color: #faf9f6;
}

body {
  font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  color: #1a1a1a;
  background-color: #faf9f6;
  /* Delicate dot-grain texture: a single 1px dot per tile, repeated
     every 22px, at low opacity — reads as a subtle paper texture
     rather than a visible pattern. Pure CSS (no image file), so it
     stays crisp at any zoom/density and tiles seamlessly down the
     page. */
  background-image: radial-gradient(circle at 1px 1px, rgba(26, 26, 26, 0.06) 1px, transparent 0);
  background-size: 22px 22px;
  line-height: 1.6;
}

/* ============================================
   2. COLOR & TYPE TOKENS
   Change these once, and the whole site updates.
   ============================================ */
:root {
  --ink: #1a1a1a;
  --ink-hover: #4e4e4e;
  --paper: #faf9f6;
  --index: #eeece6;
  --accent: #2455e6;      /* deep pine green — swap for your brand color */
  --muted: #6b6b66;
  --button: rgba(250, 249, 246, 0.3);
  --button-hover: rgba(250, 249, 246, 0.5);
  --link: #2455e6;
  --link-hover: #2232c1;
  --display-font: 'Helvetica Neue', Helvetica, Arial, sans-serif;
  --carousel-bg-plots:#363636;
  --carousel-bg-white:#FFFFFF;
  --carousel-bg-grey:#E6E6E6;
  --image-radius: 4px;    /* project cards, hero/figure/carousel images, lightbox */
}

/* ============================================
   3. HEADER + INDEX NAV
   ============================================ */

/* Hides text visually but keeps it for screen readers + SEO.
   Used on the H1/H2 that the logo image now visually replaces. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background-color: rgba(250, 249, 246, 0.75);
  backdrop-filter: blur(12px) saturate(180%);
  -webkit-backdrop-filter: blur(12px) saturate(180%);
  padding: 1.25rem 1.5rem;
  box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.06);
  
  /* .scrolled is toggled by script.js once you're past the very top —
     shrinks the header (and the logo, below) so it takes up less room
     while you're reading, then relaxes back to full size at the top. */
  transition: padding 0.25s ease;
}

.site-header.scrolled {
  padding: 0.4rem 1.5rem;
}

@media (prefers-reduced-motion: reduce) {
  .site-header {
    transition: none;
  }
}

/* Frosted glass needs a solid fallback for reduced-transparency and for
   browsers without backdrop-filter support, so content underneath never
   reads illegibly through the header. */
@supports not (backdrop-filter: blur(1px)) {
  .site-header {
    background-color: var(--paper);
  }
}

@media (prefers-reduced-transparency: reduce) {
  .site-header {
    background-color: var(--paper);
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    border-bottom: 1px solid #e4e2db;
  }
}

.header-row {
  position: relative;
  display: flex;
  align-items: center;
  min-height: 40px;
}

.index-nav {
  display: flex;
  align-items: center;
  margin-left: auto;
}

.nav-links {
  list-style: none;
  display: flex;
  align-items: center;
  gap: 3rem;
  flex-wrap: wrap;
  margin-right: 3rem;
}

.index-nav a {
  position: relative;
  display: inline-block;
  color: var(--ink);
  text-decoration: none;
  font-weight: 400;
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding-bottom: 2px;
}

/* Logo stays perfectly centered regardless of how wide the nav is.
   Stays position: absolute at every width (rather than switching to
   static on mobile) specifically so left/transform can transition
   smoothly as the viewport crosses the 900px breakpoint — animating
   between two different position values isn't possible, but animating
   left/transform within the same positioning scheme is. */
.logo-link {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  transition: left 0.3s ease, transform 0.3s ease;
}

@media (prefers-reduced-motion: reduce) {
  .logo-link {
    transition: none;
  }
}

.site-logo {
  width: clamp(130px, 40vw, 190px);
  height: auto;
  display: block;
  transition: width 0.25s ease;
}

.site-header.scrolled .site-logo {
  width: clamp(95px, 30vw, 135px);
}

@media (prefers-reduced-motion: reduce) {
  .site-logo {
    transition: none;
  }
}

/* Hamburger button: hidden on desktop, shown only on mobile (see media query) */
.menu-toggle {
  display: none;
  background: none;
  border: none;
  padding: 0.25rem;
  cursor: pointer;
  color: var(--ink);
  position: relative;
  z-index: 11;
  transition: transform 0.1s ease-out;
}

.menu-toggle:active {
  transform: scale(0.88);
}

.menu-toggle .material-symbols-outlined {
  font-size: 28px;
  display: block;
}

.menu-toggle .menu-icon-close {
  display: none;
}

.menu-toggle[aria-expanded="true"] .menu-icon-open {
  display: none;
}

.menu-toggle[aria-expanded="true"] .menu-icon-close {
  display: block;
}

/* Mobile: no hamburger/dropdown — Projects and About stay visible
   inline, same as desktop. Logo moves to the left (out of its
   desktop centered position) and the nav links sit on the right. */
@media (max-width: 1300px) {
  .menu-toggle {
    display: none;
  }

  .logo-link {
    left: 1rem;
    transform: translateY(-50%);
  }

  .nav-links {
    gap: 2rem;
    margin-right: 1rem;
  }
}

@media (max-width: 520px) {
  .logo-link {
    left: 0rem;
  }

  .nav-links {
    margin-right: 0rem;
  }
}

/* ============================================
   4. SECTIONS
   scroll-margin-top keeps a section from being hidden
   under a fixed header when jumped to — increase this
   if you add a sticky nav bar later.
   ============================================ */
.section {
  max-width: 1200px; /* 960px */
  margin: 0 auto;
  /*padding: 5rem 1.5rem;*/
  scroll-margin-top: 2rem;
}

@media (max-width: 1300px) {
  .section {
    padding: 0rem 1.5rem;
  }
}

.section h2 {
  font-family: var(--display-font);
  font-size: 2rem;
  line-height: 1.15;
  margin-bottom: 2rem;
}

/* Homepage tagline: types out "I love creating <word>!" on load, then
   loops — backspacing only the word (never the fixed "I love creating"
   part) and typing the next one in from script.js's TYPEWRITER_WORDS.
   aria-hidden since a screen reader reading out every keystroke would
   be unusable; the .visually-hidden sibling right after it in the
   markup gives screen readers the same sentence as a single static
   read instead. */
.typewriter-cursor {
  animation: typewriter-blink 1s step-end infinite;
}

@keyframes typewriter-blink {
  0%, 50% { opacity: 1; }
  50.01%, 100% { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .typewriter-cursor {
    animation: none;
  }
}

/* ============================================
   5. PROJECT GRID
   ============================================ */
.project-grid {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 2rem;
}

.section--projects {
  /*padding-top: 3rem;*/
}

.project-card a {
  display: block;
  text-decoration: none;
  color: var(--ink);
}

.project-image {
  position: relative;
  overflow: hidden;
  border-radius: var(--image-radius);
  aspect-ratio: 1 / 1;
  background-color: #ece9e2; /* shows while the image loads */
}

.project-image img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 0.4s ease;
}

/* The caption sits on top of the image, invisible until hovered */
.project-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: 1.25rem;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.85) 0%, rgba(0, 0, 0, 0.5) 60%, rgba(0, 0, 0, 0) 95%);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.project-overlay h3 {
  font-family: var(--display-font);
  font-size: 1.15rem;
  color: #fff;
}

.project-overlay .project-meta {
  font-size: 0.85rem;
  color: #f0efe9;
  margin-top: 0.25rem;
}
.divider {
  padding-bottom: 2rem;
  border-bottom: 1px solid rgb(228, 226, 219);
}

.project-card a:hover .project-image img,
.project-card a:focus-visible .project-image img {
  transform: scale(1.06);
}

.project-card a:hover .project-overlay,
.project-card a:focus-visible .project-overlay {
  opacity: 1;
}

@media (prefers-reduced-motion: reduce) {
  .project-image img,
  .project-overlay {
    transition: none;
  }
}

/* ============================================
   6. FOOTER
   ============================================ */
.site-footer {
  text-align: center;
  padding: 2rem;
  color: var(--muted);
  font-size: 0.85rem;
}

/* ============================================
   7a. LIGHTBOX (click-to-zoom on project images)
   ============================================ */
.project-hero img,
.project-figure img {
  cursor: zoom-in;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: rgba(0, 0, 0, 0.92);
  padding: 4.5rem 1.5rem;
  opacity: 0;
  visibility: hidden;
  /* Nothing in here is meant to be text-selected — a stray click-drag
     (easy to do reaching for the zoom/nav buttons) was highlighting
     the image in the browser's selection blue instead of doing
     anything useful. */
  user-select: none;
  -webkit-user-select: none;
  /* Hands every touch gesture inside the lightbox (pinch, drag, swipe)
     to our own touchstart/move/end handlers in script.js instead of
     the browser's native page-zoom/scroll — without this, a pinch here
     zoomed the whole page (dragging the fixed backdrop/buttons along
     with it) and a drag could scroll the page behind the overlay. */
  touch-action: none;
  /* visibility has its own 0s transition, delayed until the opacity
     fade finishes closing — without this, visibility (which can't be
     animated) snaps to hidden the instant .open is removed, cutting
     off the fade-out and the image's shrink-to-thumbnail animation
     before either gets a chance to actually play. */
  transition: opacity 0.35s ease, visibility 0s linear 0.35s;
}

.lightbox.open {
  opacity: 1;
  visibility: visible;
  transition: opacity 0.35s ease, visibility 0s linear 0s;
}

.lightbox-img-wrap {
  width: 100%;
  height: 100%;
  display: flex;
  /* "safe center" keeps the image centered while it fits, but falls
     back to start-aligned once it's bigger than the wrap — so zooming
     in doesn't clip the overflow on the far side out of scroll reach.
     Declared twice: browsers that don't understand "safe" (an older
     Safari) ignore that whole line and keep the plain "center" above
     it instead of dropping alignment entirely. */
  align-items: center;
  align-items: safe center;
  justify-content: center;
  justify-content: safe center;
  overflow: auto;
}

.lightbox-img-wrap img {
  max-width: 100%;
  max-height: 100%;
  width: auto;
  height: auto;
  border-radius: var(--image-radius);
  cursor: zoom-in;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
  -webkit-user-drag: none;
  /* transform/opacity: grow-from-thumbnail on open, shrink-back on
     close, and the prev/next slide — script.js drives all three by
     setting a starting transform/opacity and clearing it. Zoom level
     changes (width/height) are intentionally NOT in this list — they
     apply instantly rather than animating. */
  transition: transform 0.35s cubic-bezier(0.2, 0.8, 0.2, 1), opacity 0.2s ease;
}

/* Some source images (icon sheets, diagram GIFs) have a genuinely
   transparent background rather than a solid one — on the dark
   lightbox backdrop that reads as a hole. script.js toggles this class
   on/off per image, driven by data-lightbox-bg="white" on the
   original <img> in the page. */
.lightbox-img-wrap img.lightbox-img-on-white {
  background-color: #ffffff;
}

@media (prefers-reduced-motion: reduce) {
  .lightbox-img-wrap img {
    transition: none;
  }
}

/* Zoomed-in state: the image is sized above its fit-to-screen size
   (script.js sets explicit width/height). display/overflow don't need
   to change here anymore — the wrap is always flex + overflow: auto
   (see above) — this just swaps the cursor and lets the explicit size
   exceed the fit-to-screen max-width/max-height. Switching display
   between flex and block here used to be the bug behind the "jumps to
   a corner" zoom animation: display isn't animatable, so centering
   vanished the instant you zoomed, before the width/height had
   actually finished growing. */
.lightbox-img-wrap.zoomed img {
  max-width: none;
  max-height: none;
  cursor: zoom-out;
}

.lightbox-caption {
  position: absolute;
  bottom: 1.25rem;
  left: 0;
  right: 0;
  text-align: center;
  color: #f0efe9;
  font-size: 0.85rem;
  padding: 0 4.5rem;
  /* Its box spans the full width even though the text is centered and
     padded — without this, it sits on top of (and swallows clicks on)
     anything sharing this row, like the zoom controls on desktop. */
  pointer-events: none;
}

/* Shared round icon-button look, used by the lightbox controls (close,
   prev/next, zoom in/out) and the carousel's prev/next — a translucent
   frosted-glass circle rather than a flat/bare icon, so every "button
   floating over an image" on the site reads as the same kind of
   control. Dark ink icon on a light frosted pill reads clearly against
   both the lightbox's near-black overlay and the carousel's dark
   viewport background. */
.lightbox-btn,
.carousel-prev,
.carousel-next {
  width: 44px;
  height: 44px;
  position: relative;
  border: none;
  background-color: var(--button);
  backdrop-filter: blur(8px) saturate(180%);
  -webkit-backdrop-filter: blur(8px) saturate(180%);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
  transition: transform 0.1s ease-out, background-color 0.2s ease;
  /* The close button gets focused programmatically on open (for
     keyboard users tabbing through afterward), which was showing the
     browser's default blue focus ring even though nobody "selected"
     it — the background-color change on :focus-visible below is
     already a clear enough focus indicator on its own. */
  outline: none;
}

/* Square corners to match the logo's mark (see .back-to-top,
   .carousel-dot and .lightbox-zoom for the same treatment). */
.lightbox-btn,
.carousel-prev,
.carousel-next {
  border-radius: var(--image-radius);
}

.lightbox-btn:hover,
.carousel-prev:hover,
.carousel-next:hover {
  background-color: var(--button-hover);
  color: var(--ink-hover);
}

.lightbox-btn:active,
.carousel-prev:active,
.carousel-next:active {
  transform: scale(0.9);
}

.lightbox-btn:disabled {
  opacity: 0.4;
  cursor: default;
  pointer-events: none;
}

.lightbox-btn svg,
.carousel-prev svg,
.carousel-next svg {
  width: 20px;
  height: 20px;
  display: block;
}

@supports not (backdrop-filter: blur(1px)) {
  .lightbox-btn,
  .carousel-prev,
  .carousel-next {
    background-color: rgba(250, 249, 246, 0.92);
  }
}

/* The close and prev buttons sit before .lightbox-img-wrap in the DOM
   (see script.js), so without a z-index a wide or tall image — which
   paints after them — would cover the button underneath it. */
.lightbox-close {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  z-index: 1;
}

.lightbox-prev {
  position: absolute;
  left: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
}

.lightbox-prev:active {
  transform: translateY(-50%) scale(0.9);
}

.lightbox-next {
  position: absolute;
  right: 0.75rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 1;
}

.lightbox-next:active {
  transform: translateY(-50%) scale(0.9);
}

@media (max-width: 640px) {
  /* Hidden in favor of swipe-to-navigate (see touchstart/move/end in
     script.js) — the buttons aren't needed once swipe covers the same
     job, and they'd otherwise sit awkwardly close to the thumb. */
  .lightbox-prev,
  .lightbox-next {
    display: none;
  }
}

/* Zoom controls: bottom-center on narrow/mobile screens (centered is
   easiest to reach with a thumb); bottom-right on desktop, out of the
   way of the centered caption. Both stay above the caption's row. */
.lightbox-zoom {
  position: absolute;
  bottom: 4.5rem;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  align-items: center;
  gap: 0.75rem;
  background-color: rgba(0, 0, 0, 0.35);
  backdrop-filter: blur(8px) saturate(180%);
  -webkit-backdrop-filter: blur(8px) saturate(180%);
  padding: 0.4rem 0.75rem;
  border-radius: var(--image-radius);
}

@media (min-width: 900px) {
  .lightbox-zoom {
    /* Same bottom offset as .lightbox-caption, so the zoom pill (right
       corner) and the caption (centered) sit on the same row instead
       of the pill floating above it. */
    bottom: 1.25rem;
    left: auto;
    right: 1.5rem;
    transform: none;
  }
}

@media (max-width: 640px) {
  /* No zoom controls on mobile at all — the click-to-zoom toggle is
     also disabled in script.js to match. */
  .lightbox-zoom {
    display: none;
  }
}

.lightbox-zoom-level {
  color: #fff;
  font-size: 0.8rem;
  min-width: 3.2em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.lightbox-zoom .lightbox-btn {
  width: 32px;
  height: 32px;
}

.lightbox-zoom .lightbox-btn svg {
  width: 16px;
  height: 16px;
}

/* ============================================
   7b. BACK-TO-TOP (project pages only, added by script.js)
   ============================================ */
.back-to-top {
  position: fixed;
  right: 1.5rem;
  bottom: 1.5rem;
  z-index: 20;
  width: 44px;
  height: 44px;
  border-radius: var(--image-radius);
  border: none;
  /* Same frosted-glass treatment as the carousel arrows/lightbox
     controls, rather than the solid ink fill this used to have. */
  background-color: var(--button);
  backdrop-filter: blur(8px) saturate(180%);
  -webkit-backdrop-filter: blur(8px) saturate(180%);
  color: var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
  opacity: 0;
  transform: translateY(8px);
  pointer-events: none;
  transition: opacity 0.25s ease, transform 0.25s ease, background-color 0.2s ease, color 0.2s ease;
}

@supports not (backdrop-filter: blur(1px)) {
  .back-to-top {
    background-color: rgba(250, 249, 246, 0.92);
  }
}

.back-to-top.visible {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}

.back-to-top:hover,
.back-to-top:focus-visible {
  /* Blue/purple hover, distinct from the plain glass-brightening
     hover the other frosted buttons use — the icon flips to a light
     color to stay visible against it. Solid, not translucent — tried
     as glass and it didn't read well here. */
  background-color: var(--link-hover);
  color: var(--paper);
}

.back-to-top.visible:active {
  transform: translateY(0) scale(0.9);
}

.back-to-top svg {
  width: 20px;
  height: 20px;
}

@media (prefers-reduced-motion: reduce) {
  .back-to-top {
    transition: none;
  }
}

/* ============================================
   7. ACCESSIBILITY
   ============================================ */
@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
}

/* ============================================
   8. PROJECT PAGE (used by files inside /projects/)
   ============================================ */
.project-header {
  max-width: 1200px; /* 720px */
  margin: 0 auto;
  padding: 3rem 1.5rem 2rem;
}

.back-link {
  position: relative;
  display: inline-block;
  color: var(--muted) !important;
  text-decoration: none;
  font-size: 0.9rem;
  margin-bottom: 1.5rem;
}

.back-link:hover,
.back-link:focus-visible {
  color: var(--link-hover) !important;
}

.project-header h1 {
  font-family: var(--display-font);
  font-size: clamp(2.5rem, 5vw, 3rem);
  line-height: 1.15;
}



/* Same fade/slide-from-the-left reveal as the H2s below, just a
   touch slower (1.6s vs 0.7s) since the H1 is the page's one big
   opening move rather than a per-section beat. Toggled by the same
   IntersectionObserver in script.js, so it replays every time the
   title scrolls out of view and back in, not just on first load. */
.h1-reveal {
  opacity: 0;
  transform: translateX(-24px);
  transition: opacity 1.6s cubic-bezier(0.2, 0.8, 0.2, 1), transform 1.6s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.h1-reveal.in-view {
  opacity: 1;
  transform: translateX(0);
}

/* Section H2s get the same left-to-right reveal as the H1 above, just
   faster (0.7s) since there are several of these per page instead of
   one. script.js adds .h2-reveal itself (rather than it living in the
   base h2 rule) so a heading only ever starts hidden once JS has
   actually confirmed it can reveal it again — if the script fails to
   load, h2s simply render normally with no animation, instead of
   staying invisible. */
.h2-reveal {
  opacity: 0;
  transform: translateX(-24px);
  transition: opacity 1s cubic-bezier(0.2, 0.8, 0.2, 1), transform 1s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.h2-reveal.in-view {
  opacity: 1;
  transform: translateX(0);
}

/* Same left-to-right reveal as the H1/H2 headings above, applied to
   each timeline logo on the About page instead — same 0.7s speed as
   the H2s, toggled by the same repeat-on-scroll IntersectionObserver
   in script.js. */
.timeline-logo-reveal {
  opacity: 0;
  transform: translateX(-24px);
  transition: opacity 0.7s cubic-bezier(0.2, 0.8, 0.2, 1), transform 0.7s cubic-bezier(0.2, 0.8, 0.2, 1);
}

.timeline-logo-reveal.in-view {
  opacity: 1;
  transform: translateX(0);
}

/* Same reveal as the H1 next to it, since they sit in the same
   opening row on the About page — same 1.6s speed, same repeat-on-
   scroll IntersectionObserver in script.js. */
.about-portrait-reveal {
  opacity: 0;
  /*transform: translateX(-24px);*/
  transition: opacity 2s cubic-bezier(0.2, 0.8, 0.2, 1), transform 2s cubic-bezier(0.2, 0.8, 0.2, 1);
  /*transition-delay: 0.2s;*/
}

.about-portrait-reveal.in-view {
  opacity: 1;
  /*transform: translateX(0);*/
}

.project-header .project-meta {
  margin-top: 0.75rem;
  font-size: 1rem;
  color: var(--muted);
  font-size: clamp(1rem, 2vw, 1.1rem);
}

@media (max-width: 700px) {
  .project-meta-grid {
    grid-template-columns: 1fr;
    gap: 2rem;
  }
}

/* Quick-facts list (role, team, timeline, tool) under the H1 */

/* Flexbox, not grid: a CSS Grid column track (even capped with
   fit-content(N)) is sized up to its max limit whenever the grid has
   room for it, regardless of how little the wrapped badge-grid inside
   actually uses — so a short Timeline block and a long Role block
   both got stretched to their track's ceiling, leaving wildly
   different leftover whitespace before the next column despite an
   identical declared column-gap. A flex item's auto width genuinely
   hugs its content instead (shrink-to-fit), so switching to flex
   makes the visual gap between Role/Tool/Timeline match the declared
   gap exactly, every time — no per-project column-width tuning
   needed, which is why the old per-page modifier classes below are
   gone. Each block still wraps its own badges independently (rather
   than the whole block dropping to a new line) via the max-width on
   the child rule just below. */
.project-specs {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 2rem 40px; /* row-gap column-gap — 100px between Role/Tool/Timeline */
}

.project-specs > div {
  /*flex: 0 1 auto;*/
  max-width: 260px;
  /*ackground-color:#2455e6;*/
}

/* Float-based alternative to .project-specs' flexbox, same goal:
   each block hugs its own content and wraps internally, with a fixed
   100px gap to the next block. A floated element's width defaults to
   shrink-to-fit — the same intrinsic sizing a flex item's auto width
   gets — so .badge-block never needs a track/basis to be told to hug
   its content. Its .badge-specs children are inline-flex, which
   behaves like inline-block for line-wrapping: they flow and wrap
   within .badge-block's max-width on their own, no flex-wrap needed. */
.project-specs-new {
  display: flow-root; /* contains the floated .badge-block children so this element doesn't collapse to zero height */
  width: 100%;
  /*background-color: #00ff00;*/
   
}

.badge-block {
  position: relative;
  float: left;
  margin-right: 3rem;
  /*background-color: #ff0000;*/
  margin-bottom: 1rem;
  /* A float never shrinks below its own declared width — if the
     specs column gets narrower than a block's fixed px width (the
     header starts losing room to the "On this page" box well before
     the 900px full-stack fallback below), the float just overflows
     past the column's edge instead of wrapping, which is what was
     reading as content "cut off". max-width caps it at whatever
     room is actually left, letting the float shrink like a normal
     block would; badges then wrap inside a narrower row instead of
     spilling out. Omnitrack's own #id widths already have dedicated
     breakpoints for this, so this is mostly a safety net there. */
  max-width: 100%;
}

/*AQUI*/
/*@media (max-width: 900px) {
  .badge-block {
    width: 100% !important;
  }
}*/

/* Omnitrack Website */
#omnitrack-website-role{ width: 146px; }
#omnitrack-website-tools{ width: 358px; }
#omnitrack-website-timeline{ width: 192px; }

@media (max-width: 1128px) {
  #omnitrack-website-tools{ width: 360px; }
  #omnitrack-website-timeline{ width: 370px; }
}

@media (max-width: 892px) {
  #omnitrack-website-role{ width: 100%; }
  #omnitrack-website-timeline{ width: 100%; }
}

@media (max-width: 840px) {
  #omnitrack-website-role{ width: 146px; }
  #omnitrack-website-tools{ width: 358px; }
  #omnitrack-website-timeline{ width: 100%; }
}

@media (max-width: 648px) {
  #omnitrack-website-role{ width: 100%; }
  #omnitrack-website-tools{ width: 358px; }
}

/* Tools is still fixed-width at this point — same overflow as
   Enkontra/Haus above, so cap it to 100% for phones too. */
@media (max-width: 640px) {
  #omnitrack-website-tools{ width: 100%; }
}



/* Civi */
#civi-role { width: 155px; }
#civi-tools { width: 320px; }
#civi-timeline { width: 195px; }

@media (max-width: 1106px) {
  #civi-timeline { width: 100%; }
}

@media (max-width: 900px) {
  #civi-tools { max-width: 450px; }
}

@media (max-width: 860px) {
  #civi-role { width: 100%; }
}

/* Design Sprint 2.0 — Civi */
#design-sprint-civi-role { width: 290px; }
#design-sprint-civi-tools { width: 195px; }
#design-sprint-civi-timeline { width: 150px; }


/* Differential Motor */
#differential-motor-role { width: 240px; }
#differential-motor-tools { width: 262px; }
#differential-motor-timeline { width: 228px; }

@media (max-width: 594px) {
  #differential-motor-role { width: 100%; }
}

@media (max-width: 1162px) {
  #differential-motor-tools { width: 140px; }
}

@media (max-width: 1040px) {
  #differential-motor-timeline { width: 132px; }
}

@media (max-width: 944px) {
  #differential-motor-role { width: 148px; }
}

@media (max-width: 852px) {
  #differential-motor-tools { width: 262px; }
  #differential-motor-timeline { width: 228px; }
}

@media (max-width: 839px) {
  #differential-motor-role { width: 100%; }
}

@media (max-width: 704px) {
  #differential-motor-tools { width: 262px; }
  #differential-motor-timeline { width: 228px; }
}

@media (max-width: 634px) {
  #differential-motor-tools { width: 140px; }
  #differential-motor-timeline { width: 132px; }
}


/* Enkontra */
#enkontra-role { width: 135px; }
#enkontra-tools { width: 364px; }
#enkontra-timeline { width: 255px; }

@media (max-width: 1086px) {
  #enkontra-tools { width: 242px; }
}

@media (max-width: 1064px) {
  #enkontra-timeline { width: 160px; }
}

@media (max-width: 969px) {
  #enkontra-tools { width: 364px; }
  #enkontra-timeline { width: 255px; }
}

/* Below that, on phones, none of the widths above leave enough room —
   .badge-block's own max-width: 100% safety net doesn't reliably
   catch this because the grid column it sits in sizes itself to fit
   the badge's declared width in the first place, so the fixed px
   width just wins and overflows past the header's padding on both
   sides (the column is centered, so it bleeds evenly left and right). */
@media (max-width: 640px) {
  #enkontra-tools { width: 100%; }
  #enkontra-timeline { width: 100%; }
}

/* Haus */
#haus-role { width: 134px; }
#haus-tools { width: 364px; }
#haus-timeline { width: 256px; }

@media (max-width: 1186px) {
  #haus-tools { width: 242px; }
}

@media (max-width: 1067px) {
  #haus-timeline { width: 160px; }
}

@media (max-width: 1067px) {
  #haus-role { width: 100%; }
  #haus-tools { width: 100%; }
  #haus-timeline { width: 100%; }
}

@media (max-width: 840px) {
  #haus-role { width: 134px; }
  #haus-tools { width: 364px; }
}

/* ...but that re-fixes them wider than a phone screen — same overflow
   as Enkontra above, so cap them back to 100% below that. */
@media (max-width: 640px) {
  #haus-role { width: 100%; }
  #haus-tools { width: 100%; }
}

/* Motion Graphics */
#motion-graphics-role { width: 290px; }
#motion-graphics-tools { width: 265px; }
#motion-graphics-timeline { width: 115px; }

@media (max-width: 1102px) {
  #motion-graphics-role { width: 100%; }
  #motion-graphics-tools { width: 100%; }
  #motion-graphics-timeline { width: 100%; }
}

/* Motion Graphics — Loadsmart, PortPass, and Balaroti share the same
   Role and (mostly) the same Tools; only Timeline changes per piece. */
#motion-graphics-loadsmart-role,
#motion-graphics-portpass-role,
#motion-graphics-balaroti-role,
#motion-graphics-age-role,
#motion-graphics-regenerate-pro-role,
#motion-graphics-prodiet-role { width: 390px; }

#motion-graphics-loadsmart-tools,
#motion-graphics-portpass-tools,
#motion-graphics-balaroti-tools { width: 364px; }

#motion-graphics-loadsmart-timeline { width: 165px; }
#motion-graphics-portpass-timeline { width: 260px; }
#motion-graphics-balaroti-timeline { width: 140px; }



/* Age Nutrilatina, Regenerate Pro, and Prodiet only used two Tools
   (no Lottie), so they get their own narrower width. */

#motion-graphics-age-tools,
#motion-graphics-regenerate-pro-tools,
#motion-graphics-prodiet-tools { width: 265px; }

#motion-graphics-age-timeline { width: 135px; }
#motion-graphics-regenerate-pro-timeline { width: 90px; }
#motion-graphics-prodiet-timeline { width: 135px; }

@media (max-width: 1100px) {
  #motion-graphics-loadsmart-role,
  #motion-graphics-portpass-role,
  #motion-graphics-balaroti-role,
  #motion-graphics-age-role,
  #motion-graphics-regenerate-pro-role,
  #motion-graphics-prodiet-role { width: 290px; }

  #motion-graphics-loadsmart-tools,
  #motion-graphics-portpass-tools,
  #motion-graphics-balaroti-tools { width: 264px; }

  #motion-graphics-portpass-timeline { width: 164px; }
}

@media (max-width: 911px) {
  #motion-graphics-loadsmart-role,
  #motion-graphics-portpass-role,
  #motion-graphics-balaroti-role,
  #motion-graphics-age-role,
  #motion-graphics-regenerate-pro-role,
  #motion-graphics-prodiet-role { width: 150px; }

  #motion-graphics-loadsmart-tools,
  #motion-graphics-portpass-tools,
  #motion-graphics-balaroti-tools { width: 140px; }

  #motion-graphics-portpass-timeline { width: 164px; }

  #motion-graphics-age-tools,
  #motion-graphics-regenerate-pro-tools,
  #motion-graphics-prodiet-tools { width: 142px; }
}


@media (max-width: 647px) {
  #motion-graphics-loadsmart-role,
  #motion-graphics-portpass-role,
  #motion-graphics-balaroti-role,
  #motion-graphics-age-role,
  #motion-graphics-regenerate-pro-role,
  #motion-graphics-prodiet-role,
  #motion-graphics-loadsmart-tools,
  #motion-graphics-portpass-tools,
  #motion-graphics-balaroti-tools,
  #motion-graphics-age-tools,
  #motion-graphics-regenerate-pro-tools,
  #motion-graphics-prodiet-tools,
  #motion-graphics-loadsmart-timeline,
  #motion-graphics-portpass-timeline,
  #motion-graphics-balaroti-timeline,
  #motion-graphics-age-timeline,
  #motion-graphics-regenerate-pro-timeline,
  #motion-graphics-prodiet-timeline { width: 100%; }
}

/* Omnitrack Catalogue */
#omnitrack-catalogue-role { width: 150px; }
#omnitrack-catalogue-tools { width: 260px; }
#omnitrack-catalogue-timeline { width: 250px; }

@media (max-width: 1092px) {
  #omnitrack-catalogue-tools { width: 504px; }
  #omnitrack-catalogue-timeline { width: 100%; }
}

@media (max-width: 1038px) {
  #omnitrack-catalogue-tools { width: 260px; }
}

@media (max-width: 914px) {
  #omnitrack-catalogue-timeline { width: 380px; }
}

@media (max-width: 840px) {
  #omnitrack-catalogue-timeline { width: 100%; }
}

@media (max-width: 554px) {
  #omnitrack-catalogue-tools { width: 260px; }
}

/* Plots */
#plots-role { width: 215px; }
#plots-tools { width: 100px; }
#plots-timeline { width: 340px; }

/* No breakpoints at all above, so timeline (340px) overflows a phone
   screen outright — same fix as Enkontra/Haus/Omnitrack Website. */
@media (max-width: 640px) {
  #plots-role { width: 100%; }
  #plots-tools { width: 100%; }
  #plots-timeline { width: 100%; }
}

/* SupplyCyber */
#supplycyber-role { width: 240px; }
#supplycyber-tools { width: 224px; }
#supplycyber-timeline { width: 180px; }

@media (max-width: 1097px) {
  #supplycyber-timeline { width: 100%; }
}


.badge-specs {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  margin-right: 0.5rem;
  margin-bottom: 0.5rem;
  padding: 0.4rem 0.8rem;
  background-color: var(--index);
  border-radius: 4px;
  font-size: 0.9rem;
  color: var(--ink);
  text-decoration: none;
  cursor: default;
  white-space: nowrap;
  transition: background-color 0.15s ease;
  box-shadow: 0 4px 8px -4px rgba(0, 0, 0, 0.1);
}

a .badge-specs {
  cursor: pointer;
}

a:hover .badge-specs,
a:focus-visible .badge-specs {
  background-color: #e4e2db;
}

.block-title{
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.35rem;
}

.block-text{
  max-width: 700px;
  font-size: 0.95rem;
  margin-bottom: 0.5rem;
}

.project-specs-4 {
  grid-template-columns: auto auto auto auto !important;
}

@media (max-width: 1300px) {
  .project-specs-4 {
    grid-template-columns: auto auto !important;
  }
  .project-specs > div {
    max-width: 45%;
    width: 100%;
  }
}

@media (max-width: 740px) {
  .project-specs-4 {
    grid-template-columns: auto !important;
  }
  .project-specs > div {
    max-width: 100%;
  }
}



.project-specs-new dt,
.project-specs dt {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.35rem;
}

.project-specs-new dd,
.project-specs dd {
  font-size: 0.95rem;
}


.project-header-content{
  padding-right: 280px;
  width: 100%;
}

@media (max-width: 840px) {
  .project-header-content{
    padding-right: 0;
  }
}

.subtitle{
  /*font-size: 0.75rem;*/
  /*font-weight: 700;*/
  /*letter-spacing: 0.06em;*/
}



/* Table of contents, auto-built by script.js from the page's H2/H3s */
.project-index {
  min-width: 210px;
  align-items: center; /* start | end | center | stretch */
  background-color: var(--index);
  border-radius: 4px;
  box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);

  padding: 1.5rem;
  
  /*border-left: 1px solid rgb(228, 226, 219);
  padding-left: 2rem;*/
}

.project-index-plots {
  min-width: 210px;
}

.project-index-civi {
  min-width: 280px;
}

.project-index-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.75rem;
}

.project-index ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.project-index a {
  position: relative;
  display: inline-block;
}



.project-header a {
  color: var(--link);
  text-decoration: none;
  font-size: 0.9rem;
}

.project-header a:hover,
.project-header a:focus-visible {
  color: var(--link-hover);
}

.project-hero {
  max-width: 1200px;
  margin: 0 auto 3rem;
  padding: 0 1.5rem;
}

.project-hero img {
  width: 100%;
  height: auto;
  border-radius: var(--image-radius);
  display: block;
}

@media (max-width: 900px) {
  /* Matches the <picture> source swap to plots-cover-mobile.webp in
     the HTML — that image is shot/cropped for this ratio. */
  .project-hero img {
    aspect-ratio: 2 / 1;
    object-fit: cover;
  }
}

@media (max-width: 700px) {
  /* Matches the <picture> source swap to plots-cover-mobile.webp in
     the HTML — that image is shot/cropped for this ratio. */
  .project-hero img {
    aspect-ratio: 4 / 5;
    object-fit: cover;
  }
}

.project-body {
  max-width: 1200px; /* 720px */
  margin: 0 auto;
  margin-top: 2rem;
  /*padding: 0 1.5rem 2rem;*/
  padding-bottom: 1rem;
}

@media (max-width: 1300px) {
  .project-body {
    padding: 0rem 1.5rem 1rem 1.5rem;
  }
}

.project-body h2 {
  font-family: var(--display-font);
  font-size: 1.5rem;
  line-height: 1.15;
  margin-top: 4rem; /* 3rem; */
  margin-bottom: 1rem;
  scroll-margin-top: 6rem; /* clears the sticky site header when linked to */
}

.project-body h2:first-child {
  margin-top: 0;
}

.project-body h3 {
  font-family: var(--display-font);
  font-size: 1.05rem;
  margin-top: 2rem;
  margin-bottom: 0.75rem;
  scroll-margin-top: 6rem;

  /*font-size: 0.9rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 0.7rem;
  margin-top: 2rem;*/
}

.project-body h4 {
  font-size: 0.8rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink);
  margin-bottom: 0.6rem;
  margin-top: 1.5rem;
}

.project-body p {
  margin-bottom: 0.5rem;
  font-size: 1.1rem;
  font-weight: 300;
  letter-spacing: 0.06em;
}

.introduction-text{
  font-size: clamp(0.95rem, 3vw, 1.1rem) !important;
  /*line-height: 2;*/
}
/*AQUI*/

.project-body ul {
  margin: 0 0 1rem 1.25rem;
}

.project-body li {
  margin-bottom: 0.5rem;
  font-size: 1.1rem;
  font-weight: 300;
  letter-spacing: 0.06em;
}

/* Case-study screenshots: image + caption, full width of the text column */
.project-figure {
  margin: 2rem 0;
}

.project-figure img {
  width: 100%;
  height: auto;
  border-radius: var(--image-radius);
  display: block;
  background-color: var(--paper); /* #ece9e2*/
}

.project-figure figcaption {
  margin-top: 0.8rem;
  font-size: 0.9rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
  color: var(--muted);
  text-align: center;
}

/* A single row of images sharing one fixed height, each keeping its own
   aspect ratio (so width varies naturally) instead of the equal-width
   columns .figure-grid uses. Wraps to multiple lines on narrow screens
   rather than shrinking every image down to fit. */
.figure-row {
  display: grid;
  grid-template-columns: auto auto auto auto auto auto;
  flex-wrap: wrap;
  align-items: center;
  gap: 2rem auto;
  background-color: #ffffff;
  padding: 2rem;
  margin-bottom: 2rem;
}



.figure-row .project-figure {
  margin: 0;
  /*flex-shrink: 0;*/
}

.figure-row .project-figure img {
  width: auto;
  height: 200px;
  /*max-width: 100%;*/
}

@media (max-width: 1120px) {
  .figure-row .project-figure img {
    height: 150px;
  }
  
}

@media (max-width: 860px) {
  .figure-row .project-figure img {
    height: 120px;
  }
  
}

@media (max-width: 700px) {
  .figure-row {
    grid-template-columns: auto auto;
  }

  .figure-row .project-figure img {
    height: 240px;
  }
}

@media (max-width: 620px) {
  .figure-row {
    grid-template-columns: auto auto;
    gap: 4rem auto;
  }

  .figure-row .project-figure img {
    height: 180px;
  }
}

/* Related screenshots shown side by side instead of one long stack */
.figure-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1.5rem;
  margin: 2rem 0;
}

.figure-grid .project-figure {
  margin: 0;
}

@media (max-width: 640px) {
  .figure-grid {
    grid-template-columns: 1fr;
  }
}

.figure-grid-4 {
  grid-template-columns: 1fr 1fr 1fr 1fr;
}

/*@media (max-width: 700px) {
  .figure-grid-4 {
    grid-template-columns: 1fr 1fr;
  }
}*/

/* Carousel: alternates between however many .carousel-slide elements
   are inside it. script.js builds the dots and wires up prev/next —
   this just handles the visuals. */
.project-carousel {
  position: relative;
  margin: 2rem 0;
}

.carousel-viewport {
  position: relative;
  overflow: hidden;
  border-radius: var(--image-radius);
  background-color: var(--carousel-bg);
  aspect-ratio: 3 / 2;
  /* Let JS own horizontal swipe; the browser still handles vertical
     page scroll, so the two gestures don't fight each other. */
  touch-action: pan-y;
  cursor: grab;
}

.carousel-bg-plots {
  background-color: var(--carousel-bg-plots);
}
/*
.carousel-bg-civi {
  background-color: var(--carousel-bg-civi);
}

.carousel-bg-omnitrack {
  background-color: var(--carousel-bg-omnitrack);
}*/

.carousel-bg-white {
  background-color: var(--carousel-bg-white);
}

.carousel-bg-grey {
  background-color: var(--carousel-bg-grey);
}

.carousel-viewport:active {
  cursor: grabbing;
}

.carousel-track {
  display: flex;
  height: 100%;
  transition: transform 0.4s ease;
}

@media (prefers-reduced-motion: reduce) {
  .carousel-track {
    transition: none;
  }
}

.carousel-slide {
  flex: 0 0 100%;
  min-width: 0;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
}

.carousel-slide img {
  width: 100%;
  height: 100%;
  object-fit: contain;
  cursor: zoom-in;
}

/* Visual style (size, background, hover/active) lives on the shared
   .lightbox-btn, .carousel-prev, .carousel-next rule near the lightbox
   styles — this is just positioning within the carousel. */
.carousel-prev,
.carousel-next {
  position: absolute;
  /* Positioned relative to .carousel-viewport (see above), not
     .project-carousel — that wrapper also contains the caption and
     dots below the image, so top:50% there landed below the image's
     actual center. Now this 50% is against just the image area. */
  top: 50%;
  transform: translateY(-50%);
}

.carousel-prev:active,
.carousel-next:active {
  transform: translateY(-50%) scale(0.9);
}

.carousel-prev {
  left: 0.75rem;
}

.carousel-next {
  right: 0.75rem;
}

@media (max-width: 640px) {
  /* Swipe (already touch-only — see script.js) is the only way to
     navigate the carousel on mobile; the buttons are just clutter
     over the image at that size. */
  .carousel-prev,
  .carousel-next {
    display: none;
  }
}

.project-carousel .carousel-caption {
  margin-top: 0.8rem;
  font-size: 0.9rem;
  font-weight: 400;
  line-height: 1.5;
  letter-spacing: normal;
  color: var(--muted);
  text-align: center;
}

.carousel-dots {
  display: flex;
  justify-content: center;
  gap: 0.5rem;
  margin-top: 0.75rem;
}

.carousel-dot {
  width: 8px;
  height: 8px;
  /* var(--image-radius) (4px) would round this into a circle again at
     this size — needs a proportionally smaller radius to actually
     read as a tiny square instead. */
  border-radius: 2px;
  border: none;
  padding: 0;
  background-color: #d8d5cc;
  cursor: pointer;
}

.carousel-dot.active {
  background-color: var(--accent);
}

.topic-divider{
  border-bottom: 1px solid #e4e2db;
  padding-top: 3rem;
  padding-bottom: 1.5rem;
  scroll-margin-top: 4rem; /* tucks the previous divider's border under the docked header (~3.3rem), then padding-top below gives the h2 breathing room */
}

.topic-divider-no-line{
  border-bottom: 0 !important;
  padding-top: 3rem !important;
}

.topic-divider-first{
  padding-top: 0.5rem;
}

@media (max-width: 900px) {
  .topic-divider{
    /*padding-top: 2rem;
    padding-bottom: 0.5rem;*/
    scroll-margin-top: 3.25rem;
  }

  .topic-divider-first{
    padding-top: 0rem;
  }
}

@media (max-width: 600px) {
  .topic-divider{
    /*padding-top: 1.5rem;
    padding-bottom: 0rem;*/
    scroll-margin-top: 2.5rem;
  }

  .topic-divider-first{
    padding-top: 0rem;
  }
}

/* Project header: stats (3fr) next to the "On this page" index (1fr).
   Below 900px there isn't room for both, so the index drops under the
   stats and both go full width. */
.project-header-columns {
  display: grid;
  /*grid-template-columns: 6fr 1fr;*/
  align-items: start;
  gap: 0;
  margin-top: 2rem;
  padding: 0;
  justify-content: center;
}

.about-header-columns {
  display: grid;
  grid-template-columns: 1fr;
  align-items: center;
  margin-top: 2rem;
  padding: 0;
}

@media (max-width: 840px) {
  .project-header-columns {
    grid-template-columns: 1fr;
  }
  

  .project-index {
    max-width: 250px;
    min-width: 210px;
    background-color: var(--index);
    border-radius: 4px;
    box-shadow: 0 8px 16px -8px rgba(0, 0, 0, 0.1);
    padding: 1.5rem;
  }

  .project-index ul {
    gap: 0.3rem;
  }

  .project-index-label {
    margin-bottom: 0.35rem;
  }
}

/* Desktop: the index floats to the right of the specs instead of
   sharing a grid track with them — reserve room on the right for it
   (padding-right) and pin it there (position: absolute, relative to
   this container so it lines up with the top of the specs row
   regardless of how tall the H1/subtitle above happen to be). It's
   positioned only — it scrolls away normally with the rest of the
   header, it doesn't stay pinned as you scroll the page. */
@media (min-width: 840px) {
  .project-header-columns {
    position: relative;
    grid-template-columns: 1fr;
    padding-right: 240px;
  }

  /* Both columns share the single grid cell instead of stacking into
     two rows — the second one (wrapping the now absolutely-positioned
     .project-index) is otherwise empty, but a grid row still gets
     created for it, and gap still adds space above that empty row
     even though nothing visible is there to see. */
  .project-header-columns > .split-column {
    grid-row: 1;
    grid-column: 1;
  }

  .project-index {
    position: absolute;
    /*top: -160px;*/
    top: -200px;
    /*right: 6rem;*/
    right: 0;
    width: 260px;
    /*background-color: none;*/
  }
}

@media (max-width: 1200px) {
  .project-index {
    right: 0;
  }
}


/* Two-column layout: put images in one .split-column and text in the
   other. Order in the HTML decides left vs. right — put the images
   markup first for image-left/text-right, or swap it for the reverse.
   items: start keeps each column's own height (the taller one doesn't
   stretch the other), and it collapses to a single stacked column on
   narrow screens. */
.split-columns, .split-columns-tool-kit {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
  align-items: start;
  margin: 1rem 0 1rem 0;
}

.split-column .project-figure:first-child {
  margin-top: 0;
  
}


@media (max-width: 856px) {
  .split-columns-tool-kit {
    grid-template-columns: 1fr;
    gap: 0rem;
  }
}

@media (max-width: 700px) {
  .split-columns {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }
}



.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr 1fr;
  gap: 3rem;
  align-items: start;
  margin: 2rem 0;
}

@media (max-width: 900px) {
  .grid {
    grid-template-columns: 1fr 1fr;
    gap: 1.5rem 3rem;
  }
}

@media (max-width: 600px) {
  .grid {
    grid-template-columns: 1fr;
    gap: 0.5rem;
  }
}




/* Text next to a large image, in a fixed 1:3 ratio (text:image) once
   there's room for both at their minimum widths. Below 1200px wide —
   or whenever 280px + 700px + the gap won't fit — the media query
   drops it to a single column, image below text, both full width.
   Reuses .split-column for the two children, same as .split-columns. */
.text-image-columns {
  display: grid;
  grid-template-columns: minmax(300px, 1fr) minmax(auto, 2fr);
  gap: 3rem;
  align-items: center;
}

.two-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 3rem;
  align-items: center;
  /*margin: 2rem 0;*/
}

@media (max-width: 1024px) {
  .text-image-columns {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }
}

@media (max-width: 700px) {
  .two-columns {
    grid-template-columns: 1fr;
    gap: 1.5rem;
  }
}

/* Figma embed: always keeps a 16:9 frame — width fills its container,
   height is derived from that width rather than a fixed pixel value,
   so it stays proportional (and doesn't overflow) at any screen size. */
.figma-imbeded {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 1;
  height: auto;
  border: 1px solid rgba(0, 0, 0, 0.1);
  border-radius: var(--image-radius);
}

.figma-imbeded-div {
  position: relative;
  background-color: #ffffff;
  border-radius: var(--image-radius);
  overflow: hidden;
  margin-bottom: 1rem;
}

.figma-loading-spinner {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 28px;
  height: 28px;
  margin: -14px 0 0 -14px;
  border: 3px solid rgba(0, 0, 0, 0.1);
  border-top-color: rgba(0, 0, 0, 0.35);
  border-radius: 50%;
  animation: figma-spin 0.8s linear infinite;
  pointer-events: none;
}

@keyframes figma-spin {
  to {
    transform: rotate(360deg);
  }
}

/*@media (max-width: 800px) {
  .figma-imbeded {
    aspect-ratio: 5 / 4;
  }
}*/

@media (max-width: 700px) {
  .figma-imbeded {
    aspect-ratio: 1 / 1;
  }
}

/* Same idea as .figma-imbeded but for YouTube demo videos, which are
   natively widescreen rather than a wide design-file canvas. */
.video-embed {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  height: auto;
  border: 1px solid rgba(0, 0, 0, 0.1);
  /*margin-bottom: 1.5rem;*/
}

/* A self-hosted, autoplaying, silent loop used like an animated GIF —
   no player chrome, no controls, just a short demo of an interaction.
   Unlike .video-embed (a real iframe player), this is a plain <video>,
   so it gets the same rounded-corner treatment as project imagery. */

.video{
  display: block;
  object-fit: cover;
  /*border-radius: var(--image-radius);*/
}

.inline-video {
  width: 100%;
  max-width: 700px;
  height: auto;
  /*aspect-ratio: 5 / 3;*/
  background-color: var(--paper);
  margin-bottom: 1.5rem;
}

.video-carousel {
  width: 100%;
  /*max-width: 700px;*/
  height: auto;
  /*aspect-ratio: 5 / 3;*/
  background-color: var(--paper);
}

.project-nav {
  max-width: 1200px; /* 720px */
  margin: 0 auto;
  padding: 0 2rem 4rem;
  display: flex;
  justify-content: space-between;
  /*border-top: 1px solid #e4e2db;
  padding-top: 2rem;*/
}

.project-nav a {
  position: relative;
  display: inline-block;
  color: var(--ink);
  text-decoration: none;
  font-size: 0.9rem;
}

.project-nav a:hover,
.project-nav a:focus-visible {
  color: var(--accent);
}

/* ============================================
   9. ABOUT PAGE — experience timeline
   ============================================ */

/* Featured roles: date column + content column, generous spacing —
   used for the handful of positions worth a real description. */
.timeline {
  display: flex;
  flex-direction: column;
  gap: 3rem;
  margin-bottom: 1rem;
}

/* Lines up "Product Design" / "Graphic design" / "Technical roots"
   with the content column of the entries below them (175px date
   column + the 2rem gap after it), instead of sitting flush left with
   the date badges. Reset on mobile, where the grid collapses to a
   single column and the content itself no longer starts at that
   x-position. */
.timeline-group h3 {
  /*margin-left: calc(175px + 2rem);*/
}

@media (max-width: 640px) {
  .timeline-group h3 {
    /*margin-left: 60px;*/
    display: none;
  }
}

.timeline-entry {
  display: grid;
  grid-template-columns: 175px 1fr;
  gap: 0.5rem 2rem;
  position: relative;
}

/* Wraps one or more .timeline/.timeline-compact blocks that should
   read as a single continuous timeline — Experience wraps its
   "Product Design" / "Graphic design" / "Technical roots" blocks
   together so the rail runs through all three (and their h3s) instead
   of restarting at each one; Education wraps its lone block the same
   way for consistency. Keeping the rail on this shared wrapper rather
   than on each individual block is what makes that continuity
   possible — the dots stay positioned per-entry either way. */
.timeline-group, .timeline-group-compact {
  position: relative;
}

/* The vertical "rail" running down the timeline, and a dot on it for
   each entry marking where that entry's date badge connects to it.
   Positioned at 200/202px — the date column's width — on both the
   line (relative to .timeline-group, which spans every block inside
   it) and the dot (relative to its own entry, which starts flush with
   that same left edge), so the two line up exactly. Hidden on narrow
   screens, where the date column drops above the content instead of
   sitting beside it, so there's no fixed x-position for either to
   align to anymore. */

/* TIMELINE LINE */
.timeline-group-compact::before,
.timeline-group::before {
  content: '';
  position: absolute;
  top: 5.5rem;
  bottom: 5rem;
  left: 230px;
  width: 1px;
  background-color: #e4e2db;
}

.timeline-group-compact::before{
  top: 2rem;
  bottom: 3rem;
}

@media (max-width: 700px) {
  .timeline-group::before {
    bottom: 6rem;
  }

  .timeline-group-compact::before{
    bottom: 3rem;
  }
}



/* TIMELINE BADGE */
.timeline-date {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.5rem;
  padding-top: 0.15rem;
  /*padding-right: 2rem;*/
}

/* TIME LINE PIN*/
.timeline-entry::before,
.timeline-compact-entry::before {
  content: '';
  position: absolute;
  left: 202px;
  top: 2rem;
  width: 5px;
  height: 5px;
  margin-left: -4px;
  border-radius: 50%;
  /*background-color: var(--muted);*/
  /*background-color: #b1b0aa;*/
}

/* Reuses the same pill look as the project pages' Role/Tool/Timeline
   badges, but the date column here is a fixed 200px — narrower than
   badges normally sit in — so text needs to wrap inside the pill
   instead of the badge's usual white-space: nowrap, which would just
   overflow past the column for longer entries like "3 years,
   part-time". align-items: stretch above makes every badge fill the
   column's full width, so its right edge lands right at the rail —
   needed for the first badge's tooltip-style pointer just below to
   actually touch the line instead of floating short of it. */
.timeline-date .badge {
  white-space: normal;
  text-align: left;
  cursor: default;
}

/* Tooltip-style pointer on the first (date-range) badge only, aimed
   at the rail — the other badges in the same column (duration, etc.)
   are just plain stacked labels underneath it. */
.timeline-date .badge:first-child {
  position: relative;
}

/* TRIANGLE BADGE */
.timeline-date .badge:first-child::after {
  content: '';
  position: absolute;
  top: 50%;
  right: -6px;
  transform: translateY(-50%);
  width: 0;
  height: 0;
  border-top: 5px solid transparent;
  border-bottom: 5px solid transparent;
  border-left: 6px solid var(--index);
}

@media (max-width: 640px) {
  .timeline-group::before,
  .timeline-group-compact::before{
    /*display: none;*/
    left: 24px;
  }

  .timeline-group-compact::before{
    margin-bottom: 3rem;
    margin-top: 4rem;
  }

  /* TIMELINE BADGE */
  .timeline-date {
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    padding-left: 60px;
    padding-bottom: 0.5rem;
  }

  .timeline-date .badge {
    font-size: 0.8rem;
  }

  /* TRIANGLE BADGE */
  /*.timeline-date .badge:first-child::before {
    content: '';
    position: absolute;
    top: 50%;
    left: -6px;
    transform: translateY(-50%);
    width: 0;
    height: 0;
    border-top: 5px solid transparent;
    border-bottom: 5px solid transparent;
    border-right: 6px solid var(--index);
  }*/

  .timeline-date .badge:first-child::after {
    display:none;
  }
}





.timeline-role {
  font-weight: 700;
}

.timeline-company {
  color: var(--muted);
  font-size: 0.9rem;
  margin-bottom: 0.5rem;
}

.timeline-title-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.timeline-logo-link {
  display: block;
  flex-shrink: 0;
  line-height: 0;
  border-radius: 9px;
  transition: opacity 1s ease;
}

.timeline-logo-link:hover {
  opacity: 0.8;
}

.timeline-logo {
  width: 49px;
  height: 49px;
  border-radius: 9px;
  object-fit: cover;
  flex-shrink: 0;
}

.timeline-logo-sm {
  width: 46px;
  height: 46px;
  border-radius: 8px;
}

.timeline-compact-entry .timeline-title-row {
  gap: 0.6rem;
  /*margin-bottom: 0.15rem;*/
}

.timeline-compact-body {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
}

.timeline-entry p {
  max-width: 640px;
  margin-left: calc(49px + 0.75rem);
}

.timeline-link {
  position: relative;
  display: inline-block;
  margin-left: calc(49px + 0.75rem);
  color: var(--muted);
  text-decoration: none;
}

.timeline-link:hover,
.timeline-link:focus-visible {
  color: var(--link-hover);
}

/* Compact entries don't have a large 49px logo to align under like the
   featured .timeline-entry ones do — their text column already starts
   right after the small logo via flex gap, so the base margin-left
   would double-indent the link relative to the description above it. */
.timeline-compact-body .timeline-link {
  margin-left: 0;
  margin-top: 0.35rem;
  margin-right: 1.25rem;
}

.link {
  display: inline-block;
  /*margin-top: 1rem;*/
  font-size: 0.85rem;
}

.link a {
  position: relative;
  display: inline-block;
  color: var(--muted);
  text-decoration: none;
}

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

/* Generic reusable version of the same pattern for a link sitting
   inline within a larger sentence (rather than its own standalone
   "<p class="link">"), e.g. the "Get in touch" paragraph's LinkedIn
   link — was previously unstyled, relying on the browser's default
   blue + underline. */
.inline-link {
  position: relative;
  display: inline-block;
  color: var(--link);
  text-decoration: none;
}

.inline-link:hover,
.inline-link:focus-visible {
  color: var(--link-hover);
}

@media (max-width: 640px) {
  .timeline-entry {
    grid-template-columns: 1fr;
    gap: 0.15rem;
  }

  .timeline-date {
    padding-top: 0;
  }
}

/* Condensed roles: same two-column shape, but a single row each,
   divided by hairlines — for the earlier/adjacent-field roles that
   don't need a full write-up here (most already exist in more detail
   on LinkedIn). */
.timeline-compact {
  /*border-top: 1px solid #e4e2db;*/
  position: relative;
}

.timeline-compact-entry {
  display: grid;
  grid-template-columns: 175px 1fr;
  gap: 0.5rem 2rem;
  padding: 2rem 0;
  font-size: 0.9rem;
  position: relative;
}

.timeline-compact-entry .timeline-date {
  padding-top: 0;
}

.timeline-compact-entry .timeline-role {
  font-weight: 600;
}

.timeline-compact-entry .timeline-company {
  margin-bottom: 0;
  display: inline;
}

.about-portrait-row {
  display: flex;
  align-items: center;
  gap: 2rem;
}

.about-portrait {
  width: 140px;
  height: 140px;
  border-radius: 50%;
  object-fit: cover;
  flex-shrink: 0;
}

@media (max-width: 640px) {
  .about-portrait-row {
    flex-direction: column;
    align-items: flex-start;
    gap: 1.25rem;
  }

  .about-portrait {
    width: 110px;
    height: 110px;
  }
}

@media (max-width: 640px) {
  .timeline-compact-entry {
    grid-template-columns: 1fr;
    gap: 0.15rem;
  }
}

/* ============================================
   10. COPY-EMAIL BUTTON
   ============================================ */
.copy-email-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  vertical-align: middle;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  margin-left: 0px;
  border: none;
  background: none;
  color: var(--muted);
  cursor: pointer;
  border-radius: 5px;
  transition: color 0.15s ease, background-color 0.15s ease;
}

.copy-email-btn:hover {
  color: #1a1a1a;
  background-color: rgba(26, 26, 26, 0.08);
}

.copy-email-btn .material-symbols-outlined {
  font-size: 1.05rem;
}

.copy-email-btn.copied {
  color: #2e7d32;
}

/* Small qualifier next to a toolkit category heading (e.g. "(basic)"
   on Development & data) — flags a lower confidence level without
   needing its own heading or paragraph. */
.toolkit-category-note {
  font-size: 0.75rem;
  font-weight: 400;
  color: var(--muted);
}

/* Toolkit badges — icon + name pills, sitting under the Skills & tools
   grid. Reuses the same muted paper tone as the "On this page" index
   box, just in pill form. */
.badge-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 0.7rem;
  margin-bottom: 2.2rem;
  /*background-color: #00ff00;*/
}

.graphic-motion{ max-width: 400px; }
.collaboration-documentation{ max-width: 300px; }

@media (max-width: 1159px) {
  .development-data{ max-width: 480px; }
}

@media (max-width: 1008px) {
  .development-data{ max-width: 340px; }
}

@media (max-width: 1056px) {
  .research-testing{ max-width: 300px; }
}

.badge {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.8rem;
  background-color: var(--index);
  border-radius: 4px;
  font-size: 0.9rem;
  color: var(--ink);
  text-decoration: none;
  cursor: default;
  white-space: nowrap;
  transition: background-color 0.15s ease;
  box-shadow: 0 4px 8px -4px rgba(0, 0, 0, 0.1);
}

a.badge {
  color: var(--ink);
  cursor: pointer;
}

a.badge:hover,
a.badge:focus-visible {
  background-color: #e4e2db;
}

.badge-icon {
  width: 1.1rem;
  height: 1.1rem;
  flex-shrink: 0;
}

/* ============================================
   11. CONTACT SECTION — social icon row
   ============================================ */
.social-links {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 3rem;
}

.social-links a {
  color: var(--ink);
  text-decoration: none;
}

.social-links .copy-email-btn {
  color: var(--ink);
}

.social-links a,
.social-links .copy-email-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: auto;
  height: auto;
  /* Same frosted-glass treatment as the carousel arrows/lightbox
     controls/back-to-top, but a bit bigger and more transparent at
     rest — this row reads better with a lighter touch than the
     buttons floating over photos do. Negative margin cancels the
     padding's effect on the row's own gap, so the icon-to-icon
     spacing stays what it was before this box existed. */
  padding: 16px;
  margin: -16px;
  background-color: rgba(250, 249, 246, 0.15);
  backdrop-filter: blur(8px) saturate(180%);
  -webkit-backdrop-filter: blur(8px) saturate(180%);
  border: none;
  border-radius: var(--image-radius);
  cursor: pointer;
  transition: color 0.2s ease, background-color 0.2s ease;
}

@supports not (backdrop-filter: blur(1px)) {
  .social-links a,
  .social-links .copy-email-btn {
    background-color: rgba(250, 249, 246, 0.92);
  }
}

/* Background alone — not color — applies on hover regardless of the
   copied state, so clicking the email icon doesn't make the visible
   hover background suddenly fade back down to the barely-there rest
   opacity right as the mouse is still sitting over it. */
.social-links a:hover,
.social-links a:focus-visible,
.social-links .copy-email-btn:hover {
  background-color: var(--index);
}

.social-links a:hover,
.social-links a:focus-visible,
.social-links .copy-email-btn:not(.copied):hover {
  /* Light grey background with a blue icon — the stroke picks up
     the same blue for free via currentColor. Same darker blue
     (--link-hover) as the animated underline on back-link/
     timeline-link/etc., not --accent, for one consistent hover blue
     across the site rather than two slightly different ones.
     Excludes .copied so the green confirmation color (set globally
     on .copy-email-btn.copied) isn't overridden back to blue while
     the mouse is still hovering right after a click. */
  color: var(--link-hover);
}

.social-links svg {
  width: 20px;
  height: 20px;
}

.social-links a .material-symbols-outlined,
.social-links .copy-email-btn .material-symbols-outlined {
  font-size: 20px;
}

/* The copy-email button swaps between two icons (mail / check)
   by toggling which one is displayed, rather than rewriting a single
   glyph's text content — works the same way whether the icon markup
   is a Material Symbols span or a hand-drawn SVG. */
.copy-email-btn .icon-copied {
  display: none;
}

.copy-email-btn.copied .icon-default {
  display: none;
}

.copy-email-btn.copied .icon-copied {
  display: inline-block;
}

/* ============================================
   12. TOAST — brief confirmation message, used after
   copy-to-clipboard actions. Created on demand by script.js.
   ============================================ */
.toast {
  position: fixed;
  left: 50%;
  bottom: 2rem;
  transform: translateX(-50%) translateY(12px);
  background-color: var(--ink);
  color: var(--paper);
  padding: 0.7rem 1.25rem;
  border-radius: 8px;
  font-size: 0.85rem;
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.35);
  opacity: 0;
  pointer-events: none;
  z-index: 100;
  transition: opacity 0.25s ease, transform 0.25s ease;
}

.toast.visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  .toast {
    transition: opacity 0.15s ease;
    transform: translateX(-50%);
  }
}

/* ============================================
   13. HOVER STROKE — echoes the logo's square-bracket outline.
   A masked border sweeps clockwise starting near the top-left corner
   — both drawing in on hover AND erasing back out the same clockwise
   direction it drew in, rather than un-drawing in reverse. That needs
   two independently-animatable custom properties (--stroke-start,
   --stroke-end: registered below via @property so they can actually
   transition, not just snap) instead of one: entering hover grows
   --stroke-end forward while --stroke-start stays pinned at 0%;
   leaving hover advances --stroke-start forward instead while
   --stroke-end stays pinned at 100%. A single :hover toggle can't
   drive two different properties depending on direction, so script.js
   sets these directly on mouseenter/mouseleave/focus/blur — see the
   "Hover stroke" section there.
   Applied only to standalone buttons (back-to-top, carousel arrows,
   every lightbox control) — skipped on the carousel dots (too small
   to read as a drawn line) and the zoom pill's own container (its two
   buttons already get the effect individually; a third outline around
   the whole pill on top of that reads as clutter, not polish).
   currentColor keeps the stroke matched to each button's own icon
   color, so it stays visible whether the button is light-on-dark
   (back-to-top) or dark-on-light (everything else). A conic-gradient
   mask (rather than the border-on-a-shrinking-box trick this replaced)
   also means there's no stray corner pixel at rest — the border is
   always full-size, just fully masked away until hovered.
   This is a real element (.hover-stroke-overlay, injected by
   setUpHoverStroke in script.js) rather than a ::before — a ::before
   with a var()-driven mask-image didn't reliably repaint on some
   engines when the custom properties changed, whereas a real element
   with the identical styling does. */
@property --stroke-start {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

@property --stroke-end {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

.hover-stroke-overlay {
  content: '';
  position: absolute;
  inset: 5px;
  /* A whole pixel, not fractional — fractional widths land on
     different sub-pixel offsets depending on each element's exact
     position, so they don't all anti-alias to the same visual
     weight. Matches .underline-hover-overlay's height for the same
     reason. */
  border: 1px solid currentColor;
  pointer-events: none;
  -webkit-mask-image: conic-gradient(from 315deg, transparent var(--stroke-start), black var(--stroke-start), black var(--stroke-end), transparent var(--stroke-end));
  mask-image: conic-gradient(from 315deg, transparent var(--stroke-start), black var(--stroke-start), black var(--stroke-end), transparent var(--stroke-end));
  transition: --stroke-start 0.4s linear, --stroke-end 0.4s linear;
}

@media (prefers-reduced-motion: reduce) {
  .hover-stroke-overlay {
    transition: none;
  }
}

/* ============================================
   14. UNDERLINE HOVER — the same sweep-in/sweep-out-the-same-
   direction idea as the button hover stroke above, but a single
   horizontal line instead of a full square: nav links, "back" links,
   case-study links, and "read more" links, so hovering any text link
   on the site feels consistent. Same two-property (--underline-start,
   --underline-end) technique for the same reason — see
   setUpSweepHover in script.js. currentColor keeps each link's
   underline matched to its own text color; .index-nav a overrides it
   to --accent to keep nav's existing hover color legible even though
   the nav text itself doesn't change color on hover. */
@property --underline-start {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

@property --underline-end {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

.underline-hover-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  /* Whole pixel to match .hover-stroke-overlay's border-width exactly
     — see its comment for why fractional widths render inconsistently. */
  height: 1px;
  pointer-events: none;
  background: linear-gradient(to right, transparent var(--underline-start), currentColor var(--underline-start), currentColor var(--underline-end), transparent var(--underline-end));
  transition: --underline-start 0.3s linear, --underline-end 0.3s linear;
}

.index-nav a .underline-hover-overlay {
  color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .underline-hover-overlay {
    transition: none;
  }
}