/* ZipLyne OPS FLOOR - overrides layered on top of the deck bundle stylesheet. */

/* ================================================================== *
 * NIGHT SHIFT - the shared token contract (deck + site)
 * Semantic tokens only. These three blocks are the ONLY place in this sheet
 * that names a colour literal; everything below is var(--zl-*).
 *
 * <html data-theme="light|dark"> is stamped by the inline script in the page
 * head before first paint, so there is no flash. The media block is the no-JS
 * fallback and is scoped with :not([data-theme="light"]) so an explicit light
 * choice still wins over a dark OS preference.
 * ================================================================== */

:root {
  --zl-bg: #efeae0;
  --zl-bg-2: #e6dfd0;
  --zl-bg-3: #d9d1bf;
  --zl-cover: #f4d5c2;
  --zl-fg: #0e0e0c;
  --zl-fg-2: #4a4840;
  --zl-line: #0e0e0c;
  --zl-shadow: #0e0e0c;
  --zl-inv-bg: #0e0e0c;
  --zl-inv-fg: #efeae0;
  --zl-accent: #ff5b1f;
  --zl-accent-text: #ae3d0c;
  --zl-accent-soft: #f4d5c2;
  --zl-on-accent: #0e0e0c;
  --zl-tile: url("/assets/zl-pattern-tile.svg");
}

:root[data-theme="dark"] {
  --zl-bg: #17110e;
  --zl-bg-2: #221915;
  --zl-bg-3: #2e211b;
  --zl-cover: #2a1a13;
  --zl-fg: #f1eadf;
  --zl-fg-2: #b9ab9c;
  --zl-line: #f1eadf;
  --zl-shadow: #0b0806;
  --zl-inv-bg: #0b0806;
  --zl-inv-fg: #f1eadf;
  --zl-accent: #ff6a2f;
  --zl-accent-text: #ff8a55;
  --zl-accent-soft: #3a2419;
  --zl-on-accent: #0e0e0c;
  --zl-tile: url("/assets/zl-pattern-tile-dark.svg");
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --zl-bg: #17110e;
    --zl-bg-2: #221915;
    --zl-bg-3: #2e211b;
    --zl-cover: #2a1a13;
    --zl-fg: #f1eadf;
    --zl-fg-2: #b9ab9c;
    --zl-line: #f1eadf;
    --zl-shadow: #0b0806;
    --zl-inv-bg: #0b0806;
    --zl-inv-fg: #f1eadf;
    --zl-accent: #ff6a2f;
    --zl-accent-text: #ff8a55;
    --zl-accent-soft: #3a2419;
    --zl-on-accent: #0e0e0c;
    --zl-tile: url("/assets/zl-pattern-tile-dark.svg");
  }
}

/* Round-2 names kept as aliases so anything still reaching for them (and the
   site repo, which shares this contract) keeps working. New rules use --zl-*. */
:root {
  --ziplyne-bone: var(--zl-bg);
  --ziplyne-bone-2: var(--zl-bg-2);
  --ziplyne-bone-3: var(--zl-bg-3);
  --ziplyne-ink: var(--zl-fg);
  --ziplyne-ink-2: var(--zl-inv-bg);
  --ziplyne-signal: var(--zl-accent);
  --ziplyne-signal-soft: var(--zl-accent-soft);
  --ziplyne-signal-text: var(--zl-accent-text);
}

/* ------------------------------------------------------------------ *
 * Wordmark
 * ------------------------------------------------------------------ */

.site-logo {
  width: clamp(7.5rem, 11vw, 11rem);
  height: auto;
  aspect-ratio: 2400 / 546;
}

.site-logo img,
.slide-logo--cover,
.slide__logo--cover {
  display: block;
  width: 100%;
  height: auto;
  object-fit: contain;
}

/* The fixed wordmark sits on orange on these slides, where its orange "Lyne"
   and bolt would vanish. */
body:has(.slide--workwith.is-active) .site-logo img,
body:has(.slide--howwethink.is-active) .site-logo img {
  filter: grayscale(1) brightness(0);
}

.slide__logo--cover {
  max-width: min(76vw, 64rem);
  margin-inline: auto;
}

/* ------------------------------------------------------------------ *
 * Cover props - key ring on the top-right corner, printout top-left
 * ------------------------------------------------------------------ */

.slide__highlighter {
  width: 14vw;
  top: -3vw;
  right: 3%;
  transform: rotate(-15deg);
}

.slide__note {
  width: 34vw;
}

/* The new props are already on-brand, so no recoloring filters. */
.slide__highlighter img,
.slide__note img {
  filter: none;
}

.slide__note {
  transform: rotate(-12deg);
}

@media (max-width: 992px) {
  .slide__highlighter {
    width: 18vw;
    top: -8vw;
  }

  .slide__note {
    width: 44vw;
  }
}

@media (max-width: 768px) {
  .slide__logo--cover {
    max-width: 84vw;
  }

  .site-logo {
    width: 7.5rem;
  }

  .slide__highlighter {
    width: 24vw;
    top: -6vw;
    right: 0;
  }

  .slide__note {
    width: 56vw;
  }
}

/* ------------------------------------------------------------------ *
 * Slide 2 - REMOVED stamp replaces the eraser (upright, not on its side)
 * ------------------------------------------------------------------ */

.slide__eraser {
  transform: rotate(0deg);
}

.draw-mode-placeholder--eraser {
  filter: none;
  transform: rotate(0deg);
}

/* ------------------------------------------------------------------ *
 * Slide 4 - the clay icon leads each card, over an orange tape eyebrow
 * ------------------------------------------------------------------ */

[data-swiper-what-we-do] .swiper-slide__image {
  flex: 0 0 auto;
  align-self: flex-start;
  width: auto;
  height: 42%;
  max-width: 100%;
  object-fit: contain;
  object-position: left center;
}

/* The icon takes height the card headline used to have, so the headline steps
   down a notch and the inner gap tightens. Otherwise the copy clips. */
[data-swiper-what-we-do] .swiper-slide__inner {
  gap: 0.8rem;
  justify-content: flex-start;
}

/* The grey card label becomes a strip of orange tape. */
[data-swiper-what-we-do] .swiper-slide p {
  align-self: flex-start;
  margin: 0;
  padding: 0.2rem 0.55rem 0.15rem;
  border: 2px solid var(--zl-line);
  background: var(--zl-accent);
  box-shadow: 3px 3px 0 0 var(--zl-shadow);
  color: var(--zl-on-accent);
  font-family: VT323, monospace;
  font-size: clamp(0.85rem, 5.4cqi, 1.25rem);
  line-height: 1.1;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  transform: rotate(-1.2deg);
}

