/* Photograficamente Feliz — "paper and plum"
   The photograph is the interface: no borders, no shadows, no rounded corners.
   Chrome is type and whitespace only. Every measurement here comes from the
   design canvas (Main.dc.html, HomeMovil.dc.html, Pieza.dc.html).
   @font-face lives in the inline critical CSS in default.hbs so the preload and
   the CSS request the same versioned URL. */

/* ---------- tokens ---------- */
:root {
  --paper: #F5F2F8;
  --ink: #1A1523;
  --ink-2: #2A2236;
  --plum: #745A99;
  --lavender: #D2C8DE;
  --accent: #DD3967;
  --muted: #8A7F99;

  /* Ghost's Design → font pickers, if the owner ever sets one, override the
     theme's own faces here and nowhere else. Unset, they fall through to the
     self-hosted identity. */
  --sans: var(--gh-font-heading, Oswald), "Oswald Fallback", "Arial Narrow", "Helvetica Neue", Arial, sans-serif;
  --serif: var(--gh-font-body, Newsreader), "Newsreader Fallback", Georgia, "Times New Roman", serif;

  /* The magenta is 3.9:1 on paper and 4.1:1 on plum: fine as a fill, short of
     WCAG AA as 11-12px text. These two are the same magenta nudged just far
     enough to pass, and they follow the accent custom setting.
     Quiet text follows the ground: --plum on paper (5.2:1), --muted on ink
     (4.7:1). --muted on paper is only 3.4:1 and is never used there. */
  --accent-paper: color-mix(in srgb, var(--accent) 88%, var(--ink));
  --accent-ink: color-mix(in srgb, var(--accent) 90%, var(--paper));
  --tilt-bg: color-mix(in srgb, var(--accent) 94%, var(--ink));

  --pad: 56px;
  --gap: 24px;
  --basis: 440px;
  --rule: #D2C8DE;
}

/* ---------- reset that survives the head reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--serif);
  font-size: 16px;
  line-height: 1.5;
  overflow-x: hidden;
}
h1, h2, h3, p, figure, blockquote { margin: 0; }
a { color: inherit; text-decoration: none; }
a:hover { color: var(--accent-paper); }
img, video { max-width: 100%; }
button { font: inherit; color: inherit; background: none; border: 0; padding: 0; cursor: pointer; }

/* Spaced capitals: the identity's voice, used for every label on the site. */
.caps {
  font-family: var(--sans);
  font-weight: 300;
  text-transform: uppercase;
  letter-spacing: 0.22em;
}

.u-vh {
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  clip-path: inset(50%);
  overflow: hidden; white-space: nowrap;
}

/* ---------- focus and motion ---------- */
:focus { outline: none; }
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
}
.is-dark :focus-visible { outline-color: var(--paper); }

.skip {
  position: absolute; left: 16px; top: -100px; z-index: 50;
  background: var(--ink); color: var(--paper);
  padding: 12px 18px;
  font-family: var(--sans); font-weight: 300;
  text-transform: uppercase; letter-spacing: 0.26em; font-size: 12px;
  transition: top 0.18s;
}
.skip:focus { top: 16px; color: var(--paper); }

/* The one orchestrated moment: pieces rise 12px as they enter. It is armed only
   when the feed script sets html.js, so with JavaScript off nothing is hidden. */
html.js .piece {
  opacity: 0;
  transform: translateY(12px);
  transition: opacity 0.32s ease-out, transform 0.32s ease-out;
}
html.js .piece.is-in { opacity: 1; transform: none; }

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

/* ---------- header ---------- */
.hdr {
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px;
  padding: 28px var(--pad) 20px;
}
.hdr__mark { display: flex; align-items: center; gap: 18px; }
.hdr__badge { width: 44px; height: 44px; display: block; flex: none; }
.hdr__name { font-size: 15px; letter-spacing: 0.34em; color: var(--ink); }
/* F4: 44px minimum touch target, bought with padding so the type keeps its
   size. The negative margin keeps the optical position from the canvas. */
.hdr__nav { display: flex; gap: 40px; font-size: 12px; letter-spacing: 0.26em; margin: -13px 0; }
.hdr__nav a { color: var(--plum); display: flex; align-items: center; min-height: 44px; }
.hdr__nav a:hover, .hdr__nav a[aria-current] { color: var(--accent-paper); }
.hdr__menu { display: none; }
.hdr__back { font-size: 12px; letter-spacing: 0.26em; color: var(--lavender); white-space: nowrap; }

/* Inverted header in the darkroom. */
.is-dark { background: var(--ink); color: var(--paper); }
.is-dark .hdr__name { color: var(--paper); }
.is-dark a:hover { color: var(--accent-paper); }

/* ---------- the opening spread (page one only) ---------- */
.spread {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: var(--gap);
  padding: 8px var(--pad) 0;
  align-items: stretch;
}
.spread__media {
  grid-column: span 7;
  position: relative;
  overflow: hidden;
  background: var(--lavender) center / cover;
}
.spread__media .pic, .spread__media .pic__img {
  display: block; width: 100%; height: 100%; object-fit: cover;
}
/* The tilt: the current site's rotated card, kept at signature size. */
.tag-tilt {
  position: absolute; left: -6px; top: 26px;
  transform: rotate(-6deg);
  background: var(--tilt-bg); color: #FFFFFF;
  font-size: 11px; letter-spacing: 0.3em;
  padding: 6px 14px 5px 18px;
  z-index: 2;
}
.spread__words {
  grid-column: span 5;
  display: flex; flex-direction: column; justify-content: space-between;
  padding: 8px 0 8px 16px;
}
.spread__meta { font-size: 11px; letter-spacing: 0.3em; color: var(--plum); }
.spread__title {
  margin: 18px 0 0;
  font-family: var(--sans); font-weight: 300;
  font-size: 56px; line-height: 1.02; letter-spacing: 0.02em;
  text-transform: uppercase; text-wrap: balance;
}
.spread__excerpt {
  margin: 28px 0 0;
  font-size: 22px; line-height: 1.45; font-style: italic;
  max-width: 30ch; color: var(--ink);
}
.spread__more { font-size: 12px; letter-spacing: 0.28em; color: var(--accent-paper); margin-top: 28px; }

