@font-face {
  font-family: 'General Sans';
  src: url('../assets/fonts/GeneralSans_Complete/Fonts/WEB/fonts/GeneralSans-Variable.woff2') format('woff2'),
       url('../assets/fonts/GeneralSans_Complete/Fonts/WEB/fonts/GeneralSans-Variable.woff') format('woff'),
       url('../assets/fonts/GeneralSans_Complete/Fonts/WEB/fonts/GeneralSans-Variable.ttf') format('truetype');
  font-weight: 200 700;
  font-display: swap;
  font-style: normal;
}
@font-face {
  font-family: 'General Sans';
  src: url('../assets/fonts/GeneralSans_Complete/Fonts/WEB/fonts/GeneralSans-VariableItalic.woff2') format('woff2'),
       url('../assets/fonts/GeneralSans_Complete/Fonts/WEB/fonts/GeneralSans-VariableItalic.woff') format('woff'),
       url('../assets/fonts/GeneralSans_Complete/Fonts/WEB/fonts/GeneralSans-VariableItalic.ttf') format('truetype');
  font-weight: 200 700;
  font-display: swap;
  font-style: italic;
}

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

:root {
  --bg: #232323;
  --fg: #f4f2ee;
  --fg-dim: #6a6a68;
  --accent: #f4f2ee;

  --edge-pad: 4vw;
}

html, body {
  background: var(--bg);
  color: var(--fg);
  font-family: 'General Sans', "Helvetica Neue", Arial, sans-serif;
}

/* `overflow-x: hidden` on `html` only: putting it on `body` too makes body
   compute `overflow-y: auto` per spec (only one axis can stay `visible`),
   turning body into its own scroll container. That swallowed the extra
   height our pinned/scrubbed row needs, so the document never grew and
   the page couldn't actually scroll through the slider. */
html {
  overflow-x: hidden;
}

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.75rem 2.5rem;
  z-index: 10;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  mix-blend-mode: difference;
  /* Own compositing layer: without it, Chromium can leave this fixed +
     blend-mode layer stuck at its pre-scroll paint position once the
     cascading slider's ScrollTrigger pin promotes a layer nearby. */
  will-change: transform;
}

.site-footer {
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1.75rem 2.5rem;
  z-index: 10;
  font-size: 0.8rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  mix-blend-mode: difference;
  /* Own compositing layer: without it, Chromium can leave this fixed +
     blend-mode layer stuck at its pre-scroll paint position once the
     cascading slider's ScrollTrigger pin promotes a layer nearby. */
  will-change: transform;
}

.site-header__name {
    font-weight: 400;
    letter-spacing: 0.05em;
    color: var(--fg);
    font-family: 'General Sans', "Helvetica Neue", Arial, sans-serif;
    text-decoration: none;
}

.site-header__nav {
  display: flex;
  gap: .5rem;
}

/* Nav: the current section reads at full strength, the rest sit at half
   opacity (hover brings any of them up). `is-current` is kept in sync
   with body[data-page] by syncHeaderActiveNav in js/header.js — on
   Barba swaps too, via the page:reset event. */
.site-header__label {
  text-decoration: none;
  color: var(--fg);
  opacity: 0.5;
  transition: opacity 0.25s ease;
}

.site-header__label:hover {
  opacity: 1;
}

.site-header__label.is-current {
  color: var(--accent);
  opacity: 1;
}

/* Hamburguesa + overlay de menú móvil (js/header.js). Ocultos en
   escritorio; el bloque @media (max-width:900px) más abajo los activa.
   El overlay .site-menu es hijo de <body>, no del header (ver header.js). */
.site-header__burger {
  display: none; /* activado en móvil */
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  width: 34px;
  height: 34px;
  padding: 0;
  border: none;
  background: none;
  cursor: pointer;
}

.site-header__burger-line {
  display: block;
  width: 26px;
  height: 2px;
  margin-left: auto;
  background: var(--fg);
  transition: transform 0.35s cubic-bezier(0.65, 0, 0.35, 1), opacity 0.2s ease;
}

/* Abierto: las dos líneas se cruzan en X. */
body.nav-open .site-header__burger-line:nth-child(1) {
  transform: translateY(4px) rotate(45deg);
}
body.nav-open .site-header__burger-line:nth-child(2) {
  transform: translateY(-4px) rotate(-45deg);
}

.site-menu {
  display: none; /* activado en móvil */
  position: fixed;
  inset: 0;
  z-index: 9; /* bajo el header (z-index:10): nombre + X quedan encima */
  background: var(--bg);
  flex-direction: column;
  justify-content: center;
  align-items: flex-end;
  padding: 0 8vw;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-8px);
  transition: opacity 0.4s ease, visibility 0s 0.4s, transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}

body.nav-open .site-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
  transition: opacity 0.4s ease, transform 0.4s cubic-bezier(0.65, 0, 0.35, 1);
}

.site-menu__nav {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.4em;
  text-align: right;
}

.site-menu__link {
  color: var(--fg);
  text-decoration: none;
  font-size: 3rem;
  line-height: 1.1;
  letter-spacing: -0.01em;
  text-transform: uppercase;
  opacity: 0.5;
  transition: opacity 0.25s ease;
}

.site-menu__link:hover,
.site-menu__link.is-current {
  opacity: 1;
}

.site-header__back {
  text-decoration: none;
  color: var(--fg-dim);
  transition: color 0.25s ease;
  display: none;
}

.site-header__back:hover {
  color: var(--fg);
}

/* Header is fully global now — same name/clock/nav on every page. The
   "← Index" back-link only makes sense on a case study, so that's the
   one piece that's still page-scoped. */
body[data-page="case-study"] .site-header__back {
  display: inline;
}

/* Clock "screensaver": normal state is the small inline pinned reading
   ("VIE 17 JUL · 17:44:45" — date first, zone hidden); .is-expanded is
   toggled by the scroll-scrubbed transform in script.js (initClockScrub)
   once the clock has scaled up and centered, restacking it into 3
   centered lines with the zone (back in play here) and date de-emphasized
   above/below the time. The 7x scale itself comes from the GSAP transform
   on [data-current-time] — sizes here are only relative to that, not the
   "big" look on their own. */
[data-current-time] {
  display: flex;
  align-items: baseline;
  /* gap: 0.35em; */
  transform-origin: center center;
}

[data-current-time] .current-time__zone,
[data-current-time] .current-time__date {
  transition: opacity 0.3s ease, font-size 0.3s ease;
}

/* Collapsed order: date, then time — the "· " separator lives in the
   date's own ::after (not a gap/margin) so it survives regardless of the
   flex gap above being on or off. */
[data-current-time] .current-time__zone {
  display: none;
}

[data-current-time] .current-time__date {
  display: block;
  order: -1;
}

[data-current-time]:not(.is-expanded) .current-time__date::after {
  content: '\00a0\00b7\00a0';
}