[data-swiper-what-we-do] .swiper-slide .h3 {
  margin-top: auto;
  font-size: clamp(0.85rem, min(6.9cqi, 7.4cqh), 1.7rem);
  line-height: 1.14;
}

/* The icon needs the room, so the card sheds padding rather than the copy. */
[data-swiper-what-we-do] .swiper-slide {
  padding: 1.5rem;
}

@media (max-width: 576px) {
  [data-swiper-what-we-do] .swiper-slide__image {
    height: 34%;
  }

  [data-swiper-what-we-do] .swiper-slide {
    padding: 1.25rem;
  }
}

/* ------------------------------------------------------------------ *
 * Slide 7 - Isaac as a poster, on an orange ground with the bolt tile
 * ------------------------------------------------------------------ */

.slide--howwethink .slide__image-wrapper {
  background-color: var(--zl-accent);
  background-image: url("/assets/zl-pattern-tile.svg");
  background-repeat: repeat;
  background-size: 96px 96px;
}

/* The render carries ~18% transparent margin either side and ~14% above the
   head, so a contained fit left the figure small and centred with dead space
   around it. Oversizing to 175% of the column makes the figure itself about
   110% of the column width: it bleeds off both sides and off the bottom, and
   the negative top offset puts the head just under the caption. Both numbers
   are percentages of the column, so the framing holds at any column size; the
   offset is capped in px so a very tall column cannot push the head off. */
.zl-photo-caption {
  position: absolute;
  top: 1.25rem;
  right: 1.25rem;
  bottom: auto;
  left: auto;
  z-index: 2;
  margin: 0;
  padding: 0.3rem 0.7rem 0.2rem;
  border: 2px solid var(--zl-line);
  background: var(--zl-bg);
  box-shadow: 4px 4px 0 0 var(--zl-shadow);
  font-size: 1.15rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--zl-fg);
  transform: rotate(1.5deg);
}

/* Two-column poster: the photo column is its own positioning context, the
   cutout bleeds off the bottom, and the scrim the mobile layout needs is off. */
@media (min-width: 993px) {
  .slide--howwethink .slide__image-wrapper {
    position: relative;
  }

  .slide--howwethink .slide__image-wrapper:after {
    display: none;
  }

  .slide--howwethink .slide__image-wrapper img {
    position: absolute;
    top: max(-11%, -120px);
    left: 50%;
    width: 175%;
    max-width: none;
    height: auto;
    object-fit: contain;
    transform: translateX(-50%);
  }
}

/* Below 993px the engine makes the photo column a full-bleed backdrop with a
   scrim and bone type over it. We stack instead: bone headline half on top,
   orange cutout band underneath, and the bottom reserved for the status bar. */
@media (max-width: 992px) {
  .slide--howwethink .slide__content--howwethink {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    height: 100%;
    padding-bottom: 165px;
  }

  .slide--howwethink .slide__title-wrapper {
    position: relative;
    z-index: 1;
    flex: 0 0 auto;
    align-items: center;
    background: var(--zl-bg);
    padding: 8.5rem 1.5rem 1.75rem;
  }

  .slide--howwethink .slide__title-wrapper .slide__title--howwethink {
    color: var(--zl-fg);
    text-align: left;
  }

  /* Out of the absolute full-bleed placement and into the flow as a band. */
  .slide--howwethink .slide__image-wrapper {
    position: relative;
    inset: auto;
    flex: 1 1 auto;
    width: 100%;
    max-width: none;
    height: auto;
    min-height: 0;
    overflow: hidden;
  }

  .slide--howwethink .slide__image-wrapper:after {
    display: none;
  }

  /* Fill the band and keep the head anchored at the top, so the crop always
     falls on the torso at the band's bottom edge and the figure can never
     spill sideways however wide the phone or tablet is. */
  .slide--howwethink .slide__image-wrapper img {
    position: absolute;
    inset: 0;
    width: 100%;
    max-width: none;
    height: 100%;
    object-fit: cover;
    object-position: center top;
  }

  .zl-photo-caption {
    top: 0.85rem;
    right: 1rem;
    bottom: auto;
    left: auto;
    padding: 0.25rem 0.55rem 0.15rem;
    font-size: 0.95rem;
    color: var(--zl-fg);
  }
}

/* The status bar is one row down to 577px but wraps to two below that, putting
   its top edge 152px off the bottom instead of 86px. The band has to stop above
   that edge or the notes label and the counter get drawn over Isaac. */
@media (max-width: 576px) {
  .slide--howwethink .slide__content--howwethink {
    padding-bottom: 160px;
  }
}

/* ------------------------------------------------------------------ *
 * Slide 9 - framed product shot on each case-study card
 * ------------------------------------------------------------------ */

/* The stacked card is 200vh tall and clipped, so the frame is sized off the
   height actually left under the headline rather than off the card. */
.zl-case-frame {
  align-self: flex-end;
  flex: 0 0 auto;
  height: clamp(0px, calc(100svh - 36rem), 22vh);
  aspect-ratio: 16 / 9;
  width: auto;
  max-width: min(100%, 24rem);
  margin-top: 1.5rem;
  padding: 0.4rem;
  background: var(--zl-bg);
  border: 3px solid var(--zl-line);
  box-shadow: 6px 6px 0 0 var(--zl-shadow);
  transform: rotate(-3deg);
}

.zl-case-frame img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Unbounded pushed this label past two lines and the card ran off the slide. */
[data-swiper-proof] .swiper-slide__label {
  font-size: clamp(1.65rem, 1.1rem + 2.65vw, 4.2rem);
}

[data-swiper-proof] .swiper-slide:nth-child(3) .zl-case-frame {
  background: var(--zl-accent-soft);
}

@media (max-width: 992px) {
  /* The engine stacks and tilts these cards on mobile; the frame would break the stack. */
  .zl-case-frame {
    display: none;
  }
}

/* ------------------------------------------------------------------ *
 * Case-study drawers - client quote + doors to the full site
 * ------------------------------------------------------------------ */

.project__quote {
  margin: 0;
  padding-left: 1.25rem;
  border-left: 4px solid var(--zl-accent);
  font-size: clamp(1rem, 0.9rem + 0.4vw, 1.25rem);
  line-height: 1.45;
}