/* ---------- the series strip: the archive's albums as a quiet index ---------- */
.strip-series {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 32px;
  border-top: 1px solid var(--rule);
  margin: 56px var(--pad) 0;
  padding-top: 24px;
}
.strip-series__label { font-size: 11px; letter-spacing: 0.3em; color: var(--plum); flex: none; }
.strip-series__list {
  display: flex; flex-wrap: wrap; justify-content: flex-end;
  gap: 12px 28px;
  font-size: 12px; letter-spacing: 0.22em; color: var(--ink);
}
.strip-series__n { color: var(--plum); opacity: 0.8; margin-left: 0.5em; }
.strip-series__all { color: var(--accent-paper); }

/* ---------- the feed: justified rows in pure CSS ---------- */
/* Each piece grows in proportion to its own aspect ratio and starts from a basis
   in the same proportion, so every piece on a row resolves to the same height
   and no photograph is cropped. The ::after eats the last row's spare growth. */
.feed { display: block; }
.feed__grid {
  display: flex; flex-wrap: wrap;
  gap: var(--gap);
  padding: 40px var(--pad) 0;
  /* G3: pieces take their own height. Stretching them to the tallest card in
     the row left ~280px of blank paper under a short one — a 9:16 reel beside
     two words cards. Only the words card still stretches: its plum block is
     meant to fill the row, the way the canvas draws it. */
  align-items: flex-start;
}
/* F1: the spacer only absorbs spare WIDTH on the last row. align-items:stretch
   was giving it the full row height, so when an appended page re-wrapped that
   row it collapsed from ~1000x568 to 0x0 and took the footer with it. At zero
   height the same collapse moves nothing. */
.feed__grid::after {
  content: "";
  flex: 999 1 0;
  height: 0;
  min-height: 0;
  align-self: flex-start;
}

.piece {
  flex-grow: calc(var(--ar) * 100);
  flex-basis: calc(var(--ar) * var(--basis));
  min-width: 0;
  content-visibility: auto;
  contain-intrinsic-size: auto 480px;
}
.piece__link { display: flex; flex-direction: column; height: 100%; position: relative; }

.piece__media {
  position: relative;
  overflow: hidden;
  background-color: var(--lavender);
  background-size: cover;
  background-position: center;
}
/* The 30px LQIP is the box's own background; this blurs it without touching the
   real photograph that lands on top of it. */
.piece__media::before {
  content: "";
  position: absolute; inset: 0;
  background-image: inherit;
  background-size: cover; background-position: center;
  filter: blur(14px);
  transform: scale(1.12);
}
.pic { position: relative; z-index: 1; display: block; width: 100%; height: 100%; }
.pic__img { display: block; width: 100%; height: 100%; object-fit: cover; }

.piece__caption {
  padding-top: 12px;
  font-family: var(--sans); font-weight: 300;
  text-transform: uppercase;
  font-size: 11px; letter-spacing: 0.26em;
  color: var(--plum);
}
.piece__title {
  font: inherit; margin: 0;
  display: inline;
}
.piece__meta { display: inline; margin: 0; }
/* One spaced-caps line: title, series, year, separated by a middle dot. The
   first part of the line never gets a leading dot, which matters on the words
   card where the title is out of the flow. */
.piece__serie::before,
.piece__year::before {
  content: "\00B7"; margin: 0 0.5em; color: inherit; opacity: 0.55;
}
.piece--words .piece__meta > *:first-child::before { content: none; margin: 0; }
/* A #sin-titulo card renders no title, so the series name becomes the first
   thing on the line and must not carry a leading separator. */
.piece__caption:not(:has(.piece__title)) .piece__meta > *:first-child::before { content: none; margin: 0; }
.piece:hover .piece__caption { color: var(--ink); }

/* Archive pieces are low resolution: cap the piece itself, not the media box,
   so the rest of the row absorbs the slack and the row stays justified. */
/* F8: low-resolution archive scans never display wider than 640px, so they are
   capped on the flex item itself and never asked to grow past it. */
.piece--archivo { max-width: 640px; flex-grow: 0; }

/* G3: a reel card is a photograph that happens to move. It uses the same flex
   rules and the same caption as .piece--photo — meta line below the media, on
   paper — so it stretches with its row instead of leaving a void beneath it,
   and the caption never lands on the burned-in watermark. The only thing left
   on the poster is the play ring.
   The ring is positioned by giving the button the media's own box: same width,
   same aspect-ratio, therefore the same height. The box itself is click-through
   so tapping the poster still opens the darkroom (section 13); only the ring
   takes the pointer, which is what feed.js binds its handler to. */
.piece--reel { position: relative; }
.piece--reel > .piece__play {
  position: absolute; top: 0; left: 0; right: 0; z-index: 3;
  aspect-ratio: var(--ar);
  display: flex; align-items: center; justify-content: center;
  color: var(--paper);
  pointer-events: none;
}
.piece--reel > .piece__play::before {
  content: "";
  position: absolute;
  width: 64px; height: 64px;
  border: 1.5px solid var(--paper); border-radius: 50%;
  background: rgba(26, 21, 35, 0.28);
  transition: background-color 0.2s;
  pointer-events: auto;
}
.piece--reel > .piece__play:hover::before { background: rgba(26, 21, 35, 0.55); }
.piece--reel > .piece__play svg { position: relative; z-index: 1; }