[data-current-time].is-expanded {
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
}

/* Expanded order: zone, time, date — overrides the collapsed date's
   order: -1 above so it drops back to the bottom of the stack. */
[data-current-time].is-expanded .current-time__zone {
  order: -1;
}

[data-current-time].is-expanded .current-time__date {
  order: 1;
}

[data-current-time].is-expanded .current-time__zone,
[data-current-time].is-expanded .current-time__date {
  display: block;
  font-size: 0.2em;
  opacity: 0.5;
}

[data-current-time].is-expanded .current-time__clock {
  opacity: 1;
}

/* Falls back to the real OS pointer while the clock is expanded. The
   universal `cursor: none !important` further down (every element, so the
   custom .cursor circle can take over) has specificity (0,0,0) — this
   attribute+class selector beats it even with matching !important, same
   trick as .infinite-grid's native grab/grabbing cursor below. Needed
   because the custom circle is easy to lose against the clock at 7x
   scale, on top of magnetic-cursor.js's own is-expanded guard leaving it
   parked in the header instead of following the mouse here at all. */
@media (hover: hover) and (pointer: fine) {
  [data-current-time].is-expanded,
  [data-current-time].is-expanded * {
    cursor: pointer !important;
  }
}

/* At 7x scale the hover bg block would scale up right along with the
   clock and sit huge in the middle of the screen — hide it for as long as
   the clock is expanded rather than let it get in the way. */
[data-current-time].is-expanded .magnetic-link__bg {
  display: none;
}

/* ─── Header en móvil ───────────────────────────────────────
   Oculta el reloj y la nav inline; en su lugar, la hamburguesa abre el
   overlay .site-menu. El nombre y la hamburguesa (que se vuelve X) siguen
   sobre el overlay porque el header (z-index:10) va por encima. */
@media (max-width: 900px) {
  .site-header {
    padding: 1.5rem 5vw;
  }

  .site-header [data-current-time] {
    display: none;
  }

  /* En móvil solo el nombre; la coletilla "–– Interaction Designer." se
     oculta (como en la referencia). */
  .site-header__tagline {
    display: none;
  }

  .site-header__nav {
    display: none;
  }

  .site-header__burger {
    display: flex;
  }

  .site-menu {
    display: flex;
  }

  /* Bloquea el scroll de fondo mientras el menú está abierto. */
  body.nav-open {
    overflow: hidden;
  }
}

.index {
  display: flex;
  /* .index__preview is position:fixed (out of flow either way) so this
     only actually matters once .monigotes joins .index__scroller as a
     second real flex child below — stack them, don't sit them side by
     side. */
  flex-direction: column;
  width: 100%;
  min-height: 100vh;
}

.index__scroller {
  width: 100%;
  padding: 0 var(--edge-pad);
}

.index__spacer {
  height: 100vh;
}

/* The trailing spacer only (between the list and .monigotes) — shrunk
   from 100vh to cut the dead space before .monigotes. Shrinking the
   LEADING spacer too (same class, no modifier) broke the entrance-scroll
   animation: scrollToFirstRow() in script.js targets `rect.top +
   scrollY + rect.height/2 - innerHeight/2` for row 0, and with a short
   leading spacer that target lands at/below 0 (row 0 already sits near
   the viewport center on load) — nothing left to animate, so it read as
   "no longer animates." Leaving the leading spacer at 100vh keeps that
   travel distance intact; only this one needs to be small. */
.index__spacer--end {
  height: 30vh;
}

.index__list {
  display: flex;
  flex-direction: column;
}

.index__row {
  display: flex;
  align-items: center;
  gap: 1.5rem;
  padding: 1vh 0;
  cursor: pointer;
  opacity: 0.34;
  transition: opacity 0.35s ease;
  will-change: transform;
  text-decoration: none;
  color: inherit;
}

.index__row.is-active {
  opacity: 1;
}

.index__row.is-active .index__title {
  /* font-weight: 400; */
}

.index__num {
  font-size: 0.85rem;
  color: var(--fg-dim);
  font-variant-numeric: tabular-nums;
  width: 2.5em;
  flex-shrink: 0;
}

.index__title {
  font-size: 4vw;
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.2;
}

.index__cat {
  padding-top: 16px;
  font-size: 0.85rem;
  color: var(--fg-dim);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
  padding-right: var(--edge-pad);
}

.index__preview {
  position: fixed;
  top: 50%;
  left: 4vw;
  width: clamp(140px, 15vw, 220px);
  aspect-ratio: 3 / 2;
  transform: translateY(-50%);
  pointer-events: none;
  z-index: 5;
}

.index__preview-item {
  position: absolute;
  inset: 0;
  opacity: 0;
  transform: scale(0.96);
  transition: transform 0.5s ease;
  overflow: hidden;
  border-radius: 2px;
}

.index__preview-item.is-active {
  opacity: 1;
  transform: scale(1);
}

.index__preview-item img,
.index__preview-item video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}


/* ==========================================================
   Osmo Supply — Draw On Element with Cursor
   Section/overlay structure ported close to verbatim from the source
   (.draw-section -> .monigotes, .draw-section-bg -> .monigotes__bg,
   .draw-section-overlay -> .monigotes__overlay) — renamed to match this
   site's existing class. See initDrawCursor/initDrawReveal/
   initDrawToolbar in draw-cursor.js. Index-only (index.html), so no
   page-level scoping needed here.

   .monigotes starts fully transparent (the dark page bg shows through)
   — initDrawReveal in draw-cursor.js animates its background-color to
   solid white as you scroll through it, finishing around 75% of the
   way down the section. The image/toolbar underneath are visible the
   whole time; the white background is the reveal.
   ========================================================== */

.monigotes {
  width: 96vw;
  height: 90vh;
  corner-shape: scoop;
  border-radius: 24px;
  left: 2vw;
  bottom: 2.5vh;
  position: relative;
  overflow: hidden;
  background: transparent;
}

.monigotes__bg {
  width: 100%;
  height: 100%;
}

.monigotes__bg-image {
  object-fit: cover;
  width: 100%;
  height: 100%;
  display: block;
}

.monigotes__overlay {
  z-index: 2;
  pointer-events: none;
  flex-flow: column;
  justify-content: end;
  align-items: center;
  padding: 2em;
  gap: 1.5rem;
  display: flex;
  position: absolute;
  inset: 0%;
}

.monigotes__cta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
}

.monigotes__text {
  font-size: 1.25rem;
  /* Light-on-dark everywhere else on the site; this section is the one
     white/paper moment, so its own text needs the opposite pairing. */
  color: var(--bg);
  text-align: center;
}

.btn {
  pointer-events: auto;
  padding: 0.75em 1.75em;
  border-radius: 100em;
  border: 1px solid var(--bg);
  background: transparent;
  color: var(--bg);
  font-family: 'General Sans', "Helvetica Neue", Arial, sans-serif;
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  cursor: pointer;
  transition: background 0.25s ease, color 0.25s ease;
}