.project__quote-cite,
.project__quote-note {
  display: block;
  margin: 0.9rem 0 0;
  font-size: 1.1rem;
  font-style: normal;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--zl-accent-text);
}

.project__quote-note {
  margin: 0;
}

.project__doors {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin: 0;
}

.project__door {
  display: inline-flex;
  align-items: center;
  padding: 0.6rem 0.9rem;
  border: 2px solid var(--zl-line);
  box-shadow: 4px 4px 0 0 var(--zl-shadow);
  background: var(--zl-bg);
  color: var(--zl-fg);
  font-size: 0.85rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;
}

.project__door:hover,
.project__door:focus-visible {
  background: var(--zl-accent);
  transform: translate(2px, 2px);
  box-shadow: 2px 2px 0 0 var(--zl-shadow);
}

.project__content {
  gap: 2rem;
}

/* ------------------------------------------------------------------ *
 * Menu overlay - door to the rest of ziplyne.agency
 * ------------------------------------------------------------------ */

.zl-fullsite {
  border-top: 3px solid var(--zl-line);
  margin-top: 1.5rem;
  padding: 1.25rem 0 0;
}

.zl-fullsite__title {
  margin: 0 0 0.9rem;
  font-size: 1.25rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--zl-accent-text);
}

.zl-fullsite__list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.zl-fullsite__link {
  display: inline-flex;
  align-items: center;
  padding: 0.55rem 0.85rem;
  border: 2px solid var(--zl-line);
  box-shadow: 4px 4px 0 0 var(--zl-shadow);
  background: var(--zl-bg);
  color: var(--zl-fg);
  font-family: VT323, monospace;
  font-size: 1.15rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: none;
}

.zl-fullsite__link:hover,
.zl-fullsite__link:focus-visible {
  background: var(--zl-accent);
  transform: translate(2px, 2px);
  box-shadow: 2px 2px 0 0 var(--zl-shadow);
}

/* ------------------------------------------------------------------ *
 * Status bar
 * ------------------------------------------------------------------ */

.statusbar__slide-no {
  font-family: VT323, monospace;
  font-size: 1.4rem;
  letter-spacing: 0.08em;
}

/* ------------------------------------------------------------------ *
 * CTA modal links
 * ------------------------------------------------------------------ */

.ziplyne-contact-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  margin-top: 0.8rem;
  padding: 0.75rem 1rem;
  border: 2px solid currentColor;
  font-weight: 700;
  text-transform: uppercase;
}

.ziplyne-contact-link:hover,
.ziplyne-contact-link:focus-visible {
  background: var(--zl-accent);
  color: var(--zl-on-accent);
}

/* ------------------------------------------------------------------ *
 * 404 - the wordmark needs an ink plate to survive the orange ground
 * ------------------------------------------------------------------ */

.game-title__logo {
  display: none !important;
}

.game-title [data-title-logo] {
  display: block;
  width: min(80vw, 36rem);
  margin: 0 auto 1.25rem;
  padding: 1.5rem 1.75rem;
  background: var(--zl-inv-bg);
  border: 3px solid var(--zl-line);
  box-shadow: 8px 8px 0 0 rgb(from var(--zl-shadow) r g b / 0.35);
  transform-origin: 50% 50%;
  animation: game-title-logo-float 3.6s ease-in-out 0.55s infinite;
}

.ziplyne-game-logo {
  display: block;
  width: 100%;
  height: auto;
  margin: 0;
  opacity: 0;
}

@media (prefers-reduced-motion: reduce) {
  .game-title [data-title-logo] {
    animation: none !important;
  }
}

/* ------------------------------------------------------------------ *
 * Pacing add-on (ziplyne-deck.js)
 * ------------------------------------------------------------------ */

.zl-rail {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 60;
  display: flex;
  gap: 2px;
  height: 4px;
  padding: 0;
  margin: 0;
  border: 0;
  transition: opacity 0.25s ease;
}

.zl-rail[hidden] {
  display: none !important;
}

/* Slides run bone, signal-soft, orange and ink, so the rail carries its own
   bone track - otherwise orange segments disappear on the orange slide. */
.zl-rail {
  background: rgb(from var(--zl-bg) r g b / 0.72);
}

.zl-rail__seg {
  flex: 1 1 0;
  height: 100%;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
  transition: background 0.2s ease;
}

.zl-rail__seg.is-done {
  background: var(--zl-accent);
}

.zl-rail__seg.is-current {
  background: var(--zl-inv-bg);
}

.zl-rail__seg:hover,
.zl-rail__seg:focus-visible {
  background: var(--zl-inv-bg);
}

.zl-toast {
  position: fixed;
  left: 50%;
  bottom: calc(86px + 3.25rem);
  z-index: 70;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.6rem;
  padding: 0.85rem 1rem;
  background: var(--zl-bg);
  border: 3px solid var(--zl-line);
  box-shadow: 6px 6px 0 0 var(--zl-shadow);
  transform: translateX(-50%);
  font-family: VT323, monospace;
  font-size: 1.2rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--zl-fg);
}

.zl-toast[hidden] {
  display: none !important;
}

.zl-toast__btn {
  padding: 0.35rem 0.7rem;
  border: 2px solid var(--zl-line);
  background: var(--zl-bg);
  box-shadow: 3px 3px 0 0 var(--zl-shadow);
  font: inherit;
  color: inherit;
  cursor: pointer;
}

.zl-toast__btn:hover,
.zl-toast__btn:focus-visible {
  background: var(--zl-accent);
  transform: translate(1px, 1px);
  box-shadow: 2px 2px 0 0 var(--zl-shadow);
}

.zl-skip {
  position: fixed;
  right: 1.5rem;
  bottom: calc(86px + 1.5rem);
  z-index: 65;
  padding: 0.5rem 0.85rem;
  border: 2px solid var(--zl-line);
  box-shadow: 4px 4px 0 0 var(--zl-shadow);
  background: var(--zl-bg);
  color: var(--zl-fg);
  font-family: VT323, monospace;
  font-size: 1.2rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.3s ease;
}

.zl-skip.is-visible {
  opacity: 1;
}

.zl-skip[hidden] {
  display: none !important;
}

/* Off reads recessed and bone, on reads raised and orange - same grammar as the sound toggle. */
.zl-fast[aria-pressed="false"] {
  --shadow: 0 0 0 0 var(--zl-shadow);
  box-shadow: var(--shadow);
  background-color: var(--zl-bg);
  transform: translateY(4px);
}