/* words: when the caption IS the piece, it gets 30px serif on plum */
.piece--words { flex-basis: calc(var(--basis) * 0.95); align-self: stretch; }
.piece--words .piece__title { position: absolute; width: 1px; height: 1px; clip-path: inset(50%); overflow: hidden; }
.piece--words .piece__caption {
  flex: 1;
  display: flex; flex-direction: column; justify-content: space-between;
  gap: 22px;
  background: var(--ink);
  color: var(--lavender);
  padding: 36px 32px;
  min-height: 420px;
}
/* The canvas's Palabras card is a standalone block with the words filling it.
   A #texto piece that also carries a photograph is a different shape: the block
   sits under the image, so it takes only the room its words need instead of
   holding a 420px void open under a one-line caption. */
.piece--words:has(.piece__media) .piece__caption {
  min-height: 0;
  padding: 26px 28px;
  gap: 14px;
  justify-content: flex-start;
}
.piece--words:has(.piece__media) .piece__excerpt {
  font-size: 24px;
  -webkit-line-clamp: 4;
  line-clamp: 4;
}
.piece--words:hover .piece__caption { color: var(--lavender); }
.piece--words .piece__label { font-size: 11px; letter-spacing: 0.3em; color: var(--accent-ink); }
.piece--words .piece__excerpt {
  font-family: var(--serif); font-style: italic;
  font-size: 30px; line-height: 1.25;
  text-transform: none; letter-spacing: 0;
  color: var(--paper);
  /* The clamp is what supplies the ellipsis, and only when there is more text
     than fits — a fixed character count would cut short captions with a "…"
     they had not earned. */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 6;
  line-clamp: 6;
  overflow: hidden;
}
.piece--words .piece__excerpt:empty { display: none; }
.piece--words .piece__meta { display: block; font-size: 11px; letter-spacing: 0.26em; color: var(--muted); }
.piece--words .piece__media { margin-bottom: 0; }

.feed__status { position: absolute; width: 1px; height: 1px; clip-path: inset(50%); overflow: hidden; }
.feed__sentinel { height: 1px; }

/* ---------- pagination ---------- */
.pagination {
  display: flex; align-items: center; justify-content: center;
  gap: 40px;
  padding: 56px var(--pad) 0;
  font-family: var(--sans); font-weight: 300;
  text-transform: uppercase;
  font-size: 12px; letter-spacing: 0.28em;
}
/* F4: the same 44px floor for the pagination controls. */
.pagination a, .pagination span { display: inline-flex; align-items: center; min-height: 44px; }
.pagination__prev, .pagination__next { color: var(--plum); }
.pagination__next { color: var(--accent-paper); }
.pagination__now { color: var(--ink); }
/* Exempt from contrast rules as a disabled control, but it still has to be
   visible enough to read as "there is nothing before this page". */
.pagination [aria-disabled="true"] { color: color-mix(in srgb, var(--plum) 55%, var(--paper)); pointer-events: none; }
.is-dark .pagination__now { color: var(--paper); }

/* ---------- series hero (tag pages) ---------- */
.hero {
  position: relative;
  margin: 8px var(--pad) 0;
  aspect-ratio: 2.4;
  overflow: hidden;
  background: var(--lavender) center / cover;
}
.hero__img { display: block; width: 100%; height: 100%; object-fit: cover; }
.hero__body {
  position: absolute; inset: auto 0 0 0;
  padding: 40px;
  background: linear-gradient(180deg, rgba(26, 21, 35, 0) 0%, rgba(26, 21, 35, 0.6) 45%, rgba(26, 21, 35, 0.88) 100%);
  color: var(--paper);
}
.hero__title {
  font-family: var(--sans); font-weight: 300;
  font-size: 48px; line-height: 1.05; text-transform: uppercase; letter-spacing: 0.02em;
}
.hero__meta { font-size: 11px; letter-spacing: 0.3em; color: var(--lavender); margin-top: 12px; }
.hero__desc { font-size: 19px; line-height: 1.45; max-width: 46ch; margin-top: 12px; color: var(--paper); }

.page-head { padding: 24px var(--pad) 0; }
.page-head__label { font-size: 11px; letter-spacing: 0.3em; color: var(--plum); }
.page-head__title {
  font-family: var(--sans); font-weight: 300;
  font-size: 40px; line-height: 1.05; text-transform: uppercase; margin-top: 12px;
}
.page-head__desc { font-size: 19px; line-height: 1.5; max-width: 52ch; margin-top: 14px; }

/* ---------- series index ---------- */
.series-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--gap);
  padding: 40px var(--pad) 0;
}
.series-card__media {
  position: relative; aspect-ratio: 1.3; overflow: hidden;
  background: var(--lavender) center / cover;
}
.series-card__media img { display: block; width: 100%; height: 100%; object-fit: cover; }
.series-card__name {
  margin-top: 12px;
  font-family: var(--sans); font-weight: 300; text-transform: uppercase;
  font-size: 13px; letter-spacing: 0.24em;
}
.series-card__n { color: var(--plum); opacity: 0.8; margin-left: 0.6em; }
.series-card:hover .series-card__name { color: var(--accent); }

/* ---------- pages (about, contacto, generic) ---------- */
.prose { padding: 32px var(--pad) 0; max-width: 62ch; font-size: 19px; line-height: 1.6; }
.prose:empty { display: none; }
.prose p { margin: 0 0 1.1em; }
.prose a { color: var(--accent-paper); text-decoration: underline; text-underline-offset: 3px; }
.prose h2, .prose h3 {
  font-family: var(--sans); font-weight: 300; text-transform: uppercase;
  letter-spacing: 0.08em; margin: 1.6em 0 0.5em;
}

.about { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 40px; padding: 24px var(--pad) 0; }
.about__portrait { grid-column: span 5; }
.about__portrait img { display: block; width: 100%; height: auto; background: var(--lavender); }
.about__body { grid-column: span 7; }
.about__body .prose { padding: 0; margin-top: 20px; }
.about__contact {
  margin-top: 32px; padding-top: 20px; border-top: 1px solid var(--rule);
  display: flex; flex-wrap: wrap; gap: 12px 32px;
  font-family: var(--sans); font-weight: 300; text-transform: uppercase;
  font-size: 11px; letter-spacing: 0.26em; color: var(--plum);
}
.about__contact a { color: var(--accent-paper); }