.btn:hover {
  background: var(--bg);
  color: var(--fg);
}

/* Toolbar (Figma: node 1122:272) — move / paint / erase. Literal hex
   values here rather than the site's --fg/--bg tokens: those are meant
   for the dark theme everywhere else, but this toolbar's colors are
   fixed by the design regardless of theme (a light control sitting on
   the one white section of an otherwise dark site). */
.draw-toolbar {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px;
  background: #f7f7f7;
  border-radius: 10px;
}

.draw-toolbar__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 12px;
  border: none;
  border-radius: 8px;
  background: transparent;
  color: #1c1b1f;
  cursor: pointer;
  transition: background 0.2s ease, color 0.2s ease;
}

.draw-toolbar__btn svg {
  width: 100%;
  height: 100%;
}

.draw-toolbar__btn:hover {
  background: rgba(0, 0, 0, 0.06);
}

.draw-toolbar__btn.is-active {
  background: #000;
  color: #fff;
}

/* Avatar builder (Figma: node 1130:166) — panel + placed face parts.
   Original addition, not part of the ported Osmo resource above. Panel
   sits left-center, absolutely positioned so it doesn't disturb
   .monigotes__overlay's existing bottom-center flex flow (same
   pointer-events opt-back-in trick as .draw-toolbar/.btn already use
   against the overlay's own pointer-events:none). Shown/hidden via
   [hidden], toggled by initAvatarBuilder's setArmed() in draw-cursor.js. */
.avatar-panel {
  pointer-events: auto;
  position: absolute;
  left: 2em;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  flex-direction: column;
  gap: 1rem;
  padding: 1rem;
  background: #f7f7f7;
  border-radius: 10px;
}

/* Setting `display` above breaks the [hidden] attribute's own default
   UA-stylesheet rule (display:none) — an author rule with a `display`
   declaration always wins over that regardless of specificity, so
   .avatar-panel stayed visible in free-draw mode even while `hidden` was
   set. This rule has strictly higher specificity than the one above
   (adds the [hidden] attribute on top of the same class), so it wins
   back the override. */
.avatar-panel[hidden] {
  display: none;
}

.avatar-panel__group {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.avatar-panel__label {
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #1c1b1f;
}

.avatar-panel__row {
  display: flex;
  gap: 8px;
}

.avatar-option__btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 6px;
  border: 2px solid transparent;
  border-radius: 8px;
  background: #fff;
  cursor: pointer;
  transition: border-color 0.2s ease, background 0.2s ease;
}

/* NOT width:100%/height:100%+object-fit:contain on purpose: these icons
   are the actual assets/img/*.svg files, and each has its own,
   DIFFERENT native size (nariz-a is a thin 3x19 line, ojos-a is a wide
   37x20) — stretching every one of them up to fill the same 40px box
   independently scales each one's stroke-width by a different factor,
   so the smaller-viewBox assets (nariz-a; labio-2/3) came out looking
   drawn with a much heavier line than the rest. Rendering at native
   intrinsic size and applying ONE shared scale to all of them instead
   (same "native size x one multiplier, not stretch-to-fit" fix already
   used for the parts on the actual canvas — see AVATAR_BASE_SCALE in
   draw-cursor.js) keeps stroke weight visually consistent across every
   option. 0.7 comfortably fits the largest asset (nariz-b, 38px on its
   long side) inside this button's ~28px content box (40px - 2x6px
   padding), with room to spare for the rest. */
.avatar-option__btn img {
  display: block;
  transform: scale(0.7);
  pointer-events: none;
}

.avatar-option__btn:hover {
  background: #ececec;
}

/* Blue ring instead of the toolbar's black-invert (.draw-toolbar__btn
   above): these buttons' icons ARE the face-part artwork itself
   (white-filled/black-stroked), so inverting to a black background would
   make the strokes disappear instead of reading as "selected". Blue also
   pairs this with the canvas's own dashed-blue selection language below. */
.avatar-option__btn.is-active {
  border-color: #2563eb;
  background: #eaf0ff;
}

.avatar-option__btn--text {
  font-size: 0.65rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #1c1b1f;
}

/* Print-only credit line (see @media print below) — hidden on screen. */
.print-credit {
  display: none;
}

/* "Imprimir" (initAvatarBuilder's onPrintClick, draw-cursor.js) calls
   window.print() after measuring the canvas — this stylesheet is what
   makes that useful: it isolates .monigotes onto a single printed
   page/PDF and strips everything that's editing UI, not artwork
   (toolbar, panel, handles, selection outline). */

/* Zero margin kills the browser's own date/URL header/footer (they
   render inside the page margins), and pinning the paper to A4 portrait
   makes its CSS-px size a known constant (794x1123) that onPrintClick's
   fit-to-page scale is computed against. */
@page {
  size: A4 portrait;
  margin: 0;
}

@media print {
  /* Everything that isn't .monigotes, inside it, or an ancestor of it
     gets display:none. The previous visibility-only approach kept the
     hidden rest of the page occupying layout, so the document was still
     ~3 pages tall and the PDF paginated accordingly. Ancestors have to
     stay displayed (display:none takes the whole subtree down, children
     can't opt back in), but with all their other children gone they
     collapse to nothing. */
  body *:not(.monigotes):not(.monigotes *):not(:has(.monigotes)) {
    display: none !important;
  }

  body {
    margin: 0;
  }

  .monigotes {
    position: fixed;
    inset: 0;
    width: 100%;
    height: 100%;
    border-radius: 0;
    overflow: visible;
    /* The scroll-reveal (initDrawReveal, draw-cursor.js) animates this
       from transparent — whatever it happened to be mid-scroll at print
       time, force it back to a clean white page. */
    background: #fff !important;
  }

  /* The artwork box keeps its EXACT on-screen pixel size and is scaled
     uniformly to fit the page — strokes (absolute-px SVG lines) and
     avatar parts (px-sized at %-positions) only hold their composition
     if the box they're measured against doesn't reflow to the paper's
     aspect ratio. The vars are set by onPrintClick in draw-cursor.js
     right before window.print(); the 100%/1 fallbacks just mean "old
     behavior" if it somehow didn't run. */
  .monigotes__bg {
    position: fixed;
    left: 50%;
    top: 50%;
    width: var(--print-draw-w, 100%);
    height: var(--print-draw-h, 100%);
    transform: translate(-50%, -50%) scale(var(--print-draw-scale, 1));
  }

  .draw-toolbar,
  .avatar-panel,
  .monigotes__cta,
  .avatar-part__handle,
  .avatar-part__resize {
    display: none !important;
  }

  .avatar-part.is-selected {
    outline: none !important;
  }

  .avatar-part.is-selected::after {
    display: none !important;
  }

  .print-credit {
    display: block !important;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 8mm;
    margin: 0;
    text-align: center;
    font-family: 'General Sans', "Helvetica Neue", Arial, sans-serif;
    font-size: 11pt;
    color: #000;
  }
}