.zl-fast[aria-pressed="false"]:hover,
.zl-fast[aria-pressed="false"]:active {
  --shadow: 0 0 0 0 var(--zl-shadow);
  box-shadow: var(--shadow);
  transform: translateY(4px);
}

.zl-fast__glyph {
  display: block;
  width: 20px;
  height: 20px;
}

@media (prefers-reduced-motion: reduce) {
  .zl-rail,
  .zl-rail__seg,
  .zl-skip {
    transition: none !important;
  }

  .zl-skip {
    opacity: 1;
  }
}

/* ------------------------------------------------------------------ *
 * Type scale - Unbounded 800 sets ~29% wider than BN Dime Display at the
 * same size, so the deck's clamps are pulled back to keep the original
 * line counts and stop headlines running into the edges.
 * ------------------------------------------------------------------ */

h1,
h2,
h3,
h4,
h5,
h6,
.h0,
.h1,
.h2,
.h3 {
  letter-spacing: -0.015em;
}

.h0 {
  font-size: clamp(4.1rem, 2.6rem + 7.4vw, 11.5rem);
}

h1,
.h1 {
  font-size: clamp(2.65rem, 2.1rem + 3.7vw, 6.4rem);
}

h2,
.h2 {
  font-size: clamp(1.65rem, 1.1rem + 2.65vw, 4.2rem);
}

h3,
.h3 {
  font-size: clamp(1.25rem, 1.05rem + 0.85vw, 2.05rem);
}

h4,
.h4 {
  font-size: clamp(1.05rem, 0.95rem + 0.4vw, 1.45rem);
}

/* Each rule has to hold one line, or the block grows tall enough to push the
   eyebrow up into the fixed top-centre wordmark. */
.slide__statements .h2 {
  font-size: clamp(1.25rem, 0.55rem + 2.55vw, 3.6rem);
}

.slide__title--whatwedo {
  font-size: clamp(1.65rem, 9.8cqh, 6.5rem);
}

.slide__title--workwith {
  font-size: clamp(2rem, 16cqh, 8rem);
}

.project__title-block {
  font-size: clamp(1.65rem, 1.1rem + 2.65vw, 4.2rem);
}

.btn--big {
  font-size: 1.25rem;
}

@media (max-width: 576px) {
  .slide__statements .h2 {
    font-size: clamp(1.45rem, 1rem + 3.4vw, 2.25rem);
  }
}

@media (max-width: 768px) {
  [data-swiper-proof] .swiper-slide__label {
    font-size: clamp(1.65rem, 1.1rem + 2.65vw, 4.2rem);
  }
}

/* "BOTTLENECK." is one unbreakable word; the container-query size ignores
   width, so cap it against the viewport on narrow screens. */
@media (max-width: 576px) {
  .slide__title--whatwedo {
    font-size: clamp(1.5rem, min(9.8cqh, 8vw), 3rem);
  }
}

/* Slide 2 - keep the stamp clear of the right edge. */
.slide__eraser {
  right: 2%;
}

/* ------------------------------------------------------------------ *
 * Fix batch - mobile slide 6, status bar, slide 3 marker, slide 6 figures
 * ------------------------------------------------------------------ */

/* Slide 6 title: Unbounded pushed "WE WORK WITH" to three lines, and because
   the cards keep their aspect ratio inside a size container with
   overflow:visible, the extra line ended up behind the first card. Two lines
   plus a smaller card cap keeps them clear of each other. */
@media (max-width: 768px) {
  .slide__title--workwith {
    flex: 0 0 auto;
    font-size: clamp(1.75rem, min(9cqh, 11vw), 8rem);
  }

  [data-swiper-workwith] {
    margin-top: 0.75rem;
    padding-top: 0;
  }

  [data-swiper-workwith] .swiper-slide {
    max-height: calc(100cqh - 13rem);
  }
}

/* Status bar: the fast-mode button is a third control the original 200px
   info column never budgeted for, so at phone widths the counter was clipped.
   Size the column to its content and shrink the round buttons. */
@media (max-width: 576px) {
  .statusbar__info {
    width: auto;
    gap: 0.35rem;
  }

  .statusbar .sound-toggle.btn-round {
    width: 2.25rem;
    height: 2.25rem;
  }

  .statusbar .sound-toggle__icon,
  .statusbar .zl-fast__glyph {
    width: 17px;
    height: 17px;
  }

  .statusbar__slide-no {
    flex: none;
    font-size: 1.1rem;
    letter-spacing: 0;
    white-space: nowrap;
  }
}

/* Slide 3 marker: it was a free-floating SVG pinned to bottom:25% of the slide
   and sized to font-size x a constant, which under Unbounded rendered as a
   ~420x110 smear across lines 2 and 3. It is now a highlighter swipe drawn on
   the last rule's own word with the same :after mechanism slide 2 uses, so it
   tracks that word at any line count, type scale or viewport. */
.highlight--beliave {
  position: relative;
  top: auto;
  right: auto;
  bottom: auto;
  left: auto;
  z-index: auto;
  transform: none;
}

/* Geometry against Unbounded in a .98 line box: baseline lands ~.865em down and
   cap height is ~.72em, so this starts just above the caps and finishes a touch
   below the baseline, reaching ~.4em left of the P and just past the period. */
.highlight--beliave:after {
  content: "";
  position: absolute;
  z-index: -1;
  top: -0.08em;
  bottom: -0.14em;
  left: -0.4em;
  right: -0.16em;
  clip-path: inset(0 calc(100% - var(--highlight-progress)) 0 0);
  background-image: url(/assets/zl-marker-stroke.svg);
  background-repeat: no-repeat;
  background-size: 100% 100%;
  pointer-events: none;
}

/* Slide 6 figures read small inside the cards because the renders carry
   transparent margins, so the card sheds padding and the image takes the full
   inner width. The scale that used to make up the difference is gone: a
   paint-only scale overflows its own box, which is exactly what put the figure
   on top of the label. A real gap is the only thing that guarantees clearance. */
[data-swiper-workwith] .swiper-slide__card {
  gap: 1.35rem;
}

@media (min-width: 993px) {
  [data-swiper-workwith] .swiper-slide__card {
    padding: 0.75rem 0.75rem 1.5rem;
    overflow: hidden;
  }

  [data-swiper-workwith] .swiper-slide__image {
    transform: none;
    width: 100%;
  }

  [data-swiper-workwith] .swiper-slide__card .h3,
  [data-swiper-workwith] .swiper-slide__card p {
    position: relative;
    z-index: 1;
  }
}