/* ---------- the darkroom: one piece at a time ---------- */
.dark { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 32px; padding: 16px var(--pad) 0; }
.dark__left { grid-column: span 8; display: flex; flex-direction: column; gap: 16px; }
.dark__right { grid-column: span 4; display: flex; flex-direction: column; justify-content: space-between; padding: 8px 0 0 8px; }

/* The carousel is native scroll-snap over Ghost's own gallery output: the
   figure / container / row wrappers collapse with display:contents so every
   .kg-gallery-image becomes a snap point of one scroller. It swipes, wheels and
   arrow-keys with JavaScript off. */
.stage-wrap { position: relative; }
.stage {
  display: flex;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  /* width:100% is load-bearing: as a flex item with an aspect-ratio the stage
     stops stretching to its column, and max-height would then pull the WIDTH
     down with it. Pinning the width leaves the ratio to supply the height only,
     which max-height is free to cap. */
  width: 100%;
  aspect-ratio: var(--ar, 1);
  max-height: 72vh;
  background: var(--ink-2);
  scrollbar-width: none;
}
.stage::-webkit-scrollbar { display: none; }
.stage > :not(figure) { display: none; }
.stage figure { display: contents; }
.stage .kg-gallery-container, .stage .kg-gallery-row { display: contents; }
.stage figcaption { display: none; }
/* Ghost's cards.min.js writes an inline `flex: w/h 1 0` on every
   .kg-gallery-image to justify its own three-up rows. Here the row IS the
   carousel, so the inline value has to be overridden — which only !important
   can do against a style attribute. */
.stage .kg-gallery-image,
.stage .kg-image-card > img,
.stage .kg-video-card > div,
.stage .stage__slide {
  flex: 0 0 100% !important;
  scroll-snap-align: center;
  scroll-snap-stop: always;
  min-width: 100%;
  height: 100%;
}
.stage .kg-gallery-image { display: flex; align-items: center; justify-content: center; overflow: hidden; }
.stage .kg-gallery-image img, .stage .kg-image-card > img, .stage__slide img {
  width: 100%; height: 100%; object-fit: contain;
}
/* When the body carries its own images the theme's feature-image slide is a
   duplicate of the first one, so it stands down. */
.stage:has(.kg-gallery-image) .stage__slide,
.stage:has(.kg-image-card) .stage__slide,
.stage:has(.kg-video-card) .stage__slide { display: none; }

.stage__arrow {
  position: absolute; top: 0; bottom: 0; width: 72px; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  color: var(--paper);
}
.stage__arrow--prev { left: 0; }
.stage__arrow--next { right: 0; }
.stage__arrow[hidden] { display: none; }

/* Same content again at 88px: the filmstrip. It is a duplicate of the stage's
   images, so the srcset resolves to the same files and costs no extra bytes. */
