@import url('https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,400;0,500;0,700;1,400&display=swap');

:root{
  --bg: #F3F3F3;
  --ink: #121212;
  --ink-secondary: #6E6E6E;
  --border: #DDDDDD;
  --tile-gap: 48px;
  --row-gap: 64px;
  --nav-h: 60px;
  --sidebar-w: 360px;
  --font-heading: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-body: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif;
  --font-caption: 'Roboto', -apple-system, BlinkMacSystemFont, sans-serif;
  --ease-soft: cubic-bezier(.22,.68,.24,1);

  /* Slide-up de entrada (.reveal). Los cuatro valores juntos aca porque se
     ajustan como un set: subir la duracion sin subir el recorrido hace que la
     pieza flote en el lugar, y al reves queda apurada.
     2026-08-27, pedido del usuario ("mas suave en general, mas despacio"):
     fade 550->900ms, movimiento 800->1300ms, recorrido 20->28px, escalonado
     70->110ms (REVEAL_STAGGER_MS en script.js).
     El ease tambien cambio, y es la mitad del asunto: venia de un expo-out
     (.16,1,.3,1), que gasta casi todo el recorrido en los primeros 150ms y
     despues arrastra una cola larguisima. Estirarle la duracion a eso no lo
     hace mas lento, lo hace mas pegajoso —la pieza llega enseguida y despues
     se queda quieta pareciendo trabada—. Un cubic-out reparte el movimiento a
     lo largo de toda la duracion, que es lo que se lee como "despacio". */
  --reveal-ease: cubic-bezier(.33,1,.68,1);
  --reveal-shift: 28px;
  --reveal-fade: 900ms;
  --reveal-move: 1300ms;
}

*{ box-sizing: border-box; }
/* El swipe horizontal de dos dedos en Mac dispara el "volver atras" del
   navegador. En desktop ese gesto lo usamos para cambiar de seccion (ver
   initDesktopSwipe en script.js), asi que se apaga el overscroll horizontal
   ademas del preventDefault del handler. */
html, body{ overscroll-behavior-x: none; }
[hidden]{ display:none !important; }

html, body{
  margin:0;
  padding:0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  -webkit-font-smoothing: antialiased;
}

a{ color: inherit; text-decoration: none; }
ul{ margin:0; padding:0; list-style:none; }
h1,h2,h3{ font-family: var(--font-heading); font-weight:500; margin:0; }
p{ margin:0; }
img, video{ display:block; width:100%; height:100%; object-fit:cover; }
button{ font-family: inherit; }

/* Placeholder for real photos/videos, not added yet (see script.js coverTag/slideTag) */
.media-placeholder{ width:100%; height:100%; background: var(--border); }

/* ---------- Top nav (pill-style, shared mobile + desktop) ---------- */
.site-nav{
  position: sticky;
  top: 0;
  z-index: 20;
  display:flex;
  background: var(--bg);
  transition: background .3s var(--ease-soft), backdrop-filter .3s var(--ease-soft);
}
.site-nav.is-scrolled{
  background: rgba(243, 243, 243, 0.72); /* fallback: --bg al 72% */
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
}
/* Full-width segmented tab bar: equal-width segments separated by hairline
   dividers, no logo/site-name — the active segment gets a solid pill.
   Height is a literal, content-driven value (26px = Pencil's Nav Mobile
   Master, kEPui) — NOT var(--nav-h): that variable is *derived* from this
   element's rendered height via setNavHeight() in script.js, so having
   height depend on it here was circular and left the bar stuck at the
   60px root fallback instead of Pencil's tight 26px. */