@media (max-width: 992px) {
  [data-swiper-workwith] .swiper-slide__card {
    padding: 1.25rem 1.25rem 1.75rem;
  }
}

/* ------------------------------------------------------------------ *
 * Fix batch - mobile cover chrome
 * ------------------------------------------------------------------ */

/* SKIP sat bottom-right, which on a phone is exactly where the sound, fast
   and counter controls are. Round 3 gave the top-right corner to contact, so it
   queues under that pair instead: it only ever shows on the cover, where the
   pair itself starts below the key tag at 29.8vw. */
@media (max-width: 768px) {
  .zl-skip {
    top: calc(29.8vw + 0.75rem + 4.5rem);
    right: 1rem;
    bottom: auto;
  }

  /* The wordmark's descender was landing on the tagline's first line. */
  .slide__tagline--cover {
    margin-top: 0.625rem;
  }
}

/* The phone status bar is two rows (~120px tall, top edge ~152px off the
   bottom), so the resume toast has to clear that, not the 86px single row. */
@media (max-width: 576px) {
  .zl-toast {
    bottom: calc(152px + 1rem);
    max-width: calc(100vw - 2rem);
  }
}

/* Slide 7 on phones: the engine's statusbar--howwethink modifier paints the
   status text bone because the desktop photo column sits behind it. In the
   stacked mobile layout the status bar sits on the bone ground, so it must
   return to ink. */
@media (min-width: 993px) {
  .statusbar--howwethink #nav-slide-indicator {
    padding: 0.1rem 0.5rem;
    background: var(--zl-inv-bg);
    white-space: nowrap;
  }
}

@media (max-width: 992px) {
  .statusbar--howwethink #nav-slide-indicator,
  .statusbar--howwethink #nav-notes-btn .notes-button__default,
  .statusbar--howwethink #nav-notes-btn .notes-button__hover {
    color: var(--zl-fg);
  }
}

/* ================================================================== *
 * ROUND 2
 * ================================================================== */

/* ------------------------------------------------------------------ *
 * Cover - the tagline is the promise, so it gets read before the props
 * ------------------------------------------------------------------ */

/* Scoped to the cover: the other eyebrows sit under headlines that already
   carry the label voice, and turning them all mono would be a second design
   change nobody asked for. */
.slide--cover .slide__eyebrow {
  font-family: VT323, monospace;
  font-size: 1.5rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--zl-accent-text);
}

.slide__tagline--cover {
  width: 40rem;
  max-width: 82vw;
  margin-top: 2.75rem;
  font-family: "Instrument Sans", "Instrument Sans Fallback", sans-serif;
  font-weight: 600;
  font-size: 1.35rem;
  line-height: 1.35;
}

@media (max-width: 992px) {
  .slide--cover .slide__eyebrow {
    font-size: 1.3rem;
  }

  .slide__tagline--cover {
    margin-top: 2rem;
    font-size: 1.15rem;
  }
}

/* The mobile fix batch pulled the tagline back up under the wordmark's
   descender; the new gap is what keeps them apart, so that nudge goes. */
@media (max-width: 768px) {
  .slide__tagline--cover {
    margin-top: 1.75rem;
  }
}

/* ------------------------------------------------------------------ *
 * Slide 3 - the three rules as orange tape chips, 1 to 3, left to right
 * ------------------------------------------------------------------ */

.zl-rules {
  display: flex;
  flex-wrap: nowrap;
  justify-content: center;
  align-items: stretch;
  gap: 0.85rem;
  width: min(94vw, 78rem);
  text-align: left;
}

.zl-rule {
  display: flex;
  flex: 1 1 0;
  align-items: center;
  gap: 0.75rem;
  margin: 0;
  padding: 0.55rem 0.85rem 0.55rem 0.55rem;
  border: 3px solid var(--zl-line);
  background: var(--zl-accent);
  box-shadow: 5px 5px 0 0 var(--zl-shadow);
  color: var(--zl-on-accent);
  text-transform: none;
}

.zl-rule:nth-child(1) { transform: rotate(-1.1deg); }
.zl-rule:nth-child(2) { transform: rotate(0.6deg); }
.zl-rule:nth-child(3) { transform: rotate(-0.5deg); }

.zl-rule__no {
  display: grid;
  flex: 0 0 auto;
  place-items: center;
  width: 2.1rem;
  height: 2.1rem;
  background: var(--zl-inv-bg);
  color: var(--zl-accent);
  font-family: VT323, monospace;
  font-size: 1.5rem;
  line-height: 1;
}

.zl-rule__text {
  font-family: "Instrument Sans", "Instrument Sans Fallback", sans-serif;
  font-weight: 600;
  font-size: 1.05rem;
  line-height: 1.2;
}

@media (max-width: 992px) {
  .zl-rules {
    flex-wrap: wrap;
    gap: 0.6rem;
  }

  .zl-rule {
    flex: 1 1 14rem;
  }

  .zl-rule__text {
    font-size: 0.95rem;
  }
}

/* The engine hides this footnote row below 576px because the three rules
   themselves already fill a phone screen; as chips they are taller still, so the
   hide stays. Setting display on .zl-rules would otherwise override it. */
@media (max-width: 576px) {
  .slide__footnotes--believe.zl-rules {
    display: none;
  }
}

/* ------------------------------------------------------------------ *
 * Slide 4 - the headline gets its own air above the cards
 * ------------------------------------------------------------------ */

.slide__content--whatwedo {
  gap: clamp(0.75rem, 5.5cqh, 4.5rem);
}

[data-swiper-what-we-do] .swiper-slide {
  max-height: calc(100cqh - 9rem);
}

/* ------------------------------------------------------------------ *
 * Slide 9 - one button you can click, one that will not let you
 * ------------------------------------------------------------------ */

.cta-actions {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2rem;
  width: 100%;
}

/* The runaway button roams inside its own zone, so it can never end up parked
   on top of the primary one. That overlap was what made "Start a project"
   impossible to click. */
.cta-actions .cta-button-zone {
  flex: 0 1 34rem;
  width: auto;
  max-width: 34rem;
  min-width: 18rem;
  min-height: 13rem;
}

/* Round 3: the zone is also what keeps the runaway button off the 3D mark. The
   engine clamps the button to this box (its right edge lands 8px inside the
   zone's), so reserving the mark's column here is the whole fix. The reserve is
   the mark's own geometry: height 38vh capped at width 40vw, rotated -8deg,
   which widens the box by cos8 + sin8 = 1.129, plus its 1vw offset and a gap.
   The zone stays centred in what is left, so centring can only pull it further
   from the mark, never towards it. Below 769px the mark is a faint watermark
   behind the headline and the buttons stack, so the reserve is desktop-only. */