/* Placed avatar parts — created at runtime in initAvatarBuilder,
   appended into [data-draw] as a sibling of svg.cursor-svg. */
.avatar-layer {
  position: absolute;
  z-index: 11; /* above svg.cursor-svg's z-index: 10 */
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.avatar-layer.is-armed .avatar-part {
  pointer-events: auto;
}

.avatar-part {
  position: absolute;
  cursor: grab;
  user-select: none;
  touch-action: none;
}

.avatar-part:active {
  cursor: grabbing;
}

.avatar-part__img {
  display: block;
  /* No width/height override: each face-part SVG has its own, DIFFERENT
     native intrinsic size (e.g. ojos-a is 37x20, nariz-b is 15x38) —
     they were authored together at a shared scale, so forcing them all
     to the same rendered width distorted their sizes relative to each
     other (a narrow, tall nose blown up to eye-width read as huge).
     Rendering at native size and scaling the whole .avatar-part up via
     `transform: scale()` (see applyTransform in draw-cursor.js) keeps
     them proportional to each other exactly as designed. */
  pointer-events: none;
  -webkit-user-drag: none;
}

.avatar-part.is-selected {
  outline: 2px dashed #2563eb;
  outline-offset: 6px;
}

/* Stem connecting the selected part to its rotation handle — length
   matches .avatar-part__handle's `top: -28px` below. */
.avatar-part.is-selected::after {
  content: '';
  position: absolute;
  top: -26px;
  left: 50%;
  width: 1px;
  height: 24px;
  background: #2563eb;
  transform: translateX(-50%);
  pointer-events: none;
}

/* Handle/resize-grip size and position: `top`/`left` here are laid out
   in .avatar-part's own local (pre-transform) box, so they still track
   the right edge/corner correctly once the parent's `scale()` blows the
   whole thing up — but left un-countered, a 24px icon would balloon up
   by the same factor (144px at the default 6x scale). The
   `scale(calc(1 / var(--avatar-scale)))` term cancels just that part
   back out, keeping the grab target a constant, comfortable size
   regardless of how big the underlying face part is. --avatar-scale is
   set alongside the rest of the transform in applyTransform()
   (draw-cursor.js). Both icons are complete graphics already (own
   circular bg baked in) rather than a bare shape this CSS draws, so
   there's nothing to style here beyond size/position/cursor. */
.avatar-part__handle,
.avatar-part__resize {
  display: none;
  position: absolute;
  width: 24px;
  height: 24px;
  pointer-events: auto;
}

.avatar-part__handle {
  top: -28px;
  left: 50%;
  transform: translate(-50%, -50%) scale(calc(1 / var(--avatar-scale, 1)));
  cursor: grab;
}

.avatar-part__resize {
  top: 100%;
  left: 100%;
  transform: translate(-50%, -50%) scale(calc(1 / var(--avatar-scale, 1)));
  cursor: nwse-resize;
}

.avatar-part.is-selected .avatar-part__handle,
.avatar-part.is-selected .avatar-part__resize {
  display: block;
}

[data-draw] {
  position: relative;
}

/* Solo mientras el pincel está activo (clase puesta por draw-cursor.js):
   el lienzo intercepta el gesto táctil para pintar en vez de scrollear.
   En avatar/otros modos NO se pone, así que la página scrollea normal. */
[data-draw].is-drawing-active {
  touch-action: none;
}

svg.cursor-svg {
  position: absolute;
  z-index: 10;
  top: 0;
  left: 0;
  height: 100%;
  width: 100%;
  pointer-events: none;
  /* The source resource used mix-blend-mode:difference + an orange
     stroke (visible against any background, inverting whatever's under
     it). This brush needs to be a literal black, and difference-blended
     black is a no-op — blends to whatever's already there, invisible —
     so the blend mode has to go along with the color swap below. */
}

svg.cursor-svg line {
  stroke: #000;
  stroke-width: 15;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* ─── Zona de dibujo en móvil ───────────────────────────────
   El panel de avatar (fijo a la izquierda-centro en escritorio) se
   compacta y se ancla arriba para no tapar el lienzo ni salirse de la
   caja; la barra de herramientas se reduce un poco. */
@media (max-width: 900px) {
  .avatar-panel {
    left: 3vw;
    top: 2.5vh;
    transform: none;
    gap: 0.6rem;
    padding: 0.7rem;
    max-height: 42vh;
    overflow: auto;
  }

  .avatar-panel__row {
    gap: 6px;
  }

  .avatar-option__btn {
    width: 40px;
    height: 40px;
  }

  .draw-toolbar__btn {
    width: 42px;
    height: 42px;
    padding: 10px;
  }
}


@media (max-width: 900px) {
  .index__scroller {
    padding: 0 5vw;
  }

  /* Más pequeño y con salto de línea natural: los títulos largos pasan a
     2+ líneas en vez de desbordar (la onda ya usa un offset reducido). */
  .index__title {
    font-size: 6vw;
    line-height: 1.05;
  }

  .index__cat {
    display: none;
  }

  /* El thumbnail SÍ se ve en móvil (en escritorio va fijo a la izquierda):
     fijo abajo-centro, actualizándose con la fila centrada al hacer
     scroll. pointer-events:none ya heredado, no interfiere con la lista. */
  .index__preview {
    left: 50%;
    top: auto;
    bottom: 3vh;
    transform: translateX(-50%);
    width: min(52vw, 260px);
    z-index: 6;
  }
}


/* ==========================================================
   Case study detail page
   ========================================================== */

.case {
  width: 100%;
  min-height: 100vh;
  padding: 0 20vh;
  padding-top: 240px;
  padding-bottom: 10vh;
}

.case__row {
  display: flex;
  align-items: flex-start;
  padding-right: 10vh;
  gap: 10vw;
}

.case__text {
  width: 100%;
  max-width: 560px;
  flex-shrink: 0;
}

.case__title {
  font-size: 60px;
  /* Weight/letter-spacing/color match .index__title exactly on purpose:
     the index -> case title morph ends in an instant swap between the
     flying clone (index metrics) and this element — any typographic
     difference between the two would read as a glitch at the handoff. */
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1;
  font-family: 'General Sans', "Helvetica Neue", Arial, sans-serif;
  /* text-transform: uppercase; */
  color: var(--fg);
  margin-bottom: 1.5rem;
}

.case__info {
  font-family: "General Sans", "Helvetica Neue", Arial, sans-serif;
  font-size: 28px;
  line-height: 1.4;
  color: #cacaca;
}

.case__info h3 {
    font-weight: 500;
    color: var(--accent);
}

.case__info strong {
    font-weight: 500;
    color: var(--accent);
}

.case__info p {
  margin-bottom: 1.2em;
}

.case__info p:last-child {
  margin-bottom: 0;
}

/* Made With GSAP #097 text effect — SplitText output, see initCaseText */
.case__info .line {
  width: max-content;
  max-width: 100%;
}

.case__info .word {
  will-change: transform;
}

/* ==========================================================
   Case cover view — the landing state of every case study
   (data-case-view="cover" on main.case): title + "Leer case study"
   fixed at the left middle, year/category fixed at the right middle,
   the slider centered between them. Clicking the read link morphs into
   data-case-view="reading" (the classic two-column layout above) — see
   expandCaseStudy in script.js, which Flips title/media between the
   two states and slides the info text up from the bottom.
   ========================================================== */

.case__read {
  display: inline-block;
  color: var(--fg-dim);
  text-decoration: none;
  font-size: 24px;
  line-height: 1.2;
  transition: color 0.25s ease;
}

.case__read:hover {
  color: var(--fg);
}

.case[data-case-view="reading"] .case__read {
  display: none;
}

/* Reading-only counterpart of .case__read — collapses back to the
   slider-only cover view (collapseCaseStudy, script.js). */
.case__collapse {
  display: none;
  color: var(--fg-dim);
  text-decoration: none;
  font-size: 1.15rem;
  line-height: 1.2;
  margin-bottom: 1.5rem;
  transition: color 0.25s ease;
}

.case__collapse:hover {
  color: var(--fg);
}

.case[data-case-view="reading"] .case__collapse {
  display: inline-block;
}

/* Fixed overlay in BOTH states (being out of flow, it never disturbs
   the reading flex row) — reading just fades it out. `50vh`, not `50%`:
   while a Barba transition is animating the container, the container's
   transform makes IT the containing block of fixed descendants, and 50%
   would then mean half of the multi-viewport-tall page instead of the
   viewport. 50vh reads the same in both coordinate spaces (the
   transitioning container is pinned to the viewport top). */
.case__meta {
  position: fixed;
  right: var(--edge-pad);
  top: 50vh;
  transform: translateY(-50%);
  text-align: right;
  z-index: 5;
  transition: opacity 0.4s ease, visibility 0s 0.4s;
}

.case[data-case-view="reading"] .case__meta {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.case__year {
  margin: 0 0 0.4rem;
  font-size: 34px;
  font-weight: 400;
  line-height: 1.15;
  color: var(--fg);
}

.case__cat {
  margin: 0;
  font-size: 24px;
  line-height: 1.2;
  color: var(--fg-dim);
}

.case[data-case-view="cover"] {
  /* The pinned 100vh slider box starts right at the page top, so at
     scroll 0 the cascade sits vertically centered in the viewport. */
  padding-top: 0;
}

.case[data-case-view="cover"] .case__row {
  display: block;
  padding-right: 0;
}

.case[data-case-view="cover"] .case__text {
  position: fixed;
  left: var(--edge-pad);
  top: 50vh; /* not 50% — see the .case__meta comment above */
  transform: translateY(-50%);
  z-index: 5;
  max-width: 24vw;
}

.case[data-case-view="cover"] .case__title {
  font-size: 34px;
  line-height: 1.15;
  margin-bottom: 0.6rem;
}

.case[data-case-view="cover"] .case__info {
  display: none;
}

.case[data-case-view="cover"] .case__media {
  width: min(46vw, 680px);
  margin: 0 auto;
}

@media (max-width: 900px) {
  .case__meta {
    display: none;
  }

  /* Cover en móvil = columna única: título (y "Leer case study") en flujo
     normal arriba, media a todo el ancho debajo — nada fijo ni a la
     izquierda. initCoverRelease se salta en móvil (ver script.js), así que
     el título scrollea de forma natural. */
  .case[data-case-view="cover"] {
    padding: 96px 5vw 12vh;
  }

  .case[data-case-view="cover"] .case__text {
    position: static;
    transform: none;
    max-width: none;
    margin-bottom: 2rem;
  }

  .case[data-case-view="cover"] .case__title {
    font-size: 7vw;
    line-height: 1.1;
  }

  .case[data-case-view="cover"] .case__media {
    width: 100%;
  }
}

/* Title clone for the index -> case shared-element morph (see
   startCaseTitleMorph in script.js): a fixed copy of the clicked
   .index__title that rides on top of the page transition, travelling
   into the cover title's spot. Its font-size and width are TWEENED (not
   transform-scaled), so the text re-wraps naturally mid-flight like a
   window resize; by landing it's typographically identical to the real
   title underneath and the swap is an instant, invisible handoff. */
.case-title-morph {
  position: fixed;
  z-index: 70;
  margin: 0;
  pointer-events: none;
  color: var(--fg);
  will-change: transform;
}



/* ==========================================================
   Made With GSAP #060 — cascading pinned slider
   See initCaseSlider in script.js.
   ========================================================== */

.case__media {
  flex: 1;
  min-width: 0;
}

.case__slider-track {
  width: 100%;
  /* Height is set in JS (initCaseSlider) to the full pin scroll distance.
     Plain block, not a flex item: pinning a flex child of `.case__row`
     directly is a known GSAP edge case where the auto pin-spacer doesn't
     reliably reproduce flex-grow sizing, so the document can end up
     shorter than the pin distance and scrolling stalls before the last
     slide. Nesting the pinned box in its own non-flex block sidesteps it
     — mirrors the source demo's `.pin-height` > `.container` structure. */
}

.case__slider-pin {
  width: 100%;
  height: 100vh;
  display: flex;
  justify-content: center;
  padding: 1vh 0;
}

.case__slides {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  gap: 1vh;
}

.case__slide {
  width: 100%;
  flex: 0;
  position: relative;
}

.case__slide-media {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  position: absolute;
  top: 0;
  left: 0;
  /* corner-shape: bevel;
  border-radius: 24px; */
}

@media (max-width: 900px) {
  /* La base usa `padding: 0 20vh` — en móvil eso son ~300px horizontales
     (vh) que aplastan el contenido a una tira central. A ancho de móvil,
     padding cómodo y columna única. */
  .case {
    padding: 96px 5vw 10vh;
  }

  .case__row {
    flex-direction: column;
    gap: 3rem;
    padding-right: 0;
  }

  .case__text {
    max-width: none;
  }

  .case__title {
    font-size: 9vw;
  }

  .case__info {
    font-size: 1.15rem;
  }

  .case__media {
    flex: none;
  }

  .case__slider-pin {
    height: 70dvh;
  }

  /* Next project: más pequeño para que quepa en 1-2 líneas a ancho de
     móvil (las letras van en spans inline-block con física Matter, así que
     el reflujo por palabras/letras es natural — solo hay que reducir el
     cuerpo). */
  .case-next__title {
    font-size: clamp(24px, 8vw, 40px);
    line-height: 1.05;
    margin: 3vh 0;
  }

  .case-next__cover {
    width: 120px;
    height: 120px;
  }
}

/* ==========================================================
   Made With GSAP #089 — next project (title letter-wobble)
   See initNextCase in script.js.
   ========================================================== */

.case-next {
  /* No gap above on purpose: this section entering is what "pushes" the
     case study out — in reading, the sticky info text releases right as
     the row ends; in cover, initCoverRelease (script.js) scrubs the
     fixed title/meta up in lockstep with this section's entrance. */
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  text-decoration: none;
  color: inherit;
  padding: 10vh var(--edge-pad);
}

.case-next__eyebrow {
  font-size: 0.85rem;
  text-transform: uppercase;
  letter-spacing: 0.1em;
  color: var(--fg-dim);
  margin-bottom: 2rem;
}

.case-next__cover {
  width: 150px;
  height: 150px;
  object-fit: cover;
  corner-shape: bevel;
  border-radius: 20px;
  display: block;
}

.case-next__title {
  margin: 4vh 0 4vh;
  font-size: max(32px, 2.5vw);
  font-family: 'General Sans', "Helvetica Neue", Arial, sans-serif;
  line-height: 1;
}

.case-next__letter {
  display: inline-block;
}

.case-next__excerpt {
  width: 100%;
  max-width: 440px;
  font-family: "General Sans", "Helvetica Neue", Arial, sans-serif;
  font-size: 0.95rem;
  line-height: 1.5;
  color: var(--fg-dim);
}

/* ==========================================================
   Osmo Supply — Infinite Draggable Grid ("Shots" page)
   See shots.js. Ported verbatim; only `body{background:#000}` was
   dropped since html/body already gets --bg from the rules above.
   ========================================================== */

.infinite-grid {
  touch-action: none;
  width: 100%;
  height: 100svh;
  position: relative;
  overflow: clip;
  transition: opacity 0.5s ease;
  opacity: 1;
}

.infinite-grid[data-infinite-grid-status="loading"] {
  opacity: 0;
}

/* Native grab/grabbing hand instead of the magnetic circle here — more
   legible as "this is draggable" than a dot would be. Needs !important +
   explicit descendant coverage to win against the global cursor:none
   below (every element gets its own !important cursor:none, so a plain
   inherited `cursor:grab` on the wrapper alone never reaches its
   children). */
@media (hover: hover) and (pointer: fine) {
  .infinite-grid,
  .infinite-grid * {
    cursor: grab !important;
  }

  .infinite-grid[data-infinite-grid-status="dragging"],
  .infinite-grid[data-infinite-grid-status="dragging"] * {
    cursor: grabbing !important;
  }
}

.infinite-grid__collection {
  will-change: transform;
  width: 100%;
  height: 100%;
  position: absolute;
}

.infinite-grid__list {
  grid-template-columns: repeat(var(--grid-columns), auto);
  display: grid;
  position: absolute;
  top: 0;
  left: 0;
}

.infinite-grid__item {
  aspect-ratio: 1;
  justify-content: center;
  align-items: center;
  width: 13em;
  padding: 1.75em;
  font-size: clamp(1.5em, 2vw, 3em);
  display: flex;
}

.infinite-grid__card {
  -webkit-user-select: none;
  user-select: none;
  border-radius: .125em;
  width: 80%;
  height: 100%;
  position: relative;
}

.infinite-grid__card.is--landscape {
  width: 100%;
  height: 75%;
}

.infinite-grid__card-img {
  pointer-events: none;
  object-fit: contain;
  border-radius: inherit;
  width: 100%;
  height: 100%;
  position: absolute;
}


/* ==========================================================
   Osmo Supply — Scramble Text Cursor ("Shots" page)
   Ported verbatim. See initScrambleTextCursor in shots.js. Lives in its
   own `.scramble-cursor` wrapper (not `.cursor`) — shots.html now also
   carries the Magnetic Cursor's `.cursor` circle for its header links
   (see below), so the two need distinct names to coexist on that page.
   ========================================================== */

.scramble-cursor {
  /* Above the reel modal (z-index 50) — without this the custom
     cursor renders BEHIND their backdrop the moment either one opens. */
  z-index: 60;
  pointer-events: none;
  padding-top: 1em;
  padding-left: .5em;
  padding-right: 1em;
  position: fixed;
  top: 0;
  left: 0;
}

.cursor-scramble {
  grid-column-gap: .5em;
  grid-row-gap: .5em;
  opacity: 0;
  color: #fff;
  background-color: #000;
  border-radius: .25em;
  justify-content: center;
  align-items: center;
  height: 2em;
  padding-left: .75em;
  padding-right: .75em;
  display: flex;
  position: relative;
}

.cursor-scramble__text {
  letter-spacing: -.01em;
  white-space: nowrap;
  font-size: .875em;
  font-weight: 500;
}

.cursor-scramble__chevron {
  color: #a1ff62;
  width: .375em;
}

@media (hover: hover) and (pointer: fine) {
  [data-cursor] .cursor-scramble {
    transition: transform 0.4s cubic-bezier(0.625, 0.05, 0, 1), opacity 0.2s ease 0.1s;
    transform: translateX(0%) scale(1) rotate(0.001deg);
  }

  [data-cursor="active"] .cursor-scramble {
    transition: transform 0.4s cubic-bezier(0.625, 0.05, 0, 1), opacity 0.2s ease 0s;
    opacity: 1;
    transform: translateX(0%) scale(1) rotate(0.001deg);
  }

  [data-cursor="active-edge"] .cursor-scramble {
    transition: transform 0.4s cubic-bezier(0.625, 0.05, 0, 1), opacity 0.2s ease 0s;
    opacity: 1;
    transform: translateX(-100%) scale(1) rotate(0.001deg);
  }
}

@media (hover: none) and (pointer: coarse) {
  .scramble-cursor {
    display: none;
  }
}

/* ==========================================================
   "Ampliar" pill variant of the scramble cursor above — the pill brings
   no background of its own: on hover, initZoomCursor (js/case-zoom.js)
   Flip-morphs the magnetic cursor's shared .cursor-bg dot into
   [data-zoom-cursor-bg], the same swallow the header's magnetic
   links do, so the circle visually BECOMES the button. Visibility is
   therefore "is the dot currently inside", not pill opacity — only the
   text fades, slightly delayed so it appears once the bg has mostly
   taken shape.
   ========================================================== */

[data-zoom-cursor] .cursor-scramble {
  background-color: transparent;
  opacity: 1;
}

[data-zoom-cursor] .cursor-scramble__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: .25em;
}

[data-zoom-cursor] .cursor-scramble__text {
  position: relative;
  z-index: 1;
  opacity: 0;
  transition: opacity 0.2s ease;
}

[data-zoom-cursor][data-cursor="active"] .cursor-scramble__text {
  opacity: 1;
  transition-delay: 0.12s;
}

/* While swallowed into the pill: a solid black button surface instead
   of the difference-blended white dot. Flip animates the color/radius
   between the two (props: 'backgroundColor,borderRadius' in
   initZoomCursor); the blend-mode change itself can't tween and
   snaps at reparent time, which reads fine at dot size. */
[data-zoom-cursor] .cursor-bg {
  background-color: #000;
  mix-blend-mode: normal;
}

/* ==========================================================
   Osmo Supply — Magnetic Cursor (every page)
   Ported verbatim. See magnetic-cursor.js. Unscoped from body[data-page]
   now that `.scramble-cursor` above has its own name — this `.cursor`
   circle is the same element/behavior on index, case-study AND shots.
   ========================================================== */

.cursor {
  aspect-ratio: 1;
  border-radius: 100em;
  width: .75em;
  position: fixed;
  inset: 0% auto auto 0%;
  /* Same reasoning as .scramble-cursor's z-index above. */
  z-index: 60;
  pointer-events: none;
  mix-blend-mode: difference;

}

.cursor-bg {
  border-radius: inherit;
  background-color: #fff;
  mix-blend-mode: difference;
  width: 100%;
  height: 100%;
}

.magnetic-link {
  color: inherit;
  padding: .5em .875em;
  text-decoration: none;
  position: relative;
}

.magnetic-link__inner {
  z-index: 1;
  grid-column-gap: .35em;
  grid-row-gap: .35em;
  justify-content: center;
  align-items: center;
  display: flex;
  position: relative;
}

.magnetic-link__icon {
  justify-content: center;
  align-items: center;
  width: .4em;
  margin-bottom: -.125em;
  display: flex;
}

.magnetic-link__bg {
  z-index: 0;
  border-radius: .25em;
  position: absolute;
  inset: 0%;
}

.magnetic-link .cursor-bg {
  opacity: 0.1;
}

/* "Underline": thin bar under the site name / nav links — the only
   hover-bg style the (now single, shared) header uses. */
.underline-link {
  position: relative;
}

.underline-link__bg {
  width: 100%;
  height: 1px;
  border-radius: 0;
  position: absolute;
  left: 0;
  bottom: 0px;
}

.underline-link .cursor-bg {
  opacity: 0.6;
}

@media (hover: none) and (pointer: coarse) {
  .cursor {
    display: none;
  }
}

/* Native pointer is redundant once the circle/scramble follower above is
   on screen — hide it everywhere a real mouse is present (fine pointer),
   leave touch devices alone (they have no cursor to hide anyway, and
   nothing above renders there). `cursor: pointer/grab` etc. declared on
   specific interactive elements elsewhere in this file are more specific
   than this universal selector, hence !important. */
@media (hover: hover) and (pointer: fine) {
  *, *::before, *::after {
    cursor: none !important;
  }
}

/* ==========================================================
   Osmo Supply — Custom Bunny HLS Player (Advanced)
   Ported verbatim. See initBunnyPlayer in reel.js.
   ========================================================== */

.bunny-player {
  pointer-events: none;
  color: #fff;
  isolation: isolate;
  border-radius: 1em;
  justify-content: center;
  align-items: center;
  width: 100%;
  display: flex;
  position: relative;
  overflow: hidden;
  transform: translateX(0);
}

.bunny-player__before {
  padding-top: 62.5%;
}

/* Animation */
[data-bunny-player-init] :is(.bunny-player__placeholder, .bunny-player__dark, .bunny-player__playpause, .bunny-player__loading) {
  transition: opacity 0.3s linear, visibility 0.3s linear;
}

/* Placeholder */
.bunny-player__placeholder {
  object-fit: cover;
  width: 100%;
  height: 100%;
  position: absolute;
}

[data-bunny-player-init][data-player-status="playing"] .bunny-player__placeholder,
[data-bunny-player-init][data-player-status="paused"] .bunny-player__placeholder,
[data-bunny-player-init][data-player-activated="true"][data-player-status="ready"] .bunny-player__placeholder {
  opacity: 0;
  visibility: hidden;
}

/* Dark Overlay */
.bunny-player__dark {
  opacity: .1;
  background-color: #000;
  width: 100%;
  height: 100%;
  position: absolute;
}

[data-bunny-player-init][data-player-status="paused"] .bunny-player__dark,
[data-bunny-player-init][data-player-status="playing"][data-player-hover="active"] .bunny-player__dark{
  opacity: 0.3;
}

[data-bunny-player-init][data-player-status="playing"] .bunny-player__dark {
  opacity: 0;
}

.bunny-player__video {
  width: 100%;
  height: 100%;
  padding-bottom: 0;
  padding-right: 0;
  display: block;
  position: absolute;
  top: 0;
  left: 0;
}

/* Play/Pause */
.bunny-player__playpause {
  pointer-events: auto;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  display: flex;
  position: absolute;
}

[data-bunny-player-init][data-player-status="playing"] .bunny-player__playpause,
[data-bunny-player-init][data-player-status="loading"] .bunny-player__playpause {
  opacity: 0;
}

[data-bunny-player-init][data-player-status="playing"][data-player-hover="active"] .bunny-player__playpause {
  opacity: 1;
}

[data-bunny-player-init][data-player-status="playing"] .bunny-player__play-svg,
[data-bunny-player-init][data-player-status="loading"] .bunny-player__play-svg {
  display: none;
}

[data-bunny-player-init][data-player-status="playing"] .bunny-player__pause-svg,
[data-bunny-player-init][data-player-status="loading"] .bunny-player__pause-svg{
  display: block;
}

/* Loading */
.bunny-player__loading {
  opacity: 0;
  visibility: hidden;
  background-color: #00000054;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;
  display: flex;
  position: absolute;
}

[data-bunny-player-init][data-player-status="loading"] .bunny-player__loading {
  opacity: 1;
  visibility: visible;
}

/* Interface */
.bunny-player__interface {
  flex-flow: column;
  justify-content: flex-end;
  align-items: baseline;
  width: 100%;
  height: 100%;
  display: flex;
  position: absolute;
  transition: all 0.6s cubic-bezier(0.625, 0.05, 0, 1);
}

[data-bunny-player-init][data-player-status="playing"] .bunny-player__interface,
[data-bunny-player-init][data-player-status="loading"] .bunny-player__interface{
  opacity: 0;
  transform: translateY(1em) rotate(0.001deg);
}

[data-bunny-player-init][data-player-status="playing"][data-player-hover="active"] .bunny-player__interface,
[data-bunny-player-init][data-player-status="loading"][data-player-hover="active"] .bunny-player__interface {
  opacity: 1;
  transform: translateY(0em) rotate(0.001deg);
}

.bunny-player__interface-bottom {
  grid-column-gap: 1em;
  grid-row-gap: 1em;
  pointer-events: auto;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  padding: min(2em, 4vw);
  display: flex;
  position: relative;
}

.bunny-player__toggle-mute,
.bunny-player__toggle-fullscreen {
  cursor: pointer;
  width: 1.5em;
  height: 1.5em;
}

.bunny-player__timeline {
  cursor: pointer;
  flex: 1;
  align-items: center;
  height: 1em;
  margin-left: .5em;
  margin-right: .5em;
  display: flex;
  position: relative;
}

[data-bunny-player-init][data-player-status="idle"][data-player-activated="false"] .bunny-player__timeline,
[data-bunny-player-init][data-player-status="ready"][data-player-activated="false"] .bunny-player__timeline {
  pointer-events: none;
}

.bunny-player__timeline-progress {
  pointer-events: none;
  background-color: #ff4c24;
  border-radius: 1em;
  width: 100%;
  height: 100%;
  position: absolute;
  transform: translateX(-100%);
}

.bunny-player__timeline-buffered {
  opacity: .2;
  pointer-events: none;
  background-color: #fff;
  border-radius: 1em;
  width: 100%;
  height: 100%;
  position: absolute;
  transform: translateX(-100%);
}

.bunny-player__timeline-handle {
  transition: transform 0.15s ease-in-out;
  pointer-events: none;
  background-color: #ff4c24;
  border-radius: 1em;
  width: 1em;
  height: 1em;
  position: absolute;
  top: 50%;
  transform: translate(-50%, -50%)scale(0);
}

[data-bunny-player-init][data-timeline-drag="true"] .bunny-player__timeline-handle {
  transform: translate(-50%, -50%) scale(1);
}

.bunny-player__timeline-bar {
  border-radius: 1em;
  width: 100%;
  height: 30%;
  position: absolute;
  overflow: hidden;
}

.bunny-player__time {
  grid-column-gap: .125em;
  grid-row-gap: .125em;
  flex: none;
  justify-content: center;
  align-items: center;
  width: 5.75em;
  display: flex;
}

.bunny-player__timeline-bg {
  background-color: #ffffff26;
  border-radius: 1em;
  width: 100%;
  height: 100%;
  position: absolute;
}

.bunny-player__toggle-playpause {
  cursor: pointer;
  width: 1.5em;
  height: 1.5em;
}

.bunny-player__text {
  white-space: nowrap;
  margin-bottom: 0;
  font-size: .9375em;
  line-height: 1;
}

.bunny-player__big-btn {
  -webkit-backdrop-filter: blur(1em);
  backdrop-filter: blur(1em);
  cursor: pointer;
  background-color: #64646433;
  border: 1px solid #ffffff1a;
  border-radius: 50%;
  justify-content: center;
  align-items: center;
  width: 6em;
  height: 6em;
  padding: 2em;
  display: flex;
  position: relative;
}

.bunny-player__loading-svg {
  width: 6em;
}

.bunny-player__pause-svg {
  display: none;
}

.bunny-player__interface-fade {
  opacity: .5;
  background-image: linear-gradient(#0000, #000);
  width: 100%;
  height: 25%;
  position: absolute;
  bottom: 0;
}

.bunny-player__interface-btns {
  grid-column-gap: .5em;
  grid-row-gap: .5em;
  align-items: center;
  display: flex;
}

[data-bunny-player-init][data-player-muted="true"] .bunny-player__volume-mute-svg {
  display: block;
}

[data-bunny-player-init][data-player-muted="true"] .bunny-player__volume-up-svg {
  display: none;
}

.bunny-player__volume-mute-svg {
  display: none;
}

.bunny-player__volume-up-svg {
  display: block;
}

.bunny-player__fullscreen-shrink-svg {
  display: none;
}

.bunny-player__fullscreen-scale-svg {
  display: block;
}

[data-bunny-player-init][data-player-fullscreen="true"] .bunny-player__fullscreen-shrink-svg {
  display: block;
}

[data-bunny-player-init][data-player-fullscreen="true"] .bunny-player__fullscreen-scale-svg {
  display: none;
}

/* Cover Mode */
[data-bunny-player-init][data-player-update-size="cover"] {
  height: 100%;
  top: 0;
  left: 0;
  position: absolute;
}

[data-bunny-player-init][data-player-update-size="cover"] [data-player-before] {
  display: none;
}

[data-bunny-player-init][data-player-update-size="cover"][data-player-fullscreen="false"] .bunny-player__video {
  object-fit: cover;
}

/* ==========================================================
   Reel modal (site-wide)
   Our own wrapper around the Bunny player above — opens from the
   "Play Reel" header link. See reel.js.
   ========================================================== */

.reel-modal {
  visibility: hidden;
  opacity: 0;
  z-index: 50;
  position: fixed;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 4vw;
  transition: opacity 0.35s ease, visibility 0.35s ease;
}

.reel-modal[data-reel-modal-open="true"] {
  visibility: visible;
  opacity: 1;
}

.reel-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.85);
}

.reel-modal__inner {
  width: 100%;
  max-width: 1100px;
  position: relative;
}

.reel-modal__close {
  position: absolute;
  top: -2.5em;
  right: 0;
  color: var(--fg);
  background: none;
  border: none;
  font-size: 1rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  cursor: pointer;
  padding: 0.5em;
}

/* ==========================================================
   Case-study click-to-zoom ("Ampliar") — replaces the old gallery
   modal (recoverable in git). See initCaseZoom in js/case-zoom.js: the
   lightbox <div> is created from JS (it must live outside the Barba
   container on every entry page), the clicked slide's media flies into
   a centered box via a left/top/width/height tween, and this block only
   styles the two pieces involved. `padding` here caps how large the
   zoom can grow, exactly like the source Osmo resource.
   ========================================================== */

.case-zoom__lightbox {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: none;
  background-color: #000000e6;
  padding: 3em;
}

.case-zoom__media {
  position: absolute;
  display: block;
  object-fit: cover;
  border-radius: 0.75em;
  background: #000;
  user-select: none;
  -webkit-user-drag: none;
  max-width: none;
  max-height: none;
  overflow: hidden;
}

/* Zoomed videos are OUR Bunny player (see BUNNY_ZOOM_TEMPLATE in
   js/case-zoom.js) filling the tweened box in cover mode — its own 1em
   radius would poke past the box's 0.75em corners, so it inherits. */
.case-zoom__media .bunny-player {
  border-radius: inherit;
}

/* Native zoom-out cursor while open — the attribute+class selector beats
   the universal `cursor: none !important` (same trick as the expanded
   clock above). Inside a zoomed video the pointer belongs to the Bunny
   player's controls (the circle follower is hidden here), so that
   subtree reads as clickable instead. */
@media (hover: hover) and (pointer: fine) {
  .case-zoom__lightbox.is-open,
  .case-zoom__lightbox.is-open * {
    cursor: zoom-out !important;
  }

  .case-zoom__lightbox.is-open .bunny-player,
  .case-zoom__lightbox.is-open .bunny-player * {
    cursor: pointer !important;
  }
}