.site-nav .nav-links{
  display:flex;
  align-items:center;
  width:100%;
  height: 26px;
}
.nav-divider{ flex:none; width:1px; height:14px; background:#C3C3C3; } /* 16px at desktop, see media query */
.nav-segment{
  position:relative;
  flex:1 1 0;
  min-width:0;
  height:100%;
  display:flex;
  align-items:center;
  justify-content:center;
}
.nav-pill-wrap{ display:flex; align-items:center; justify-content:center; width:100%; height:100%; padding:2px 4px; } /* 3px 4px at desktop */
/* Pencil's Pill fills its Pill Wrap (flex:1) rather than hugging the label
   text — in the kEPui screenshot the active "HOME" pill reaches almost to
   the dividers on both sides, not just wrapping "HOME" with small padding.
   width:100% (not content-fit) matches that, for any segment label. */
.nav-pill{
  display:flex;
  align-items:center;
  justify-content:center;
  width:100%;
  height:22px;
  border-radius: 4px;
  background: transparent;
  transition: background .2s var(--ease-soft);
}
.nav-label{
  font-family: var(--font-body);
  font-size: 14px;
  color: var(--ink);
  transition: color .2s var(--ease-soft);
}
.nav-segment:hover .nav-pill{ background: rgba(18, 18, 18, 0.08); background: color-mix(in srgb, var(--ink) 8%, transparent); }
.nav-segment.active .nav-pill{ background: var(--ink); }
.nav-segment.active .nav-label{ color: var(--bg); }
.nav-segment:hover.active .nav-pill{ background: var(--ink); }

/* ---------- Layout: sidebar (desktop only) + main ---------- */
.layout{ display:flex; align-items:flex-start; }
.sidebar{ display:none; }
.work-desktop-grid{ display:none; }
.info-desktop{ display:none; }
.sideb-desktop{ display:none; }

/* ---------- Main / pages ---------- */
.main{ position: relative; flex:1; min-width:0; }
.pages-track{ display:block; }
.page{ display:none; }
.page.is-active{ display:block; }
.page-scroll{ display:block; }

/* ---------- Hero (home) ---------- */
.hero{ padding: 6px 6px 0; }
.home-intro{ font-family: var(--font-body); font-size: 16px; line-height:1.35; color: var(--ink); padding: 6px 0 6px; max-width: 420px; }
.hero-media{
  display:block;
  position:relative;
  overflow:hidden;
  border-radius: 8px;
  aspect-ratio: 1.575;
  background: var(--border);
}
.hero-media-cover{ width:100%; height:100%; }
.hero-media-cover img, .hero-media-cover video{ transition: transform .6s var(--ease-soft); }
.hero-media:hover .hero-media-cover img,
.hero-media:hover .hero-media-cover video{ transform: scale(1.03); }
.play-badge{
  position:absolute;
  top:50%; left:50%;
  transform:translate(-50%,-50%);
  width: 44px; height:44px;
  border-radius:50%;
  background: rgba(18,18,18,0.65);
  color: var(--bg);
  display:flex;
  align-items:center;
  justify-content:center;
}
.tile:hover .play-badge,
.hero-media:hover .play-badge{ transform:translate(-50%,-50%) scale(1.08); }
/* Grid-tile play badges (Home/Work/Side B) are smaller (40px, 16px icon)
   than the 44px ones used on hero/gallery media (18px icon, set per-context
   below near those rules). */
.tile .play-badge{ width:40px; height:40px; }

/* ---------- Home page: two independent tile columns (matches Pencil) ---------- */
.home-tile-columns{ display:flex; gap:4px; padding: 0 6px 16px; }
.home-tile-column{ flex:1; min-width:0; display:flex; flex-direction:column; gap:4px; }
.home-tile-column .tile{ gap:6px; }
.home-tile-column .tile-cover{ aspect-ratio: unset; }
.home-tile-column:nth-child(1) .tile:nth-child(odd) .tile-cover{ aspect-ratio: 187/197; }
.home-tile-column:nth-child(1) .tile:nth-child(even) .tile-cover{ aspect-ratio: 187/177; }
.home-tile-column:nth-child(2) .tile:nth-child(odd) .tile-cover{ aspect-ratio: 187/177; }
.home-tile-column:nth-child(2) .tile:nth-child(even) .tile-cover{ aspect-ratio: 187/197; }

/* NOTA (2026-08-22): aca vivia una regla hardcodeada
   `#grid-home-left .tile[data-slug="the-movement"] .tile-cover{ aspect-ratio: 886/1920; }`
   con la proporcion del video-04, que era el homeCover de the-movement. Al
   re-sincronizar el proyecto el homeCover paso a celu.mp4 (956x1966) pero la
   regla seguia forzando la caja vieja, asi que el tile del Home mostraba el
   cover nuevo recortado a la proporcion del anterior. Se elimino: la regla
   generica de mas abajo (.tile-cover:has(.media-real) -> aspect-ratio: unset)
   ya le da a CUALQUIER proyecto con media real su proporcion nativa, que es
   justo lo que el usuario eligio sobre las reglas por slug. No volver a
   hardcodear una proporcion por proyecto. */

/* Home tile covers at their media's own proportion (2026-08-22, user
   request: "el cover del home... deberia tener el alto del video, no el
   que esta ahora"). Same pattern already used for Work Mobile tiles and
   for the project hero (see #grid-work and .project-hero-media below):
   the fixed 187/197 - 187/177 aspect-ratios above are Pencil box sizes,
   and the global img/video object-fit:cover was force-cropping real
   covers to them — La Calle Bar's and Ceremonia's Home covers are both
   gallery[0], a 1180x2556 vertical video, so they were being cut down to
   a near-square box.
   Generic on purpose (user chose this over per-slug rules): ANY project in
   REAL_MEDIA_PROJECTS gets its natural proportion here, so afends picks it
   up too and future projects need no new CSS. Placeholder tiles (no
   .media-real child) don't match :has() and keep the fixed box untouched.
   ID selectors are needed to outrank the .home-tile-column:nth-child()
   rules above. the-movement tenia una regla propia mas arriba que le ganaba
   a esta; se elimino el 2026-08-22 (ver la nota) y ahora cae aca como todos. */
#grid-home-left .tile-cover:has(.media-real),
#grid-home-right .tile-cover:has(.media-real),
#grid-home-third .tile-cover:has(.media-real){ aspect-ratio: unset; }
#grid-home-left .tile-cover .media-real,
#grid-home-right .tile-cover .media-real,
#grid-home-third .tile-cover .media-real{ height:auto; object-fit:unset; }

/* ---------- Grid (tiles with mini-carousel) ---------- */
/* Work Mobile (z0NKkz): single-column, full-width fantasy-caption cards —
   not the old 2-col numbered/carousel grid. Desktop still uses the old 2-col
   grid as an interim placeholder (see @media 861px override below) until
   Work Desktop (Ghq1t/uDnON — sidebar + 3-col masonry) gets built. */
.grid{
  display:flex;
  flex-direction:column;
  gap: 6px;
  padding: 16px 6px;
}
.tile{
  display:flex;
  flex-direction:column;
  gap: 6px;
  cursor:pointer;
}
.tile.static{ cursor:default; }
.tile .tile-cover{
  position:relative;
  overflow:hidden;
  border-radius: 8px;
  background: var(--border);
  aspect-ratio: 378/177;
}
.tile .tile-cover img,
.tile .tile-cover video{ transition: transform .5s var(--ease-soft); }
.tile:hover .tile-cover img,
.tile:hover .tile-cover video{ transform: scale(1.03); }

/* Work Mobile real-photo tiles (2026-08-12, user request): the fixed
   378/177 aspect-ratio above is a Pencil-design leftover box size, not the
   real loaded photo's proportions — it was force-cropping cover photos
   (e.g. the-movement's 2000x1500 hero) via the global img/video
   object-fit:cover rule. Real media (.media-real, see realMediaTag() in
   script.js) now sizes to its own natural aspect ratio instead; placeholder
   tiles (no .media-real child) keep the fixed box untouched. */
#grid-work .tile-cover:has(.media-real){ aspect-ratio: unset; }
#grid-work .tile-cover .media-real{ height:auto; object-fit:unset; }
.tile .tile-caption{ display:flex; justify-content:space-between; align-items:center; }
.tile h3{ font-family: var(--font-heading); font-size: 13px; font-weight:500; color: var(--ink); }
.tile p{ font-family: var(--font-body); font-size: 10px; color: var(--ink-secondary); }

/* Fantasy-copy caption (Home tiles): one flowing sentence, bold title
   inline with the description — same pattern/copy Pencil currently shows
   on every tile ("Vans" / "Serie de..."), not per-item real content, and
   shared with .project-related-caption in the Project detail view.
   Selectors are scoped with ".tile" to out-specificity the legacy
   ".tile .tile-caption" / ".tile p" rules above (equal-or-higher
   specificity is required — a bare class here would silently lose). */
.tile .tile-caption--fantasy{ display:block; }
/* Las tarjetas de proyecto de la columna izquierda de Info no son .tile, asi
   que se quedaban afuera de estas tres reglas y su caption caia al default del
   navegador: line-height normal en vez de 22px y todo en peso 400, sin la
   diferencia entre la marca y la descripcion. Se agrega su selector en vez de
   sacar el .tile, para no tocar de rebote ningun otro caption del sitio
   (2026-08-25, el usuario: "los textos de cada proyecto tienen que respetar el
   formato que tienen en el home"). */
.tile p.tile-caption-text,
.page-sidebar-project p.tile-caption-text{ margin:0; font-family: var(--font-body); font-size:16px; line-height:22px; color: var(--ink); }
/* ...pero en el sidebar va al tamanio del sidebar, 14px, que es el del bloque
   About y el que usa el resto de esa columna. A 16px cantaba al lado del texto
   de arriba (2026-08-25, el usuario: "en INFO la columna de la izquierda parece
   que tiene texto mas grande"). El peso y el color se mantienen como en el
   Home: eso es lo que hace que se lean igual. */
.page-sidebar-project p.tile-caption-text{ font-size:14px; line-height:19px; }
.tile .tile-caption-title,
.page-sidebar-project .tile-caption-title{ font-weight:500; color: var(--ink); }
.tile .tile-caption-desc,
.page-sidebar-project .tile-caption-desc{ font-weight:300; color: #121212B3; color: color-mix(in srgb, var(--ink) 70%, transparent); }

/* Logos tile: text-only, no photo */
.tile.logos-tile .tile-cover{
  display:flex;
  align-items:center;
  justify-content:center;
  padding: 16px;
  background: var(--bg);
  border: 1px solid var(--border);
}
.logos-tile-grid{ display:grid; grid-template-columns: 1fr 1fr; gap: 8px; width:100%; }
.logos-tile-grid span{
  font-family: var(--font-body);
  font-size: 10.5px;
  color: var(--ink-secondary);
  border: 1px solid var(--border);
  padding: 8px 6px;
  text-align:center;
  overflow:hidden;
  white-space:nowrap;
  text-overflow:ellipsis;
}

/* ---------- Carousels (shared by tile covers + project detail) ---------- */
.tile-carousel-track,
.project-carousel-track{ display:flex; width:100%; height:100%; }
.tile-slide,
.project-slide{ position:relative; flex: 0 0 100%; width:100%; height:100%; }
.carousel-nav{
  position:absolute;
  z-index:2;
  border:none;
  border-radius:50%;
  background: var(--ink);
  color: var(--bg);
  display:flex;
  align-items:center;
  justify-content:center;
  cursor:pointer;
  padding:0;
  line-height:1;
  transition: opacity .25s var(--ease-soft), transform .2s var(--ease-soft);
}
.carousel-nav:hover{ transform: scale(1.06); }
/* Invisible hit-area expansion: keeps the visual pill compact while giving
   touch users a target closer to the ~44px minimum recommended tap size. */
.carousel-nav::before{ content:""; position:absolute; inset:-9px; }
.carousel-nav:disabled{ opacity:0; pointer-events:none; }
.carousel-nav svg{ width: 45%; height: 45%; }

.tile .carousel-nav{ width:22px; height:22px; bottom: 10px; opacity:0; }
.tile .carousel-nav.prev{ right: 36px; }
.tile .carousel-nav.next{ right: 10px; }
.tile:hover .carousel-nav{ opacity:1; }
@media (hover:none){ .tile .carousel-nav{ opacity:1; } }

.carousel-dots{
  position:absolute;
  bottom: 14px;
  left:50%;
  transform:translateX(-50%);
  z-index:2;
  display:flex;
  gap:5px;
}
.carousel-dots .dot{
  position:relative;
  width:5px; height:5px;
  border-radius:50%;
  background: #FFFFFF88;
  cursor:pointer;
  transition: background .25s var(--ease-soft);
}
.carousel-dots .dot::before{ content:""; position:absolute; inset:-10px; }
.carousel-dots .dot.is-active{ background:#F5F5F5; }

/* ---------- Info page ---------- */
.info-content{ display:flex; flex-direction:column; gap: 0; padding: 6px 6px 48px; }
/* Hero Media Wrap (NPQR1, mobile only — Pencil's Info Desktop, X8g8f, has a
   different multi-photo layout not built yet). Negative margins cancel
   .info-content's own padding so this sits full-bleed like Pencil (6px
   padding of its own, not nested inside the container's own 6px — both
   happen to be 6px now, but the trick still stands since it's two
   independent paddings that shouldn't stack). No bottom margin — Pencil's
   bio text starts flush against the hero (see notas.md 2026-08-11). */
.info-hero-wrap{ margin: -6px -6px 0; padding: 6px; width: calc(100% + 12px); }
.info-hero-media{ position:relative; height:220px; border-radius:8px; overflow:hidden; background: var(--border); }
.info-hero-media .play-badge{ background: var(--ink); }
.info-hero-media .play-badge svg{ width:18px; height:18px; }
.info-left{ display:flex; flex-direction:column; }
.info-right{ display:flex; flex-direction:column; padding-top:16px; border-top:1px solid var(--border); margin-top:16px; }
.info-bio{ font-family: var(--font-body); font-size: 14px; line-height:1.5; color: var(--ink); }
.info-bio + .info-bio{ margin-top:12px; }

.info-divider{ height:1px; background: rgba(0,0,0,0.15); margin: 16px 0; }

/* Indented row: a fixed-width spacer column + content, matches Pencil's
   "Spacer Col" + "Content Col" pattern — used only for the Contact Row.
   The Q&A sections list (.info-sections) is NOT indented in Pencil, it
   just inherits .info-content's own ambient padding. */
.info-indent-row{ display:flex; gap:24px; }
.info-spacer{ flex:0 0 110px; }
.info-indent-col{ flex:1; min-width:0; display:flex; flex-direction:column; gap:32px; }

.info-sections{ display:flex; flex-direction:column; gap:16px; }
/* Inside .info-sections the flex gap alone provides the spacing around each
   divider (matches Pencil's Content Col, which gaps every child uniformly,
   dividers included) — the standalone .info-divider margin below is only
   for the one divider between the bio and this Q&A block, a plain sibling
   in a gap:0 container that needs its own margin to space itself out. */
.info-sections .info-divider{ margin: 0; }
.info-section{ display:flex; flex-direction:column; gap:4px; }
.info-section-heading{ font-family: var(--font-body); font-size: 16px; font-weight:700; color: var(--ink); margin:0; }
.info-section-body{ font-family: var(--font-body); font-size: 14px; line-height:1.4; color: #626262; margin:0; }

/* Two side-by-side column groups (4 + 3 categories), matches Pencil's
   "Grid Column A / B" split — not a single stacked column. */
/* Info Mobile - grilla de categorias.
   Hasta el 2026-08-23 eran dos pilas verticales independientes (5 + 3), asi que
   los items de la izquierda y los de la derecha nunca quedaban alineados entre
   si y sus bordes superiores se iban corriendo. Ahora son filas reales de dos
   columnas: cada par (Areas/Tools, Courses/Selected Clients, Operating/Awards,
   Core Principles/Media Coverage) comparte fila, queda alineado, y sobre todo
   es UNA sola unidad para initReveal() - que es lo que hace que el par entre
   en paralelo y no desfasado. Ver infoColumnsHTML() en script.js.
   .info-column-group quedo sin uso y se saco junto con el cambio. */
.info-columns{ display:flex; flex-direction:column; gap:16px; }
.info-column-row{ display:grid; grid-template-columns:1fr 1fr; gap:24px; align-items:start; }
.info-column{ display:flex; flex-direction:column; gap:8px; padding-top:16px; border-top:1px solid var(--border); }
.info-column-row:first-child .info-column{ padding-top:0; border-top:none; }
.info-column-label{ font-family: var(--font-body); font-size: 14px; font-weight:700; color: #000000; margin:0; }
.info-column-values{ font-family: var(--font-body); font-size: 14px; line-height:1.4; color: #121212B3; }

/* Cierre de Info Mobile. Hasta el 2026-08-23 el CTA iba centrado y los datos
   de contacto colgaban de un spacer de 110px, asi que ninguno de los dos
   coincidia con el borde de las columnas de arriba y el bloque entero se veia
   corrido. Ahora el CTA arranca a ras del contenido y los datos van pegados al
   otro margen: dos anclas limpias en vez de tres alineaciones distintas. */
.info-cta{ font-family: var(--font-body); font-size: 16px; line-height:1.4; color: #626262; margin:0; padding: 32px 0; text-align:left; }
.info-contact-col{ gap:4px !important; text-align:right; }
/* La frase de la consulta dentro del CTA. Va en negrita y en el color del
   texto normal (el resto del CTA es gris #626262): una negrita en gris medio
   queda apagada y no destaca nada, con el ink si lee. Cuando se prenda el
   link de Calendly hereda el mismo peso y color y solo suma el subrayado, asi
   que la frase no se mueve ni cambia de aspecto al pasar de una etapa a otra. */
.info-cta-strong,
.info-cta-link{ font-weight:700; color: var(--ink); }
.info-cta-link{ text-decoration: underline; text-underline-offset: 3px; }
.info-contact-col div{ font-family: var(--font-body); font-size: 14px; color: var(--ink); }

/* ---------- Side B page ---------- */
/* The standalone "Side B" title (h1) was removed in Pencil 2026-08-11 —
   Title Wrap now has a single child, the intro text, so its own padding
   is applied straight to .sideb-intro instead of a separate title block. */
.sideb-content{ padding: 0; }
.sideb-title-wrap{ padding: 16px 6px 0; }
.sideb-intro{ font-family: var(--font-body); font-size: 16px; line-height:1.4; color: var(--ink); margin:0; }
.sideb-grid{ display:grid; grid-template-columns: repeat(2,1fr); gap: 6px 4px; padding: 12px 6px; }
.sideb-grid .tile{ gap:0; }
.sideb-grid .tile-cover{ aspect-ratio: 1; }
.sideb-grid .tile-caption{ display:none; }
/* New in Pencil 2026-08-11: a hairline divider between the grid and the
   shared footer, full-bleed (no margin — sits flush on both sides). */
/* Side B mobile: galeria vertical, no la grilla de 2 columnas (2026-08-23,
   el usuario cargo su propio material con la convencion de un proyecto).
   El contenedor lleva tambien la clase .project-gallery, asi hereda el gap,
   el padding lateral de 6px y el "sin recorte" (height:auto + object-fit:
   unset) sin duplicar nada. Las .sideb-grid de arriba quedaron sin uso. */
.sideb-gallery{ padding-top: 12px; }
.sideb-footer-divider{ height:1px; background:#C3C3C3; }

/* ---------- Project detail view ----------
   Matches Pencil's Project Mobile frame (K9YKC): Hero Media, Title +
   "Overview" header row, Meta Col + body-text row, a stacked gallery (no
   carousel — Pencil shows plain stacked media, not a swipeable widget),
   a Related Work grid, and the shared footer. No Desktop frame exists for
   this page yet, so the ≥861px rules below are a light responsive pass,
   not a Pencil-verified layout. */
/* Work page: catalog vs. open project. #project-view lives inside
   #page-work's .page-scroll (see index.html) so it swipes/paginates with
   the rest of the track like a normal page instead of a full-screen
   overlay that blocked the swipe gesture. #page-work.is-project-open
   (toggled in script.js) hides the catalog + its footer and shows this. */
.project-view{ display:none; padding: 0; }
#page-work.is-project-open #grid-work,
#page-work.is-project-open #grid-work-desktop,
#page-work.is-project-open > .page-scroll > [data-footer]{ display:none; }
#page-work.is-project-open .project-view{ display:block; }
.project-hero-wrap{ padding: 6px 6px 0; }
.project-hero-media{ position:relative; height:220px; border-radius:8px; overflow:hidden; background: var(--border); }
.project-hero-media .play-badge{ background: var(--ink); }
.project-hero-media .play-badge svg{ width:18px; height:18px; }

/* Project hero photo (2026-08-12, user request, same fix as the Work Mobile
   tile-cover above): the fixed 220px height + global object-fit:cover was
   cropping the real hero photo top/bottom instead of showing it at its own
   proportions. Real media sizes to its natural aspect ratio; placeholder
   projects (no .media-real) keep the fixed 220px box. */
.project-hero-media:has(.media-real){ height:auto; }
.project-hero-media .media-real{ height:auto; object-fit:unset; }

.project-info{ display:flex; flex-direction:column; gap:6px; padding: 6px 6px; }
.project-info-header{ display:flex; gap:24px; align-items:flex-start; }
.project-title{ flex:0 0 140px; width:140px; font-size:16px; font-weight:700; color: var(--ink); }
.project-overview-label{ flex:1; min-width:0; padding-top:9px; font-family: var(--font-body); font-size:12px; font-weight:700; color: var(--ink); }
.project-info-body{ display:flex; gap:24px; width:100%; }
.project-meta-col{ flex:0 0 140px; width:140px; display:flex; flex-direction:column; gap:4px; }
.project-meta-line{ font-family: var(--font-body); font-size:12px; font-weight:500; color:#525252; }
.project-overview-text{ flex:1; min-width:0; font-family: var(--font-body); font-size:14px; line-height:1.45; color:#626262; }
.project-overview-text p{ margin:0 0 10px; }
.project-overview-text p:last-of-type{ margin-bottom:0; }
.project-link{
  display: inline-block;
  margin-top: 6px;
  font-family: var(--font-body);
  font-size: 12px;
  color: var(--ink);
  border-bottom: 1px solid var(--ink-secondary);
}
.project-link:hover{ border-color: transparent; color: var(--ink-secondary); }

.project-gallery{ display:flex; flex-direction:column; gap:6px; padding: 0 6px 16px; }
/* height:auto + object-fit:unset (2026-08-11, user request): the old fixed
   height:220px + the global img/video object-fit:cover rule was cropping
   every photo top/bottom to force it into that box — photos should show at
   their own optimized aspect ratio instead, full width, no crop. */
.project-gallery-item{ position:relative; overflow:hidden; background: var(--border); border-radius:4px; }
.project-gallery-item img, .project-gallery-item video{ height:auto; object-fit:unset; }
/* Realce por pieza: una foto/video puntual puede entrar mas angosta que el
   resto de la galeria. El valor viene de `mediaAccents` en data.js via la
   custom property --inset, no hardcodeado por proyecto (2026-08-23, vans:
   "la foto del hero y la 35 ... deberian tener 50px de margen de cada lado").
   --inset es el margen TOTAL contra el borde de la pantalla, por eso se le
   restan los 6px de padding lateral que ya ponen .project-gallery y
   .project-hero-wrap: asi el numero de data.js es el que se mide en pantalla.
   Se toca el margen y no el ancho para que la foto siga a su proporcion real
   (height:auto + object-fit:unset, ver la regla de arriba). */
.project-gallery-item.is-inset,
.project-hero-media.is-inset{ margin-inline: calc(var(--inset, 6px) - 6px); }
.project-gallery-divider{ height:1px; background:#C3C3C3; }

/* Photo carrusel groups (see galleryItemHTML()/marqueeHTML() in script.js):
   images sit edge to edge (no gap) at a fixed height, natural aspect
   ratio (no crop), auto-scrolling right-to-left in an infinite loop. The
   track is the group rendered twice back to back, animated exactly -50%
   so the loop point is seamless. */
.project-marquee{ height:220px; overflow:hidden; }
/* Alto propio del grupo (campo `height` en data.js). Va por clase y no por
   style inline: un inline le ganaria a la regla de desktop de mas abajo y un
   grupo con los dos altos se quedaria siempre con el de mobile. */
.project-marquee--h{ height: var(--marquee-h); }
.project-marquee-track{
  display:flex;
  height:100%;
  width:max-content;
  animation: project-marquee-scroll 22s linear infinite;
}
.project-marquee-item{ flex:0 0 auto; height:100%; }
.project-marquee-item img{ width:auto; height:100%; object-fit:unset; }
@keyframes project-marquee-scroll{
  from{ transform:translateX(0); }
  to{ transform:translateX(-50%); }
}

/* "Slide-cut" group (see galleryItemHTML()/slideshowHTML() in script.js,
   data.js comment on PROJECTS[].gallery): one photo at a time, hard cut to
   the next (no transition — that's the point, see user request 2026-08-12),
   infinite loop, no nav/dots. Two height modes, picked per group via the
   optional `height` field on the {type:"slideshow", items, height} entry
   in data.js (2026-08-12, Ceremonia): */
.project-slideshow{ position:relative; border-radius:4px; }
/* Default (no `height` given) — display:none/block (not absolute+opacity/
   height:220px, see 2026-08-12 revision) so the visible slide's own
   natural size drives the box height — full photo, full width, no crop,
   no fixed "mockup rectangle" box (the 220px height was the old Pencil
   placeholder box size; the user specifically didn't want the real photo
   confined to it). This is what afends' slideshows use — each slide's own
   aspect ratio, box height changes on every cut. */
.project-slideshow-slide{ display:none; }
.project-slideshow-slide.is-active{ display:block; }
.project-slideshow-slide img, .project-slideshow-slide video{ height:auto; object-fit:unset; }
/* Fixed-height mode (`height` given, e.g. Ceremonia's slide-cut-4 group,
   2026-08-12) — .project-slideshow gets an inline `style="height:Npx"`
   from slideshowHTML() and the `.is-fixed-height` modifier class, which
   makes every slide (and its img/video) fill that exact box via
   object-fit:cover so the group reads as one uniform box across all
   photos instead of jumping size on every cut, at the cost of cropping
   photos whose aspect ratio doesn't match the box. */
.project-slideshow.is-fixed-height .project-slideshow-slide{ height:100%; }
.project-slideshow.is-fixed-height .project-slideshow-slide img,
.project-slideshow.is-fixed-height .project-slideshow-slide video{ width:100%; height:100%; object-fit:cover; }
/* prefers-reduced-motion opt-out removed 2026-08-11 (user request) — the
   scroll is the content presentation here (a filmstrip), not a decorative
   flourish, and the user wants it moving unconditionally on every device.
   Most likely explanation for "looks fine but doesn't move" on the phone:
   iOS Reduce Motion was on, which this media query used to honor. */

.project-related{ display:flex; flex-direction:column; gap:20px; padding: 16px 6px 24px; }
.project-related-row{ display:flex; gap:4px; }
.project-related-item{ display:block; flex:1 1 0; min-width:0; }
/* Un tile solo en la ultima fila (cuando la cantidad de proyectos es impar)
   se estiraba a todo el ancho por el flex:1 de arriba — se veia al doble de
   tamano que el resto (2026-08-22, user: "tiene que aparecer a 1 columna
   como el resto, ahora aparece a 2"). Queda a media fila, alineado con la
   columna izquierda; el gap de .project-related-row es de 4px. */
.project-related-row > .project-related-item:only-child{ flex: 0 1 calc(50% - 2px); }
.project-related-item .tile-cover{ position:relative; overflow:hidden; background: var(--border); border-radius:8px; aspect-ratio:unset; }
.project-related-item .play-badge{ width:40px; height:40px; background: var(--ink); }
.project-related-row:nth-child(odd) .project-related-item:nth-child(1) .tile-cover{ height:197px; }
.project-related-row:nth-child(odd) .project-related-item:nth-child(2) .tile-cover{ height:177px; }
.project-related-row:nth-child(even) .project-related-item:nth-child(1) .tile-cover{ height:177px; }
.project-related-row:nth-child(even) .project-related-item:nth-child(2) .tile-cover{ height:197px; }

/* Related Work covers at their media's own proportion (2026-08-22, user
   request: "lo mismo en el footer de la calle bar cuando aparece en cover
   de ceremonia"). These tiles render slideTag(item.hero) — for a project
   with real media that's the actual cover file (La Calle Bar's hero.webp
   is a 1180x2556 frame pulled from its hero video), and the fixed
   177/197px heights above plus the global object-fit:cover were cropping
   it. Selectors repeat the nth-child chain so they outrank those four
   rules; placeholder projects keep the fixed heights. */
.project-related-row:nth-child(odd) .project-related-item:nth-child(1) .tile-cover:has(.media-real),
.project-related-row:nth-child(odd) .project-related-item:nth-child(2) .tile-cover:has(.media-real),
.project-related-row:nth-child(even) .project-related-item:nth-child(1) .tile-cover:has(.media-real),
.project-related-row:nth-child(even) .project-related-item:nth-child(2) .tile-cover:has(.media-real){ height:auto; }
.project-related-item .tile-cover .media-real{ height:auto; object-fit:unset; }
/* One flowing sentence, title inline with the description — matches
   Pencil's "Title" + "Prefix"/"Rest" caption (hand-split there for its
   static text tool; here plain CSS word-wrap does the same job). */
.project-related-caption{ margin:0; padding-top:6px; font-family: var(--font-body); font-size:16px; line-height:22px; }
.project-related-title{ font-weight:500; color: var(--ink); }
.project-related-desc{ font-weight:300; color: #121212B3; color: color-mix(in srgb, var(--ink) 70%, transparent); }

.project-footer-divider{ height:1px; background:#C3C3C3; }

/* Kept for the (currently unused) "logos" project type. */
.project-header{ display:flex; flex-direction:column; gap:16px; padding: 24px 20px; }
.project-header h1{ font-size: 28px; font-weight:500; }
.project-meta{ display:flex; gap:20px; }
.project-meta-item{ display:flex; flex-direction:column; gap:3px; }
.project-meta-label{ font-family: var(--font-body); font-size:10px; color: var(--ink-secondary); }
.project-meta-value{ font-family: var(--font-body); font-size:12px; color: var(--ink); }
.project-body{ padding: 20px 20px 0; }
.project-body p{ font-family: var(--font-body); font-size: 14px; line-height: 1.5; color: var(--ink); margin-bottom: 12px; }
.project-body p:last-child{ margin-bottom: 0; }
.next-project-row{ display:flex; justify-content:flex-end; padding: 16px 20px 40px; }
.next-project-link{ font-family: var(--font-body); font-size: 12px; color: var(--ink); }
.next-project-link:hover{ color: var(--ink-secondary); }

/* Logos detail page */
.logos-detail-grid{ display:grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin: 0 20px 40px; }
.logo-card{
  border: 1px solid var(--border);
  padding: 30px 16px;
  display:flex;
  align-items:center;
  justify-content:center;
  min-height: 110px;
  text-align:center;
}
.logo-card:hover{ border-color: var(--ink-secondary); }
.logo-mark{ color: var(--ink); line-height:1; }
.logo-mark.style-hand{ font-family: var(--font-heading); font-size:20px; font-style:italic; }
.logo-mark.style-sacred{ font-family: var(--font-heading); font-size:20px; letter-spacing:0.15em; }
.logo-mark.style-bold{ font-family: var(--font-heading); font-weight:700; font-size:22px; }
.logo-mark.style-condensed{ font-family: var(--font-body); font-weight:700; font-size:18px; letter-spacing:-0.02em; text-transform:uppercase; }
.logo-mark.style-pill{ font-family: var(--font-heading); font-weight:700; font-size:20px; border:2px solid var(--ink); border-radius:20px; padding:6px 18px; }
.logo-mark.style-spaced{ font-family: var(--font-body); font-weight:700; font-size:18px; letter-spacing:0.3em; }
.logo-mark.style-stencil{ font-family: var(--font-heading); font-weight:700; font-size:22px; letter-spacing:0.05em; }
.logo-mark.style-script{ font-family: var(--font-heading); font-style: italic; font-size:22px; }

/* ---------- Sidebar (desktop only, rendered per-page by renderSidebar()) ----------
   Home/Work share this as-is (Photo + About/Contact/Instagram cards); Info
   adds 5 project preview cards below; Side B empties it entirely (its own
   body is a photo masonry instead, not sidebar + content). */
.sidebar:empty{ display:none; }
.page-sidebar-photo{ position:relative; height:240px; border-radius:8px; overflow:hidden; background: var(--border); }
.page-sidebar-divider{ height:1px; background: rgba(0,0,0,0.15); }
.page-sidebar-card{ display:flex; flex-direction:column; gap:6px; }
.page-sidebar-card-header{ display:flex; justify-content:space-between; align-items:baseline; gap:12px; font-family: var(--font-body); font-size:14px; }
.page-sidebar-card-header span{ font-weight:700; color:#000000; }
.page-sidebar-card-header a{ font-weight:400; color:#000000; }
.page-sidebar-card-header a:hover{ color: var(--ink-secondary); }
.page-sidebar-card p{ font-family: var(--font-body); font-size:14px; line-height:1.4; color:#000000; margin:0; }
.page-sidebar-project{ display:flex; flex-direction:column; gap:6px; }
.page-sidebar-project .tile-cover{ position:relative; overflow:hidden; border-radius:8px; background: var(--border); }
/* Cover a la proporcion de su propio hero, sin alto fijo (2026-08-25). El
   aspect-ratio de .tile .tile-cover es para los tiles que siguen en
   placeholder; con foto real manda el archivo, mismo criterio que el Home,
   Work y la galeria del proyecto. */
.page-sidebar-project .tile-cover:has(.media-real){ aspect-ratio: unset; height: auto; }
.page-sidebar-project .tile-cover .media-real{ height: auto; object-fit: unset; }

/* ---------- Footer (all pages) ----------
   Matches Pencil's reusable Footer component (TKUqY): Contact lines
   (email/phone/Instagram), a divider, then Copyright+Rights on the left
   and a bold "Back to top" on the right. */
.site-footer{
  display:flex;
  flex-direction:column;
  gap: 12px;
  padding: 12px 6px 16px;
  margin-top: 32px;
}
.footer-contact{ display:flex; flex-direction:column; gap:2.5px; width:fit-content; }
.footer-contact-line{ font-family: var(--font-body); font-size: 14px; color: var(--ink); }
.footer-contact-line:hover{ color: var(--ink-secondary); }
.footer-divider{ height:1px; background:#C3C3C3; }
.footer-bottom{ display:flex; justify-content:space-between; align-items:flex-end; }
.footer-copy{ display:flex; flex-direction:column; gap:2.5px; }
.footer-copyright{ font-family: var(--font-body); font-size: 10px; color: var(--ink); }
.footer-rights{ font-family: var(--font-body); font-size: 10px; color: var(--ink-secondary); }
.back-to-top{ background:none; border:none; font-family: var(--font-body); font-size: 14px; font-weight:700; color: var(--ink); cursor:pointer; padding:0; }
.back-to-top:hover{ color: var(--ink-secondary); }
.footer-rights{ color:#626262; font-weight:300; }

/* ---------- Desktop (≥861px) ---------- */
@media (min-width: 861px){
  /* Nav Desktop Master (aZEet): 32px bar, 6px side padding, 16px dividers,
     3px vertical pill-wrap padding (26px pill = 32 - 2*3, matches below). */
  /* Margen exterior de la pagina en desktop. TIENE QUE DAR EL MISMO NUMERO que
     el borde izquierdo de la pastilla negra del nav, que sale de sumar el
     padding lateral de .nav-links (6px) + el de .nav-pill-wrap (4px) = 10px.
     Antes el contenido iba a 16px y la pastilla a 10px, y se notaba
     (2026-08-24, el usuario: "esto no esta alineado, deberia estar alineado
     todo ese margen con la pastilla negra"). Si alguna vez cambian esos dos
     paddings del nav, hay que recalcular este valor. */
  :root{
    --edge: 10px;
    /* Separacion entre dos pastillas del nav: 4px de .nav-pill-wrap + 1px de
       .nav-divider + 4px del wrap siguiente. Es la que tienen que usar todas
       las grillas de la pagina para que el corte entre columnas caiga JUSTO
       sobre el divisor (2026-08-24, el usuario: "el resto de las columnas
       deberian caer en linea del divider de la nav bar"). */
    --gutter: 9px;
  }

  .site-nav .nav-links{ height: 32px; padding: 0 6px; }
  .nav-divider{ height: 16px; }
  .nav-pill-wrap{ padding: 3px 4px; }
  .nav-pill{ height: 26px; padding: 0 14px; }

  /* La pagina es la misma grilla de 4 tracks que el nav: 4 columnas iguales,
     los mismos --gutter entre medio y el mismo --edge afuera. Asi el sidebar
     cae bajo la primera pastilla y cada corte entre columnas cae sobre su
     divisor. Antes era flex con el sidebar clavado en 360px y las columnas
     con gap 6px, que no daba con ningun divisor. */
  .layout{
    min-height: 100vh;
    display:grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    column-gap: var(--gutter);
    padding-inline: var(--edge);
    align-items:start;
  }
  /* La columna de la izquierda se despega del scroll de la derecha
     (2026-08-24). En Home su contenido es corto, no llega a scrollear y queda
     fija mientras se mueven las columnas de proyectos. En Info es mas largo
     —suma las 5 tarjetas de proyecto— asi que scrollea por su cuenta, y las
     dos partes se mueven independientes.
     align-self VA EN start, no stretch: estirado a la altura de la fila el
     sticky no tiene contra que pegarse y no hace nada. */
  .sidebar{
    display:flex;
    flex-direction:column;
    gap: 16px;
    grid-column: 1;
    width:auto;
    padding: 16px 0;
    align-self: start;
    position: sticky;
    top: var(--nav-h);
    max-height: calc(100vh - var(--nav-h));
    overflow-y: auto;
  }
  /* flex:none en los hijos, si no NO SCROLLEA: los encoge. Es la trampa
     clasica de un flex column con max-height — antes de desbordar, flexbox
     achica todo lo que tenga flex-shrink (o sea, todo). Con el contenido de
     Info a 2905px en una caja de 868, la foto del badge —que tiene height
     fijo de 240px y ningun contenido que la sostenga— se aplastaba a 0
     (2026-08-25). */
  .sidebar > *{ flex: none; }
  .main{ grid-column: 2 / -1; min-width:0; }
  /* Side B esconde el sidebar (renderSidebar("sideb") lo deja vacio), asi que
     ahi el contenido se queda con los 4 tracks. */
  .layout:has(> .sidebar:empty) .main{ grid-column: 1 / -1; }

  /* Home Desktop (z2Lknd): no hero/intro at all — straight to the sidebar +
     grid. */
  .hero{ display:none; }
  .home-tile-columns{ display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--gutter); padding: 16px 0; }
  .home-tile-column{ gap: var(--gutter); min-width:0; }
  /* 3rd column (9-tile 3x3 grid) is desktop-only; mobile hides it via the
     max-width query further down so Home Mobile keeps its original 6 tiles. */
  .home-tile-column:nth-child(3) .tile:nth-child(odd) .tile-cover{ aspect-ratio: 187/197; }
  .home-tile-column:nth-child(3) .tile:nth-child(even) .tile-cover{ aspect-ratio: 187/177; }

  /* Work Desktop unselected (Ghq1t): sidebar (above) + 3-col masonry catalog
     of all 12 projects, no header text. Replaces the mobile single-column
     list (.grid) entirely at this width — not a scaled-up version of it. */
  .grid{ display:none; }
  /* Cuatro columnas: el catalogo de Work no lleva sidebar (2026-08-24, el
     usuario: "el logo, mi about y la columna de la izquierda no va, van 4
     columnas de los proyectos"), asi que main ocupa los 4 tracks via
     :has(> .sidebar:empty). Con un proyecto abierto el sidebar vuelve y la
     galeria del detalle sigue siendo de 3. */
  .work-desktop-grid{ display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--gutter); padding:16px 0; }
  .work-desktop-col{ min-width:0; display:flex; flex-direction:column; gap: var(--gutter); }
  .work-desktop-col:nth-child(1) .tile:nth-child(odd) .tile-cover,
  .work-desktop-col:nth-child(3) .tile:nth-child(odd) .tile-cover{ aspect-ratio: 349/197; }
  .work-desktop-col:nth-child(1) .tile:nth-child(even) .tile-cover,
  .work-desktop-col:nth-child(3) .tile:nth-child(even) .tile-cover{ aspect-ratio: 349/177; }
  .work-desktop-col:nth-child(2) .tile:nth-child(odd) .tile-cover{ aspect-ratio: 349/177; }
  .work-desktop-col:nth-child(2) .tile:nth-child(even) .tile-cover{ aspect-ratio: 349/197; }
  .work-desktop-col:nth-child(4) .tile:nth-child(odd) .tile-cover{ aspect-ratio: 349/177; }
  .work-desktop-col:nth-child(4) .tile:nth-child(even) .tile-cover{ aspect-ratio: 349/197; }
  /* ...pero solo mientras el tile siga en placeholder. Con foto real manda la
     proporcion del archivo, mismo criterio que el Home (2026-08-24, el
     usuario: "en work, los cover de los proyectos no son fijos, son como en
     el home, se adapta al tamaño de la foto hero"). El id le gana en
     especificidad a los nth-child de arriba. */
  #grid-work-desktop .tile-cover:has(.media-real){ aspect-ratio: unset; }
  #grid-work-desktop .tile-cover .media-real{ height:auto; object-fit:unset; }

  /* Info Desktop (X8g8f): 3 content columns, each ending in a photo — not a
     reflow of the mobile layout, a different structure/content entirely
     (INFO_DESKTOP in data.js). Sidebar (above) gets 5 extra project cards
     via renderSidebar("info"). */
  .info-content{ display:none; }
  .info-desktop{ display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--gutter); padding:16px 0; }
  .info-desktop-col{ flex:1; min-width:0; display:flex; flex-direction:column; gap:16px; }
  .info-desktop-section{ display:flex; flex-direction:column; gap:6px; }
  .info-desktop-heading{ font-family: var(--font-body); font-size:14px; font-weight:700; color:#000000; margin:0; }
  .info-desktop-body{ font-family: var(--font-body); font-size:14px; line-height:1.4; color:#121212B3; margin:0; }
  .info-desktop-list{ font-family: var(--font-body); font-size:14px; line-height:1.4; color:#121212B3; }

  /* Side B Desktop (BI3ZW): NOT the mobile grid widened — a 4-track photo
     masonry (Sidebar track: title+full intro+2 photos, then 3 pure photo
     columns), no captions anywhere. The persistent sidebar is empty/hidden
     on this page (renderSidebar("sideb")); this track is separate content. */
  .sideb-content{ display:none; }
  .sideb-desktop{ display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: var(--gutter); padding:16px 0; }
  .sideb-desktop-sidebar{ min-width:0; display:flex; flex-direction:column; gap:16px; }
  .sideb-desktop-col{ min-width:0; display:flex; flex-direction:column; gap: var(--gutter); }
  .sideb-desktop-intro{ display:flex; flex-direction:column; gap:8px; }
  .sideb-desktop-title{ font-family: var(--font-heading); font-size:14px; font-weight:700; color:#000000; margin:0; }
  .sideb-desktop-body{ font-family: var(--font-body); font-size:14px; line-height:1.4; color:#121212B3; margin:0; }
  .sideb-desktop-photo{ position:relative; border-radius:8px; overflow:hidden; background: var(--border); }
  .sideb-desktop-photo .play-badge{ background:#121212; }
  .sideb-desktop-photo .play-badge svg{ width:18px; height:18px; }
  /* A proporcion real (2026-08-23, pedido del usuario): las celdas ya no
     llevan la altura fija del spec de Pencil — cada foto/video se muestra a
     su propio aspecto, mismo criterio que el Home y el Related Work.
     --group son los carruseles y slide-cuts, que traen su propia caja. */
  .sideb-desktop-photo--auto{ height:auto; background:none; }
  .sideb-desktop-photo--auto img, .sideb-desktop-photo--auto video{ height:auto; object-fit:unset; }
  .sideb-desktop-photo--group{ height:auto; background:none; border-radius:0; overflow:visible; }

  /* Project detail (Work selected Desktop, uDnON): header (title+tagline,
     then a description paragraph) + a 3-col masonry gallery with NO
     captions — a different structure from the mobile Project view, not a
     scaled-up version of it. Sidebar (above) stays visible, "work" variant. */
  .project-desktop-header{ display:flex; flex-direction:column; gap:32px; padding:16px 0 0; }
  /* La fila del encabezado usa las MISMAS 3 columnas que la galeria de abajo:
     el titulo cae en la primera y el tagline queda emblocado en la segunda, o
     sea entre el 2do y el 3er divisor del nav (2026-08-24, pedido del usuario).
     Antes era un flex con space-between y un max-width de 420px, asi que el
     tagline flotaba contra el borde derecho sin coincidir con ninguna columna. */
  .project-desktop-header-row{ display:grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: var(--gutter); }
  .project-desktop-title{ font-family: var(--font-heading); font-size:14px; font-weight:700; color: var(--ink); margin:0; }
  .project-desktop-tagline{ grid-column: 2; font-family: var(--font-body); font-size:14px; color: var(--ink); text-align:left; margin:0; }
  /* Was a single <p>; now wraps one <p> per p.body entry (script.js,
     2026-08-12) so a paragraph break in the source .docx (multiple body[]
     items) shows as a real paragraph break here too, not just on mobile. */
  .project-desktop-description{ font-family: var(--font-body); font-size:16px; line-height:1.5; color:#121212B3; max-width:560px; margin:0; }
  .project-desktop-description p{ margin:0 0 10px; }
  .project-desktop-description p:last-of-type{ margin-bottom:0; }
  /* Dos columnas adentro de un proyecto (2026-08-25). El encabezado de arriba
     sigue siendo de 3 para que el tagline caiga entre el 2do y el 3er divisor,
     que es donde se pidio. */
  .project-desktop-gallery{ display:grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: var(--gutter); padding:16px 0; }
  .project-desktop-col{ min-width:0; display:flex; flex-direction:column; gap: var(--gutter); }
  .project-desktop-photo{ position:relative; background: var(--border); border-radius:8px; overflow:hidden; }
  /* A proporcion real: la celda toma el alto de su archivo (2026-08-24, el
     usuario: "dentro de cada seccion de work, las fotos no tienen tamaño fijo,
     se adaptan proporcional a su tamaño como venimos haciendo"). El alto fijo
     queda SOLO para el placeholder gris, que no tiene proporcion propia. */
  /* OJO con la condicion: tiene que mirar si hay un PLACEHOLDER, no si falta
     .media-real. Las imagenes que arma marqueeHTML()/slideshowHTML() son <img>
     pelados, sin esa clase, asi que un :not(:has(.media-real)) tambien les
     pegaba a los carruseles y slide-cuts y les clavaba 220px: los banners de
     Ceremonia, que van a 30px, quedaban con 190px de aire abajo (2026-08-25). */
  .project-desktop-photo:has(.media-placeholder){ height:220px; }
  .project-desktop-photo .media-real{ height:auto; object-fit:unset; }
  .project-desktop-photo .play-badge{ background: var(--ink); }
  .project-desktop-photo .play-badge svg{ width:18px; height:18px; }
  /* Carrusel group cell (see renderDesktopGalleryCell() in script.js,
     2026-08-12): height comes from .project-marquee itself (220px), not the
     masonry's randomized per-cell height — no background needed either,
     the filmstrip photos fill it edge to edge. */
  .project-desktop-photo--marquee{ height:auto; background:none; }
  /* Alto propio para desktop, sin tocar el mobile: lo pone `heightDesktop` en
     data.js (ver marqueeHTML en script.js). Vive dentro del media query, asi
     que en el celu el carrusel conserva su alto normal. Le gana a la regla
     base de .project-marquee por venir despues en el archivo. */
  .project-marquee--h-desktop{ height: var(--marquee-h-desktop); }
  .project-desktop-photo--slideshow{ height:auto; background:none; }

  /* Kept for the (currently unused) "logos" project type. */
  .project-header{ flex-direction:row; justify-content:space-between; align-items:flex-end; padding: 40px 48px; }
  .project-header h1{ font-size: 48px; }
  .project-meta{ gap:32px; }
  .project-body{ padding: 40px 48px 0; }
  .project-body p{ max-width: 720px; font-size: 15px; }
  .next-project-row{ padding: 16px 48px 56px; }
  .logos-detail-grid{ grid-template-columns: repeat(3,1fr); margin: 0 48px 56px; }

  /* El footer va debajo de las columnas de la derecha y con el mismo formato
     apilado que en mobile: los contactos, el divisor a todo el ancho y abajo
     el copyright a la izquierda con Back to top a la derecha (2026-08-24, el
     usuario paso una captura). Antes en desktop era una fila con 48px de
     padding lateral, que ademas lo desalineaba de las columnas. Vive adentro
     de main, asi que ya arranca donde arrancan ellas. */
  .site-footer{ padding: 32px 0; }
}

/* ---------- JQ personal seal badge (2026-08-12, user request) ----------
   Fills the Home hero (mobile), Info hero (mobile) and the shared desktop
   sidebar photo — see badgeTag() in script.js for exactly which three call
   sites use it (the sidebar one repeats visually across Home/Work-selected/
   Info Desktop since it's one shared component, not three separate spots).
   Static piece — no bounce/float (removed 2026-08-12 per user request), a
   periodic light glint sweeps across the mark instead (.jq-badge-shine).
   Background is a warm beige (#F3EFE6), not var(--border) gray —
   deliberately different from the plain gray placeholder these boxes used
   to be, so it reads as a finished piece rather than a "still waiting on
   real content" placeholder (2026-08-12, user request). Artwork swapped to
   a fresh export of the same seal (2026-08-12, later same day, user
   request) — assets/img/badge/jq-seal.png replaced in place, no code
   changes needed for the swap itself since every call site just references
   that filename. */
.jq-badge-wrap{
  position:relative;
  width:100%; height:100%;
  display:flex; align-items:center; justify-content:center;
  background: #F3EFE6;
  overflow:hidden;
}
.jq-badge-wrap::after{
  content:"";
  position:absolute; inset:0;
  background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix type='matrix' values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 0.9 0'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
  opacity: 0.025;
  mix-blend-mode: multiply;
  pointer-events: none;
}
.jq-badge-stage{
  position: relative;
  height: 64%;
  display:flex;
  flex-direction:column;
  align-items:center;
}
.jq-badge-shine-wrap{
  position:relative;
  height:100%;
  display:flex;
}
.jq-badge-img{
  height:100%;
  width:auto;
  object-fit:contain;
  display:block;
  filter: drop-shadow(0 2px 3px rgba(18,18,18,.10));
}
/* Light glint (2026-08-12, third and current architecture — see the long
   comment above badgeTag() in script.js for the full history of why this
   went through three rewrites in one day: (1) CSS -webkit-mask-image + an
   animated-transform child died on iOS Safari specifically, (2) an inline
   SVG native <mask> + SMIL <animateTransform> rendered correctly in
   isolation but the animated gradient silently never painted a single
   pixel inside this app's actual DOM (confirmed via direct pixel sampling
   over the badge's own ink — a real, reproducible Chromium bug tied to
   this specific compositing context, not a guess). This version drops
   masking of any kind — no CSS mask, no SVG mask — so it can't trigger
   either failure mode. The bar is a plain `background: linear-gradient`
   on a div, moved by a CSS `@keyframes` transform, with
   `mix-blend-mode: screen`. It doesn't need to be shaped to the badge's
   silhouette: screen-blending a light gradient over the near-white
   `#F3EFE6` wrap background is close to a no-op by the blend math itself
   (screen of a light base stays close to that base), so in practice the
   sweep only reads as visible where it crosses the badge's own dark ink —
   the same "only lights up the mark" effect the mask used to provide
   explicitly, here as a side effect of the blend math. `.jq-badge-wrap`'s
   existing `overflow:hidden` keeps the (deliberately oversized, rotated)
   bar from spilling past the badge's rectangular box — same containment
   role it played for the very first (pre-SVG) version of this effect.
   Verified in this sandbox (Chromium only — no WebKit browser available
   here) by sampling a dark-ink pixel across a full 8s cycle: flat at
   rest, a real jump mid-sweep, flat again after — a clean rest state, not
   just "the gradient happens to be transparent there". Still can't be
   confirmed on real iOS Safari locally; removing the CSS mask (the
   specific ingredient the original WebKit bug needed) is an informed bet
   that it avoids that failure mode, not a confirmed fix on hardware. */
.jq-badge-shine{
  position:absolute;
  inset:0;
  overflow:hidden;
  pointer-events:none;
  mix-blend-mode: screen;
  /* Crop to the seal's actual oval/pill silhouette (2026-08-12, user
     request — without this the glint reads as lighting up a rectangle
     around the mark, not the mark itself). Traced from the real alpha
     channel of jq-seal.png (118 points sampled from the opaque boundary
     at 60 row-slices, top-to-bottom then bottom-to-top, as % of the
     image's own box) rather than a plain CSS ellipse() — the seal isn't
     a true ellipse (it bulges more through the vertical middle than an
     ellipse of the same width/height would), so a hand-picked ellipse()
     visibly mismatched the real edge. This is a plain geometric clip
     (not an alpha mask) so it doesn't reintroduce the -webkit-mask-image
     + animated-transform combination that broke on iOS Safari (see the
     long history above badgeTag() in script.js) — clip-path here is
     static (the polygon itself never animates, only the bar inside it
     does), functionally the same role .jq-badge-wrap's own overflow:hidden
     already played for the very first version of this effect, just
     shaped instead of rectangular. If jq-seal.png is ever swapped for
     artwork with a different silhouette, this polygon needs re-tracing
     to match (percentages are relative to this element's own box, which
     is sized to match .jq-badge-img exactly via inset:0). */
  clip-path: polygon(
    64.15% 1.72%, 70.47% 3.30%, 75.22% 5.01%, 78.73% 6.60%, 81.90% 8.31%,
    84.53% 10.03%, 86.64% 11.61%, 88.58% 13.32%, 90.33% 15.04%, 91.74% 16.62%,
    93.15% 18.34%, 94.20% 19.92%, 95.25% 21.64%, 96.13% 23.35%, 96.84% 24.93%,
    97.54% 26.65%, 98.07% 28.23%, 98.42% 29.95%, 98.77% 31.66%, 99.12% 33.25%,
    99.12% 34.96%, 99.30% 36.68%, 99.30% 38.26%, 99.30% 39.97%, 99.30% 41.56%,
    99.30% 43.27%, 99.30% 44.99%, 99.30% 46.57%, 99.30% 48.28%, 99.30% 49.87%,
    99.30% 51.58%, 99.30% 53.30%, 99.30% 54.88%, 99.30% 56.60%, 99.30% 58.31%,
    99.30% 59.89%, 99.30% 61.61%, 99.30% 63.19%, 99.12% 64.91%, 99.12% 66.62%,
    98.77% 68.21%, 98.42% 69.92%, 98.07% 71.64%, 97.54% 73.22%, 96.84% 74.93%,
    96.13% 76.52%, 95.25% 78.23%, 94.38% 79.95%, 93.15% 81.53%, 91.92% 83.25%,
    90.51% 84.83%, 88.75% 86.54%, 86.82% 88.26%, 84.71% 89.84%, 82.07% 91.56%,
    78.91% 93.27%, 75.40% 94.85%, 70.65% 96.57%, 64.50% 98.15%, 35.33% 98.15%,
    29.17% 96.57%, 24.43% 94.85%, 20.91% 93.27%, 17.75% 91.56%, 15.11% 89.84%,
    13.01% 88.26%, 11.07% 86.54%, 9.31% 84.83%, 7.91% 83.25%, 6.50% 81.53%,
    5.45% 79.95%, 4.39% 78.23%, 3.51% 76.52%, 2.81% 74.93%, 2.28% 73.22%,
    1.76% 71.64%, 1.23% 69.92%, 1.05% 68.21%, 0.70% 66.62%, 0.53% 64.91%,
    0.53% 63.19%, 0.53% 61.61%, 0.53% 59.89%, 0.53% 58.31%, 0.53% 56.60%,
    0.53% 54.88%, 0.53% 53.30%, 0.53% 51.58%, 0.53% 49.87%, 0.53% 48.28%,
    0.53% 46.57%, 0.53% 44.99%, 0.53% 43.27%, 0.53% 41.56%, 0.53% 39.97%,
    0.53% 38.26%, 0.53% 36.68%, 0.53% 34.96%, 0.70% 33.25%, 1.05% 31.66%,
    1.41% 29.95%, 1.76% 28.23%, 2.28% 26.65%, 2.99% 24.93%, 3.69% 23.35%,
    4.57% 21.64%, 5.62% 19.92%, 6.68% 18.34%, 7.91% 16.62%, 9.31% 15.04%,
    11.07% 13.32%, 13.18% 11.61%, 15.29% 10.03%, 17.93% 8.31%, 21.09% 6.60%,
    24.60% 5.01%, 29.35% 3.30%, 35.68% 1.72%
  );
}
.jq-badge-shine-bar{
  position:absolute;
  top:-45%;
  left:0;
  width:60%;
  height:190%;
  transform: rotate(18deg) translateX(-260%);
  background: linear-gradient(
    to right,
    rgba(255,255,255,0) 0%,
    rgba(255,255,255,0.22) 30%,
    rgba(255,255,255,0.85) 50%,
    rgba(255,255,255,0.22) 70%,
    rgba(255,255,255,0) 100%
  );
  animation: jqBadgeShine 8s cubic-bezier(0.65,0,0.35,1) infinite;
}
@keyframes jqBadgeShine{
  0%, 50% { transform: rotate(18deg) translateX(-260%); }
  100% { transform: rotate(18deg) translateX(260%); }
}
.jq-badge-shadow{
  width:46%;
  height:10px;
  margin-top:-6px;
  border-radius:50%;
  background: radial-gradient(ellipse at center, rgba(18,18,18,.22) 0%, rgba(18,18,18,.10) 50%, rgba(18,18,18,0) 78%);
  filter: blur(2px);
  flex-shrink:0;
}

/* ---------- Mobile-only (≤860px): swipe track ---------- */
@media (max-width: 860px){
  /* #project-view now lives inside #page-work's own scroller and swipes
     with the track like any other page (see the base "Work page: catalog
     vs. open project" rules above) — just a light fade/slide-in for polish
     when it appears, no more position:fixed overlay. */
  .project-view{
    transform: translateY(16px);
    opacity: 0;
    transition: transform .4s var(--ease-soft), opacity .3s var(--ease-soft);
  }
  .project-view.is-open{ transform:translateY(0); opacity:1; }

  /* Los pares de declaraciones de aca y de las de color-mix/touch-action mas
     arriba son fallbacks, no duplicados: si el navegador no entiende la
     segunda, la descarta y se queda con la primera. Sin esto, un Safari
     anterior al 15.4 tira la linea del dvh entera, .main queda sin alto y la
     tira de secciones no mide nada (2026-08-27). */
  .main{ width:100%; overflow:hidden; height: calc(100vh - var(--nav-h)); height: calc(100dvh - var(--nav-h)); }
  /* touch-action reparte los ejes con el navegador: la vertical la maneja el
     (scroll normal), la horizontal es nuestra. Sin esto el navegador puede
     quedarse con el gesto horizontal a mitad de camino y mandar un touchcancel
     —ver el handler en initSwipe()—. pinch-zoom explicito para no perder el
     zoom con dos dedos sobre las fotos. */
  .pages-track{ display:flex; height:100%; will-change: transform; touch-action: pan-y; touch-action: pan-y pinch-zoom; }
  /* Home Desktop's 3rd tile column (3x3 grid) doesn't exist on mobile —
     Home Mobile keeps its original 6-tile, 2-column layout. */
  .home-tile-column:nth-child(3){ display:none; }
  .page{ display:block; flex: 0 0 100vw; width:100vw; height:100%; overflow:hidden; }
  .page-scroll{ height:100%; overflow-y:auto; -webkit-overflow-scrolling:touch; overscroll-behavior-y: contain; }

  .site-footer{ padding-bottom: max(40px, env(safe-area-inset-bottom)); }
}

/* Reveal de entrada para los textos de Info (2026-08-23, pedido del usuario:
   "cada parrafo aparece con una mini animacion, slide up"). Sube 14px y hace
   fade al entrar en pantalla, escalonado por orden.
   OJO: la clase .reveal la pone initReveal() desde JS, NUNCA el HTML. Si el JS
   no corre, el texto se ve normal en vez de quedar invisible con opacity:0.
   Con Reduce Motion activado el texto aparece igual, sin transicion — no se
   oculta (ver la nota de .project-marquee mas arriba: en iOS eso ya causo un
   "se ve pero no se mueve" que costo diagnosticar). */
.reveal{
  opacity: 0;
  transform: translateY(var(--reveal-shift));
  will-change: opacity, transform;
}
/* La transicion vive SOLO en el estado final, NO en .reveal (2026-08-23).
   Puesta en .reveal, el instante en que initReveal() agrega la clase ya es un
   cambio de opacity 1 -> 0 CON transicion: el elemento arranca a desvanecerse
   durante todo el fade. Para los bloques que entran por scroll no se nota, porque el
   observer tarda en dispararse; pero para todo lo que YA esta en pantalla
   cuando se observa — el hero de un proyecto recien abierto — .is-in llega un
   frame despues, el fade de vuelta arranca de un valor casi lleno y no se ve
   nada de animacion. Declarada aca, el estado inicial se aplica de golpe y
   solo la entrada anima.
   El fade dura menos que el movimiento a proposito: el texto ya se puede leer
   mientras todavia se esta acomodando. Con las dos duraciones iguales el
   bloque llega arriba justo cuando termina de aparecer y se siente pastoso.
   Los valores viven en :root, arriba de todo. */
.reveal.is-in{
  opacity: 1;
  transform: none;
  transition: opacity var(--reveal-fade) var(--reveal-ease),
              transform var(--reveal-move) var(--reveal-ease);
  transition-delay: var(--reveal-delay, 0ms);
}
@media (prefers-reduced-motion: reduce){
  .reveal{ opacity: 1; transform: none; transition: none; }
}