@media (min-width: 769px) {
  .cta-actions {
    padding-right: calc(min(42.9vh, 45.2vw) + 1vw + 1rem);
  }
}

.cta-actions .zl-cta-primary {
  flex: 0 0 20rem;
  pointer-events: auto;
  cursor: pointer;
  background: var(--zl-accent);
  color: var(--zl-on-accent);
}

.zl-cta-runaway {
  width: auto;
  max-width: 17rem;
  background: var(--zl-bg);
}

@media (max-width: 768px) {
  .cta-actions {
    flex-direction: column;
    gap: 1rem;
  }

  .cta-actions .cta-button-zone {
    flex: 0 0 auto;
    min-height: 8rem;
  }

  .cta-actions .zl-cta-primary {
    flex: 0 0 auto;
  }
}

/* ------------------------------------------------------------------ *
 * Contact modal
 * ------------------------------------------------------------------ */

.zl-contact-form {
  display: flex;
  flex-direction: column;
  gap: 0.85rem;
  width: 100%;
  text-align: left;
  text-transform: none;
}

.zl-field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.zl-field__label {
  font-size: 1.1rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--zl-accent-text);
}

.zl-field__input {
  box-sizing: border-box;
  width: 100%;
  padding: 0.7rem 0.8rem;
  border: 3px solid var(--zl-line);
  border-radius: 0;
  background: var(--zl-bg);
  box-shadow: inset 3px 3px 0 0 rgb(from var(--zl-shadow) r g b / 0.12);
  font-family: "Instrument Sans", "Instrument Sans Fallback", sans-serif;
  font-size: 1rem;
  color: var(--zl-fg);
}

.zl-field__input--area {
  resize: vertical;
  min-height: 5rem;
}

.zl-field__input:focus-visible {
  outline: 3px solid var(--zl-accent);
  outline-offset: 2px;
}

.zl-contact-send {
  align-self: flex-start;
  padding: 0.85rem 1.6rem;
  border: 3px solid var(--zl-line);
  background: var(--zl-accent);
  box-shadow: 5px 5px 0 0 var(--zl-shadow);
  color: var(--zl-on-accent);
  font-weight: 700;
  text-transform: uppercase;
  cursor: pointer;
}

.zl-contact-send:hover,
.zl-contact-send:focus-visible {
  transform: translate(2px, 2px);
  box-shadow: 3px 3px 0 0 var(--zl-shadow);
}

.zl-contact-send[disabled] {
  opacity: 0.55;
  cursor: default;
  transform: none;
}

.zl-contact-status {
  margin: 0;
  min-height: 1.4em;
  font-family: VT323, monospace;
  font-size: 1.2rem;
  letter-spacing: 0.04em;
  color: var(--zl-accent-text);
}

.zl-contact-status a {
  color: inherit;
}

.zl-contact-foot {
  width: 100%;
  margin: 1rem 0 0;
  text-align: left;
  text-transform: none;
  font-size: 0.95rem;
}

.zl-contact-modal .modal__title {
  align-self: flex-start;
  max-width: 100%;
  margin-bottom: 1.25rem;
  text-align: left;
}

/* The sike modal's "tell them anyway" door is a button, not a link. */
button.ziplyne-contact-link {
  background: var(--zl-bg);
  cursor: pointer;
  font: inherit;
  font-weight: 700;
}

/* ------------------------------------------------------------------ *
 * Persistent chrome - contact on every slide, plus the way out
 * ------------------------------------------------------------------ */

/* Round 3: the top-right corner, on every slide and at every viewport. It used
   to park above the status bar, where the prev/menu/next cluster, the sound and
   speed toggles and the slide counter all crowded it. The corner holds these two
   and nothing else; the door to the regular site is the smaller chip under the
   pill. */
.zl-chrome {
  position: fixed;
  top: 1.25rem;
  right: 1.5rem;
  bottom: auto;
  left: auto;
  z-index: 66;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.4rem;
  transition: opacity 0.25s ease;
}

/* The cover's key-ring prop hangs into that corner. Measured off the rendered
   prop, its bottom edge sits at 17.9vw (18.9vw under 992px, 29.8vw under 768px,
   where the prop's own width and offset change), so on the cover alone the pair
   starts just below it rather than on it. Same corner, later start. */
body:has(.slide--cover.is-active) .zl-chrome {
  top: calc(17.9vw + 0.75rem);
}

@media (max-width: 992px) {
  body:has(.slide--cover.is-active) .zl-chrome {
    top: calc(18.9vw + 0.75rem);
  }
}

.zl-chrome[hidden] {
  display: none !important;
}

.zl-contact-btn {
  max-width: none;
  white-space: nowrap;
  padding: 0.55rem 0.9rem;
  border: 3px solid var(--zl-line);
  background: var(--zl-bg);
  box-shadow: 5px 5px 0 0 var(--zl-shadow);
  color: var(--zl-fg);
  font-family: "Instrument Sans", "Instrument Sans Fallback", sans-serif;
  font-weight: 700;
  font-size: 0.95rem;
  line-height: 1.15;
  text-align: left;
  cursor: pointer;
  transform: rotate(-1deg);
}

.zl-contact-btn:hover,
.zl-contact-btn:focus-visible {
  background: var(--zl-accent);
  transform: rotate(-1deg) translate(2px, 2px);
  box-shadow: 3px 3px 0 0 var(--zl-shadow);
}

.zl-chrome .zl-regular {
  flex: 0 0 auto;
  max-width: calc(100vw - 3rem);
  white-space: nowrap;
  padding: 0.2rem 0.45rem 0.15rem;
  border: 2px solid var(--zl-line);
  background: var(--zl-bg);
  box-shadow: 3px 3px 0 0 var(--zl-shadow);
  color: var(--zl-accent-text);
  font-size: 0.85rem;
  text-align: right;
  text-decoration: none;
}

/* The confirm state appends "Yes, take me" to a longer question, so the chip is
   allowed to wrap rather than run off the edge of the corner. */
.zl-chrome .zl-regular.is-asking {
  white-space: normal;
}

.zl-chrome .zl-regular:hover,
.zl-chrome .zl-regular:focus-visible {
  background: var(--zl-accent);
  color: var(--zl-on-accent);
}