.strip {
  --strip-h: 88px;
  display: flex; gap: 10px; height: var(--strip-h);
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.strip::-webkit-scrollbar { display: none; }
.strip > :not(figure) { display: none; }
.strip figure { display: contents; }
.strip .kg-gallery-container, .strip .kg-gallery-row { display: contents; }
.strip figcaption, .strip .kg-embed-card { display: none; }
/* G1: the clip is a slide, so it needs a thumbnail like the photographs, in
   slide order, or W3's script numbers nine thumbnails for ten slides. Ghost
   renders no thumbnail element — the poster exists only as an inline CSS
   background on the <video> itself and in a data- attribute CSS cannot read —
   so the video element is what gets shown, shrunk to the strip's height. Its
   own chrome stands down in favour of one play glyph. */
.strip .kg-video-card .kg-video-overlay,
.strip .kg-video-card .kg-video-player-container { display: none; }
/* Ghost's .kg-video-container refuses to take a height here — it ships
   `height: 0` for its own padding-ratio trick and the flex line stretched it to
   875px inside an 88px strip, through !important and through align-self. So the
   sized box is the link the W2 block wraps it in: our element, our rules, and
   overflow:hidden clips whatever the card does inside it. */
.strip .strip__link {
  display: block;
  flex: 1 1 0; min-width: 48px;
  height: var(--strip-h);
  position: relative; overflow: hidden;
  opacity: 0.55; transition: opacity 0.2s;
}
.strip .strip__link:hover, .strip .strip__link.is-current { opacity: 1; }
.strip .strip__link.is-current { outline: 1.5px solid var(--accent); outline-offset: -1.5px; }
.strip .kg-video-container {
  position: absolute; inset: 0;
  flex: 1 1 0 !important;
  min-width: 48px;
  overflow: hidden;
  opacity: 0.55; transition: opacity 0.2s;
}
/* once wrapped, the link owns the dimming */
.strip .strip__link .kg-video-container { position: absolute; inset: 0; opacity: 1; }
/* The clip is taken out of flow so it cannot inflate the flex line: in flow its
   720x720 intrinsic box made the whole strip 875px tall inside an 88px frame.
   Out of flow the container stretches to the line like the photo thumbnails do,
   and the poster — an inline CSS background Ghost puts on the element — fills
   it. */
.strip .kg-video-container video {
  position: absolute; inset: 0;
  width: 100% !important; height: 100% !important;
  object-fit: cover; display: block;
  background-size: cover !important;
}
.strip .kg-video-container::after,
.strip .strip__link::after {
  content: "";
  position: absolute; left: 50%; top: 50%;
  width: 22px; height: 22px; margin: -11px 0 0 -11px;
  background: no-repeat center / contain
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3E%3Ccircle cx='10' cy='10' r='9' fill='rgba(26,21,35,.55)' stroke='%23F5F2F8' stroke-width='1'/%3E%3Cpolygon points='8,6 14,10 8,14' fill='%23F5F2F8'/%3E%3C/svg%3E");
}
.strip .kg-video-container:hover, .strip .kg-video-container.is-current { opacity: 1; }
.strip .kg-video-container.is-current { outline: 1.5px solid var(--accent); outline-offset: -1.5px; }
.strip .kg-gallery-image {
  flex: 1 1 0 !important; min-width: 48px; overflow: hidden;
  opacity: 0.55; transition: opacity 0.2s;
}
.strip .kg-gallery-image img { width: 100%; height: 100%; object-fit: cover; display: block; }
.strip .kg-gallery-row:first-child .kg-gallery-image:first-child,
.strip .kg-gallery-image.is-current {
  opacity: 1;
  outline: 1.5px solid var(--accent);
  outline-offset: -1.5px;
}
.strip .kg-gallery-image:hover { opacity: 1; }
.strip:not(:has(.kg-gallery-image, .kg-video-container)) { display: none; }

.dark__meta { font-size: 11px; letter-spacing: 0.3em; color: var(--accent-ink); }
.dark__title {
  margin: 16px 0 0;
  font-family: var(--sans); font-weight: 300;
  font-size: 40px; line-height: 1.05; text-transform: uppercase;
  text-wrap: balance; color: var(--paper);
}
.dark__date { margin-top: 14px; font-size: 11px; letter-spacing: 0.28em; color: var(--muted); }

/* A piece the photographer never titled: the <h1> stays for search engines and
   for anyone arriving by keyboard or screen reader, but it is set as the meta
   line it actually is. The separate series and date lines would then say the
   same thing a third time, so they stand down — the assembled title already
   reads "<album> · <date>". */
.dark--sin-titulo .dark__title {
  margin: 0;
  font-size: 11px;
  line-height: 1.5;
  letter-spacing: 0.3em;
  color: var(--accent-ink);
  text-wrap: wrap;
}
.dark--sin-titulo .dark__date { display: none; }
.dark--sin-titulo .dark__meta { font-size: 0; letter-spacing: 0; }
.dark--sin-titulo .dark__meta .dark__count { font-size: 11px; letter-spacing: 0.3em; }
.dark--sin-titulo .dark__text { margin-top: 20px; }
.dark__text { margin-top: 28px; }
.dark__text > figure { display: none; }
.dark__text p {
  font-family: var(--serif);
  font-size: 19px; line-height: 1.5; color: var(--lavender);
  max-width: 34ch;
  margin: 0 0 1em;
}
.dark__text blockquote {
  font-style: italic; color: var(--muted);
  border-left: 1px solid var(--ink-2); padding-left: 16px; margin: 1em 0;
}
.dark__note { margin-top: 18px; font-size: 15px; line-height: 1.5; font-style: italic; color: var(--muted); }
.dark__foot { display: flex; flex-direction: column; gap: 14px; margin-top: 32px; }
/* A words-only piece has no photograph to stand beside: the stage stands down
   and the type takes the page. :has() reads Ghost's rendered body, which is the
   only way a template can know whether {{content}} carries images. */
.dark:not(:has(.stage figure)) .stage-wrap { display: none; }
.dark:not(:has(.stage figure)) .dark__left { display: none; }
.dark:not(:has(.stage figure)) .dark__right { grid-column: 3 / span 8; padding: 24px 0 0; }
.dark:not(:has(.stage figure)) .dark__text p { font-size: 26px; line-height: 1.45; max-width: 32ch; }
.dark:not(:has(.stage figure)) .dark__foot { margin-top: 64px; }
.dark__tags { display: flex; flex-wrap: wrap; gap: 18px; font-size: 11px; letter-spacing: 0.26em; color: var(--lavender); }
.dark__rule { height: 1px; background: var(--ink-2); }
.dark__nav { display: flex; justify-content: space-between; gap: 24px; font-size: 11px; letter-spacing: 0.26em; }
.dark__nav a { flex: 0 1 auto; max-width: 47%; }
.dark__nav a { color: var(--lavender); }
.dark__back { font-size: 11px; letter-spacing: 0.26em; color: var(--muted); margin-top: 8px; }

/* the video card gets its sound and its controls here */
/* The clip is pinned to the slide box rather than sized with height:100%: the
   percentage does not resolve against a flex item here, and Ghost's width/height
   attributes then hand the element a 9:16 intrinsic ratio that overflows the
   stage. inset:0 is definite whatever the container does. */
/* F3: the video slide carries the same sizing the stage does, rather than
   trying to inherit it. height:100% does not resolve against a flex height that
   came from aspect-ratio, and align-self:stretch stretches to the flex LINE —
   which the clip's own intrinsic square had already made 875px tall. Giving the
   slide the stage's ratio and the stage's clamp makes both definite, so nothing
   overflows and Ghost's control bar stays inside the frame instead of sitting
   256px below it, out of reach. */
/* F3: sized exactly like a gallery slide — Ghost's cards.min.css ships
   `.kg-video-container { height: 0 }` for its own padding-ratio trick, which
   left the card as tall as the clip's intrinsic square and pushed the control
   bar 256px below the stage, out of reach. The percentage pair resolves against
   the stage the same way it does for .kg-gallery-image, and overflow:hidden
   guarantees nothing escapes the frame. */
.stage .kg-video-card .kg-video-container { position: relative; height: 100%; }

/* F3: a stage holding a video card is not clamped. The clip's intrinsic size
   sets the flex line, so a 72vh cap on the stage did not shrink the card — it
   only scrolled Ghost's control bar out of the frame, 256px below it and
   unreachable. Letting the stage take the card's height puts the chrome back
   inside, which is what acceptance criterion 10 asks for. Photographs keep the
   cap: they are contained, never cropped, so the cap costs them nothing. */
.stage:has(.kg-video-card) { max-height: none; }
/* Ghost writes the poster onto the <video> as an inline `background: url(...)
   50% 50% / cover`, which fills and crops the slide no matter what object-fit
   the frames themselves get. Only !important reaches past a style attribute. */
.stage .kg-video-card video {
  width: 100% !important;
  height: 100% !important;
  /* Ghost writes `aspect-ratio: 1080 / 1920` inline on the <video>, which wins
     over the stylesheet and stretched the element to 1555px inside a 648px
     stage. The stage already owns the shape (--ar, from the #alto tag), so the
     clip just fills it and object-fit keeps the frames uncropped. */
  aspect-ratio: auto !important;
  object-fit: contain;
  /* the poster is painted as an inline CSS background at `cover` */
  background-size: contain !important;
  background-color: var(--ink) !important;
}

/* ---------- error ---------- */
.err { padding: 120px var(--pad); text-align: center; }
.err__code { font-family: var(--sans); font-weight: 300; font-size: 88px; line-height: 1; color: var(--plum); }
.err__msg { font-family: var(--sans); font-weight: 300; text-transform: uppercase; letter-spacing: 0.28em; font-size: 13px; margin-top: 20px; }
.err__back { display: inline-block; margin-top: 32px; font-family: var(--sans); font-weight: 300; text-transform: uppercase; letter-spacing: 0.28em; font-size: 12px; color: var(--accent-paper); }

/* ---------- footer ---------- */
.ftr {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 24px; flex-wrap: wrap;
  margin: 80px var(--pad) 0;
  padding: 24px 0 48px;
  border-top: 1px solid var(--rule);
  font-family: var(--sans); font-weight: 300; text-transform: uppercase;
  font-size: 11px; letter-spacing: 0.26em; color: var(--plum);
}
.is-dark .ftr { border-top-color: var(--ink-2); }
.ftr a:hover { color: var(--accent-paper); }

/* ---------- density ---------- */
.density-compact { --basis: 330px; --gap: 16px; }

/* ================= responsive ================= */

/* tablet: 600 – 1023, two pieces per row, the spread stacks */
@media (max-width: 1023px) {
  /* 340 is the largest basis at which two square pieces still share a row
     inside the 770px tablet container (2*340 + 20 gap < 770); 380 tips every
     square piece onto a row of its own. */
  :root { --pad: 32px; --gap: 20px; --basis: 340px; }
  .density-compact { --basis: 270px; }
  .spread__media, .spread__words { grid-column: span 12; }
  .spread__words { padding: 20px 0 0; }
  .spread__title { font-size: 44px; }
  .spread__excerpt { font-size: 20px; }
  .hdr__nav { gap: 28px; }
  .dark__left, .dark__right { grid-column: span 12; }
  .dark__right { padding: 24px 0 0; }
  .dark { gap: 24px; }
  .about__portrait, .about__body { grid-column: span 12; }
  .about { gap: 24px; }
  .hero { aspect-ratio: 1.8; }
  .hero__title { font-size: 36px; }
}

/* phone: one column, full bleed, 36px between pieces */
@media (max-width: 599px) {
  :root { --pad: 16px; --gap: 36px; }

  .hdr { padding: 18px var(--pad) 14px; }
  .hdr__badge { width: 34px; height: 34px; }
  .hdr__name { font-size: 11px; letter-spacing: 0.3em; }
  /* In the darkroom the header carries the way back instead of the menu, and
     the two do not fit on one phone line. The badge keeps the identity. */
  .is-dark .hdr__name { display: none; }
  .hdr__back { font-size: 11px; letter-spacing: 0.24em; }
  .hdr__nav { display: none; }
  .hdr__menu { display: block; }
  .hdr__menu > summary {
    list-style: none; cursor: pointer;
    width: 44px; height: 44px;
    display: flex; align-items: center; justify-content: flex-end;
  }
  .hdr__menu > summary::-webkit-details-marker { display: none; }
  .hdr__menu[open] > summary { color: var(--accent); }
  .hdr__panel {
    position: absolute; right: var(--pad); z-index: 20;
    display: flex; flex-direction: column; gap: 4px;
    background: var(--ink); color: var(--paper);
    padding: 12px 28px;
    font-size: 12px; letter-spacing: 0.26em;
  }
  /* F4: the phone menu's own links need the same 44px floor as the desktop nav. */
  .hdr__panel a { color: var(--paper); display: flex; align-items: center; min-height: 44px; }

  .spread { padding: 0; gap: 0; display: block; }
  .spread__media { aspect-ratio: 1; }
  .spread__words { padding: 14px var(--pad) 0; }
  .spread__meta { font-size: 10px; letter-spacing: 0.28em; }
  .spread__title { font-size: 26px; margin-top: 8px; }
  .spread__excerpt { font-size: 19px; margin-top: 16px; max-width: none; }
  .spread__more { margin-top: 20px; }
  .tag-tilt { left: -4px; top: 16px; font-size: 10px; letter-spacing: 0.28em; padding: 5px 12px 4px 14px; }

  .feed__grid { padding: 36px 0 0; }
  .piece { flex-basis: 100%; flex-grow: 1; }
  .piece--words { flex-basis: 100%; margin-inline: var(--pad); }
  .piece--words .piece__caption { padding: 28px 22px; min-height: 0; }
  .piece--words .piece__excerpt { font-size: 22px; line-height: 1.3; }
  .piece__caption { padding: 12px var(--pad) 0; font-size: 10px; letter-spacing: 0.26em; }
  .piece--reel .piece__caption { padding: 0 var(--pad) 16px; }
  .piece--reel > .piece__play { width: 56px; height: 56px; }
  .piece--archivo { max-width: none; }

  .strip-series { margin: 40px var(--pad) 0; flex-direction: column; gap: 14px; }
  .strip-series__list { justify-content: flex-start; }

  .pagination { gap: 24px; padding: 40px var(--pad) 32px; font-size: 11px; letter-spacing: 0.26em; }

  .dark { padding: 12px 0 0; gap: 20px; }
  .dark__right { padding: 0 var(--pad); }
  .dark__title { font-size: 28px; }
  .dark__text p { max-width: none; font-size: 18px; }
  .strip { --strip-h: 64px; padding-inline: var(--pad); }
  .dark__nav { flex-direction: column; gap: 14px; }
  .dark__nav a { max-width: none; }
  .dark:not(:has(.stage figure)) .dark__right { grid-column: span 12; }
  .dark:not(:has(.stage figure)) .dark__text p { font-size: 22px; }
  .stage { max-height: none; }

  .hero { margin: 0; aspect-ratio: 1.1; }
  .hero__body { padding: 24px var(--pad); }
  .hero__title { font-size: 28px; }
  .series-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; padding-top: 28px; }
  .prose { font-size: 18px; }
  .err { padding: 80px var(--pad); }
  .ftr { margin: 56px var(--pad) 0; flex-direction: column; gap: 10px; }
}