.zl-regular {
  padding: 0.2rem 0.1rem;
  border: 0;
  background: none;
  color: var(--zl-accent-text);
  font-family: VT323, monospace;
  font-size: 1.1rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}

.zl-regular:hover,
.zl-regular:focus-visible {
  color: var(--zl-fg);
}

.zl-regular.is-asking {
  color: var(--zl-fg);
  text-decoration: none;
}

.zl-regular__yes {
  margin-left: 0.5rem;
  color: var(--zl-accent-text);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
  background: none;
  border: 0;
  padding: 0;
}

/* In the menu overlay the same control is a full tape button. */
.zl-fullsite .zl-regular {
  display: inline-flex;
  align-items: center;
  margin-top: 1rem;
  padding: 0.55rem 0.85rem;
  border: 2px solid var(--zl-line);
  background: var(--zl-bg);
  box-shadow: 4px 4px 0 0 var(--zl-shadow);
  color: var(--zl-fg);
  font-size: 1.15rem;
  text-decoration: none;
}

.zl-fullsite .zl-regular:hover,
.zl-fullsite .zl-regular:focus-visible {
  background: var(--zl-accent);
  transform: translate(2px, 2px);
  box-shadow: 2px 2px 0 0 var(--zl-shadow);
}

/* SKIP now shares the bottom-right corner with the contact chrome, and the
   three rule chips on slide 3 run along the same band, so both move clear. */
@media (min-width: 769px) {
  .zl-skip {
    bottom: calc(86px + 8.5rem);
  }

  .slide__footnotes--believe {
    bottom: calc(86px + 9.5rem);
  }
}

/* On a phone the top-right corner is shared with the centred wordmark, so the
   pair starts below it instead of beside it. */
@media (max-width: 768px) {
  /* Clear of the fixed wordmark above (its lower edge is 59px) and of the
     highest slide headline below it, which starts at 136px on slide 7. */
  .zl-chrome {
    top: 4.25rem;
    right: 1rem;
    left: auto;
    bottom: auto;
    gap: 0.3rem;
    transform: none;
  }

  body:has(.slide--cover.is-active) .zl-chrome {
    top: calc(29.8vw + 0.75rem);
  }

  /* One line at any of the four rotating strings, so the pill's height is
     predictable and it always clears the slide headline below it. */
  .zl-contact-btn {
    max-width: 17rem;
    padding: 0.4rem 0.7rem;
    font-size: 0.72rem;
  }

  .zl-chrome .zl-regular {
    max-width: calc(100vw - 2rem);
    font-size: 0.72rem;
  }
}

@media (prefers-reduced-motion: reduce) {
  .zl-chrome {
    transition: none !important;
  }
}

/* ================================================================== *
 * ROUND 2b - the 3D object world
 * ================================================================== */

/* ------------------------------------------------------------------ *
 * Cover - the glossy inflated wordmark
 * The render carries ~27% empty canvas above the letters and ~30% below
 * (glow plus contact shadow), so the box is collapsed back onto the letter
 * block with negative margins derived from the same width the image is sized
 * by. Without that the eyebrow and tagline get pushed off the slide. The
 * factors are the transparent bands measured off the file: 0.273 and 0.304 of
 * the rendered height, which is width x 1216/2176.
 * `.slide__content--cover` is a flex column, so these margins never collapse
 * with the tagline's and the gap from task 1 is preserved exactly.
 * ------------------------------------------------------------------ */

.slide__logo--cover {
  --zl-logo3d-w: min(84vw, 75rem);
  width: var(--zl-logo3d-w);
  max-width: var(--zl-logo3d-w);
  margin-top: calc(var(--zl-logo3d-w) * -0.1526);
  margin-bottom: calc(var(--zl-logo3d-w) * -0.1699);
  margin-inline: auto;
}

/* The 76vw/64rem cap belonged to the flat wordmark; the var above replaces it
   at every width, including the phone override further up this sheet. On a
   phone the flat wordmark already used most of the width, so the extra room
   for a bigger 3D lockup comes out of the cover's own gutters. */
@media (max-width: 768px) {
  .slide--cover .container {
    padding-left: 1rem;
    padding-right: 1rem;
  }

  .slide__logo--cover {
    --zl-logo3d-w: min(92vw, 75rem);
    max-width: var(--zl-logo3d-w);
  }
}

/* ------------------------------------------------------------------ *
 * Slide 9 (CTA) - the Z-bolt mark as the slide's hero object
 * It sits in the empty lower-right quadrant rather than directly behind the
 * headline: black display type over a glossy black-and-orange object is
 * unreadable, and the void under the buttons is what the slide was missing.
 * ------------------------------------------------------------------ */

.zl-hero-mark {
  position: absolute;
  right: 1vw;
  bottom: 19vh;
  z-index: 0;
  width: auto;
  height: 38vh;
  max-width: 40vw;
  pointer-events: none;
  transform: rotate(-8deg);
  filter: drop-shadow(10px 14px 0 rgb(from var(--zl-shadow) r g b / 0.12));
}

/* The pattern tile competes with the object, so it steps back on this slide. */
.slide--cta .background-pattern-wrapper .background-pattern--base {
  opacity: 0.4;
}

/* The headline, taunt and buttons all sit above the object. */
.slide__content--cta {
  position: relative;
  z-index: 1;
}

/* Below 768px the CTA has no free quadrant: the headline runs four lines, the
   two buttons stack, and the two-row status bar takes the rest, leaving 76px
   between the contact chrome and the headline. So on a phone the object stops
   competing and becomes a watermark behind the headline instead, at the 22vh
   the brief asks for. */
@media (max-width: 768px) {
  .zl-hero-mark {
    top: 13vh;
    right: -6vw;
    bottom: auto;
    height: 22vh;
    max-width: 60vw;
    opacity: 0.16;
    filter: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .zl-hero-mark {
    filter: none;
  }
}

/* ------------------------------------------------------------------ *
 * Contact modal - a mail icon on the title, clay Isaac peeking in
 * The clay wordmark was the other option for the header; the mail icon wins
 * because it says what the modal does rather than repeating the branding.
 * ------------------------------------------------------------------ */

.zl-contact-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  width: 100%;
  margin-bottom: 1.25rem;
}

.zl-contact-head__icon {
  flex: 0 0 auto;
  width: 3.5rem;
  height: 3.5rem;
  object-fit: contain;
  transform: rotate(-6deg);
}

.zl-contact-modal .zl-contact-head .modal__title {
  margin: 0;
}