/* the phone menu needs a positioned ancestor for its panel */
.hdr { position: relative; }

/* ================= Koenig cards =================
   The editor ships far more cards than this site will ever use: the photographer
   writes with galleries, images, video and paragraphs, and card_assets is
   limited to gallery and video for that reason. The rules below exist so that a
   card inserted by accident, or by a future editor, inherits the site's palette
   and type instead of appearing unstyled. They are deliberately terse. */
.kg-card { margin: 1.4em 0; }
.kg-width-wide { width: 100%; max-width: 100%; }
.kg-width-full { width: 100%; max-width: 100%; }
.kg-align-center { text-align: center; }
.kg-align-left { text-align: left; }
.kg-card figcaption {
  font-family: var(--sans); font-weight: 300; text-transform: uppercase;
  font-size: 11px; letter-spacing: 0.26em; color: var(--plum);
  padding-top: 10px;
}

/* bookmark */
.kg-bookmark-card { margin: 1.4em 0; }
.kg-bookmark-container { display: flex; border: 1px solid var(--rule); color: inherit; text-decoration: none; }
.kg-bookmark-content { flex: 1 1 auto; padding: 20px; min-width: 0; }
.kg-bookmark-title { font-family: var(--sans); font-weight: 500; text-transform: uppercase; letter-spacing: 0.1em; font-size: 14px; }
.kg-bookmark-description { font-size: 15px; line-height: 1.45; color: var(--plum); margin-top: 8px; }
.kg-bookmark-metadata { display: flex; align-items: center; gap: 8px; margin-top: 14px; font-family: var(--sans); font-weight: 300; text-transform: uppercase; font-size: 11px; letter-spacing: 0.2em; color: var(--plum); }
.kg-bookmark-icon { width: 16px; height: 16px; }
.kg-bookmark-author, .kg-bookmark-publisher { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.kg-bookmark-thumbnail { flex: 0 0 180px; }
.kg-bookmark-thumbnail img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* callout */
.kg-callout-card { display: flex; gap: 14px; padding: 20px 22px; background: var(--lavender); color: var(--ink); }
.kg-callout-emoji { flex: none; font-size: 20px; line-height: 1.2; }
.kg-callout-text { font-size: 17px; line-height: 1.5; }
.kg-callout-card-accent { background: var(--accent); color: #FFFFFF; }
.kg-callout-card-grey { background: #E6E0EE; }
.kg-callout-card-white { background: transparent; border: 1px solid var(--rule); }
.kg-callout-card-blue, .kg-callout-card-green, .kg-callout-card-yellow,
.kg-callout-card-red, .kg-callout-card-pink, .kg-callout-card-purple { background: var(--lavender); }

/* toggle */
.kg-toggle-card { border-top: 1px solid var(--rule); border-bottom: 1px solid var(--rule); padding: 16px 0; }
.kg-toggle-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; cursor: pointer; }
.kg-toggle-heading-text { font-family: var(--sans); font-weight: 300; text-transform: uppercase; letter-spacing: 0.2em; font-size: 13px; }
.kg-toggle-card-icon { width: 20px; height: 20px; flex: none; color: var(--plum); }
.kg-toggle-content { padding-top: 12px; font-size: 17px; line-height: 1.55; }

/* button */
.kg-button-card { display: flex; }
.kg-button-card.kg-align-center { justify-content: center; }
.kg-button-card.kg-align-left { justify-content: flex-start; }
.kg-btn {
  display: inline-block; padding: 12px 24px;
  font-family: var(--sans); font-weight: 300; text-transform: uppercase;
  font-size: 12px; letter-spacing: 0.26em;
  border: 1px solid var(--ink); color: var(--ink);
}
.kg-btn-accent { background: var(--accent); border-color: var(--accent); color: #FFFFFF; }

/* blockquote variant */
.kg-blockquote-alt { font-style: italic; font-size: 22px; line-height: 1.4; color: var(--plum); border: 0; padding: 0; }

/* file */
.kg-file-card { display: flex; }
.kg-file-card-container { display: flex; align-items: center; gap: 16px; width: 100%; border: 1px solid var(--rule); padding: 16px 20px; color: inherit; }
.kg-file-card-contents { flex: 1 1 auto; min-width: 0; }
.kg-file-card-title { font-family: var(--sans); font-weight: 500; text-transform: uppercase; letter-spacing: 0.1em; font-size: 13px; }
.kg-file-card-caption { font-size: 15px; color: var(--plum); }
.kg-file-card-filename, .kg-file-card-filesize { font-family: var(--sans); font-weight: 300; text-transform: uppercase; font-size: 11px; letter-spacing: 0.2em; color: var(--plum); }
.kg-file-card-medium .kg-file-card-caption { display: block; }
.kg-file-card-small .kg-file-card-caption { display: none; }

/* audio */
.kg-audio-card { display: flex; }
.kg-audio-player-container { width: 100%; }
.kg-audio-player { display: flex; align-items: center; gap: 12px; border: 1px solid var(--rule); padding: 14px 18px; }
.kg-audio-thumbnail.placeholder { display: flex; align-items: center; justify-content: center; color: var(--plum); }
.kg-audio-thumbnail { width: 56px; height: 56px; object-fit: cover; flex: none; background: var(--lavender); }
.kg-audio-title { font-family: var(--sans); font-weight: 500; text-transform: uppercase; letter-spacing: 0.1em; font-size: 13px; }
.kg-audio-play-icon, .kg-audio-pause-icon, .kg-audio-mute-icon, .kg-audio-unmute-icon { width: 20px; height: 20px; color: var(--ink); }
.kg-audio-seek-slider, .kg-audio-volume-slider { accent-color: var(--accent); }
.kg-audio-time, .kg-audio-current-time, .kg-audio-duration, .kg-audio-playback-rate {
  font-family: var(--sans); font-weight: 300; font-size: 11px; letter-spacing: 0.18em; color: var(--plum);
}

/* before / after */
.kg-before-after-card { position: relative; overflow: hidden; }
.kg-before-after-card-image-before, .kg-before-after-card-image-after { display: block; width: 100%; }

/* product, NFT: never used here, kept legible if one ever appears */
.kg-product-card, .kg-nft-card { display: flex; }
.kg-product-card-container, .kg-nft-card-container { width: 100%; border: 1px solid var(--rule); padding: 22px; color: inherit; }
.kg-product-card-image, .kg-nft-image { width: 100%; height: auto; display: block; }
.kg-product-card-title-container { margin-top: 14px; }
.kg-product-card-title, .kg-nft-title { font-family: var(--sans); font-weight: 500; text-transform: uppercase; letter-spacing: 0.1em; font-size: 14px; }
.kg-product-card-description, .kg-nft-description { font-size: 16px; line-height: 1.5; color: var(--plum); margin-top: 8px; }
.kg-product-card-rating { display: flex; gap: 2px; color: var(--lavender); }
.kg-product-card-rating-star { width: 16px; height: 16px; }
.kg-product-card-rating-active { color: var(--accent); }
.kg-product-card-button { display: inline-block; margin-top: 16px; padding: 12px 24px; border: 1px solid var(--ink); font-family: var(--sans); font-weight: 300; text-transform: uppercase; font-size: 12px; letter-spacing: 0.26em; }
.kg-product-card-btn-accent { background: var(--accent); border-color: var(--accent); color: #FFFFFF; }
.kg-nft-header { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.kg-nft-creator, .kg-nft-metadata { font-family: var(--sans); font-weight: 300; text-transform: uppercase; font-size: 11px; letter-spacing: 0.2em; color: var(--plum); }
.kg-nft-opensea-logo { width: 20px; height: 20px; }

@media (max-width: 599px) {
  .kg-bookmark-container { flex-direction: column-reverse; }
  .kg-bookmark-thumbnail { flex: none; height: 160px; }
}

/* ================= W3: the hooks feed.js and piece.js need ================= */

/* W3 With the script running the reader never meets a page, so the page count
   never appears on screen — not even in the pagination the keyboard can still
   reveal, or the one a failed fetch brings back. Without the js class (no
   JavaScript) it reads normally, which is the only place it is needed. */
html.js .pagination__now { display: none; }

/* W3 The pagination while infinite scroll is working. It is taken out of the
   layout but NOT out of the tab order: the "saltar a paginación" skip link and
   the keyboard still reach it, and the first focus inside brings it back
   (feed.js removes the class on focusin). display:none would have hidden it
   from both. */
.pagination.pf-away {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  clip-path: inset(50%);
  overflow: hidden;
}

/* W3 "Cargar anteriores" (server-rendered on every page but the first, as a
   real link to the previous page) and "Reintentar" (a failed fetch). Both are
   one quiet line in the site's own voice, and both measure the same whether
   the site's own link or the script's button is inside them: the affordance
   must not change height when feed.js takes it over. */
.feed__earlier, .feed__retry {
  display: flex; justify-content: center;
  padding: 24px var(--pad) 0;
  margin: 0;
  font-size: 12px; letter-spacing: 0.28em;
}
.feed__earlier a, .feed__earlier button,
.feed__retry a, .feed__retry button {
  display: flex; align-items: center;
  font-family: var(--sans); font-weight: 300;
  text-transform: uppercase; letter-spacing: inherit; font-size: inherit;
  color: var(--plum);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 4px;
  min-height: 44px;
}
.feed__earlier a:hover, .feed__earlier button:hover,
.feed__retry button:hover { color: var(--accent-paper); }
.feed__retry button { color: var(--accent-paper); }

/* W3 The reel's clip, inserted only while the piece is on screen and removed
   when it leaves. It lies on the poster inside the same box, under the scrim,
   so the gradient and the caption keep reading the same as on the poster. */
.piece__video {
  position: absolute; inset: 0; z-index: 1;
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.piece.is-playing > .piece__play { display: none; }

/* W3 The carousel arrows are disabled at the two ends rather than removed, so
   the control does not move under the pointer. */
.stage__arrow[disabled] { opacity: 0.3; cursor: default; }

/* W3 Once piece.js has wired the filmstrip it owns .is-current, so the
   CSS-only highlight on the first thumbnail (which keeps the strip alive
   before the script runs) stands down. */
.strip.is-live .kg-gallery-row:first-child .kg-gallery-image:first-child:not(.is-current) {
  opacity: 0.55;
  outline: 0;
}
.strip.is-live .kg-gallery-image:hover { opacity: 1; }

/* W3 Restoring a position after back without bfcache: the refetched rows have
   never been painted, so content-visibility would size them from
   contain-intrinsic-size and the restored offset would land short. feed.js
   marks exactly those pieces — pages this reader had already rendered once —
   so they lay out for real; pages appended afterwards keep the auto value. */
.piece.pf-solid { content-visibility: visible; }

/* W3 While the feed still has pages to load, nothing is left in the flow below
   the grid: the footer joins the pagination out of the way, and comes back the
   moment the archive ends, a fetch fails, or the keyboard reaches either of
   them. That is what makes an appended page cost no layout shift at any
   scrolling speed — there is nothing under the grid for it to push. Out of the
   flow, not display:none, so both stay reachable by keyboard. */
.pf-feeding footer.ftr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  clip-path: inset(50%);
  overflow: hidden;
}