.zl-contact-isaac {
  position: absolute;
  right: 0.5rem;
  bottom: 0;
  z-index: 0;
  width: 120px;
  height: auto;
  pointer-events: none;
  user-select: none;
}

/* Everything typed into stays above the figurine. */
.zl-contact-modal .zl-contact-head,
.zl-contact-form,
.zl-contact-foot {
  position: relative;
  z-index: 1;
}

/* The footer line would run under the figurine's feet on a narrow modal. */
.zl-contact-foot {
  padding-right: 7.5rem;
}

@media (max-width: 576px) {
  .zl-contact-isaac {
    width: 88px;
  }

  .zl-contact-foot {
    padding-right: 5.5rem;
  }

  .zl-contact-head__icon {
    width: 2.75rem;
    height: 2.75rem;
  }
}

/* ================================================================== *
 * ROUND 3 - the top-right corner belongs to contact
 * The persistent pill and its door chip are now fixed in that corner on every
 * slide, so the three props that used to live there step out of the way. Each
 * moves rather than the chrome, because the chrome not moving is the point.
 * ================================================================== */

@media (min-width: 769px) {
  /* Slide 2: the REMOVED stamp hung from top: 2rem and the door chip landed on
     its handle. Dropping it clears the chip by ~13px and still finishes ~19px
     above the headline. */
  .slide__eraser {
    top: 6rem;
  }
}

/* Slide 7: the name plate was top-right of the photo column, directly under the
   pill. It keeps the right rail and moves to the foot of the photo, clear of
   both the chrome above and the status bar below. */
@media (min-width: 993px) {
  .zl-photo-caption {
    top: auto;
    right: 1.25rem;
    bottom: 9rem;
  }
}

/* Slide 2 on a phone: the stamp shares the top-right with the contact pair and
   there is no room for both, so it crosses to the empty top-left corner. The
   centred wordmark starts well right of it at every width in the range. */
@media (max-width: 768px) {
  .slide__eraser {
    top: 2rem;
    right: auto;
    left: 2%;
  }
}

/* ================================================================== *
 * NIGHT SHIFT - theme plumbing
 * The engine was written for one ground, so most foregrounds were left to the
 * UA default. With a ground that flips, every surface has to name its own.
 * The values below are what light already renders, so almost none of this is
 * dark-only: it just stops the page relying on "black happens to be right".
 * ================================================================== */

html {
  color-scheme: light;
}

:root[data-theme="dark"] {
  color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
  }
}

html,
body {
  background-color: var(--zl-bg);
  color: var(--zl-fg);
}

/* Orange grounds keep an ink foreground in both themes: --zl-on-accent never
   moves, because orange never moves far enough for bone to be legible on it. */
.btn,
.slide--workwith,
[data-swiper-proof] .swiper-slide:nth-child(2),
.slide--howwethink .slide__image-wrapper {
  color: var(--zl-on-accent);
}

/* Bone grounds, which become umber at night and take the foreground with them. */
.nav__arrow,
.nav-overlay__tray,
.nav-overlay__close,
.statusbar__notes-body,
.scroll-nav,
.modal__content,
.project,
.slide__donts-wrapper,
.slide__404-link,
dialog[data-modal] button[data-modal-close],
.projects__close,
.sound-toggle.is-muted,
[data-swiper-proof] .swiper-slide button,
[data-swiper-what-we-do] .swiper-slide,
[data-swiper-workwith] .swiper-slide__card {
  color: var(--zl-fg);
}

/* Slide 7's right-hand column is orange in both themes (the brief keeps it), so
   its tile stays the ink cut rather than following --zl-tile onto bone. */
.slide--howwethink .slide__image-wrapper {
  background-image: url("/assets/zl-pattern-tile.svg");
}

/* Black-clay props read as holes punched in a dark ground, so they get a hairline
   bone rim at night. Shared contract with the site: the class is `prop`. */
[data-theme="dark"] .prop,
[data-theme="dark"] .prop img {
  filter: drop-shadow(0 0 1px rgb(from var(--zl-fg) r g b / 0.35));
}

/* The nav thumbnails are screenshots of the light deck, so they stay light. Their
   frame is already token-driven and flips to bone; the images themselves come
   down a little so a wall of bone rectangles does not glare on the dark tray. */
[data-theme="dark"] .nav-overlay__thumb img {
  filter: brightness(0.9);
}

/* ------------------------------------------------------------------ *
 * NIGHT SHIFT - the sun/moon control, in the status bar beside sound
 * ------------------------------------------------------------------ */

.zl-theme__icon {
  display: block;
  width: 20px;
  height: 20px;
}

/* One glyph at a time: the moon offers the night, the sun offers the way back. */
.zl-theme-btn .zl-theme__icon--sun {
  display: none;
}

:root[data-theme="dark"] .zl-theme-btn .zl-theme__icon--sun {
  display: block;
}

:root[data-theme="dark"] .zl-theme-btn .zl-theme__icon--moon {
  display: none;
}

/* Four round controls now share the info column, so the phone breakpoint has to
   count this one too. */
@media (max-width: 576px) {
  .statusbar__info {
    gap: 0.3rem;
  }

  .statusbar .zl-theme__icon {
    width: 17px;
    height: 17px;
  }
}

/* Cover: the contact chrome owns the true top-right corner at every width, and
   the key-tag prop moves left of it on desktop so neither sits on the 3D logo. */
body:has(.slide--cover.is-active) .zl-chrome {
  top: 1.25rem;
}

.slide--cover .slide__highlighter {
  right: 25rem;
  top: -4vw;
}

@media (max-width: 992px) {
  body:has(.slide--cover.is-active) .zl-chrome {
    top: calc(18.9vw + 0.75rem);
  }

  .slide--cover .slide__highlighter {
    right: 3%;
    top: -8vw;
  }
}

@media (max-width: 768px) {
  body:has(.slide--cover.is-active) .zl-chrome {
    top: calc(29.8vw + 0.75rem);
  }
}

/* Three round controls plus the counter: never let the counter wrap. */
.statusbar__info {
  width: auto;
  min-width: 0;
}

.statusbar__slide-no {
  white-space: nowrap;
}

/* Dark mode: controls that take the page ground as their fill must also take
   the page ink, or they go ink-on-ink under NIGHT SHIFT. */
.zl-cta-runaway {
  color: var(--zl-fg);
  border-color: var(--zl-line);
}

.zl-fast[aria-pressed="false"] {
  color: var(--zl-fg);
}
