/* ThirtySquare Gallery — namespaced .tsq-gal-*
   All tokens scoped to .tsq-gal-root — zero global leakage.
   No bare-element selectors. No resets outside this scope. */

/* ── Gallery-local tokens ─────────────────────────────────── */
.tsq-gal-root {
  --tsq-gal-scrim:       rgba(0, 0, 0, 0.78);
  --tsq-gal-blur:        12px;
  --tsq-gal-dur:         0.65s;
  --tsq-gal-ease:        cubic-bezier(0.16, 1, 0.3, 1);
  --tsq-gal-z-canvas:    1;
  --tsq-gal-z-btn:       20;
  --tsq-gal-z-hover:     30;
  --tsq-gal-z-lightbox:  40;
  --tsq-gal-z-menu:      45; /* above canvas + lightbox — it's the switcher between them */
  --tsq-gal-z-intro:     50;

  /* Canvas dimensions — must match CANVAS_W / CANVAS_H in gallery.controller.js */
  --tsq-gal-canvas-w:    8000px;
  --tsq-gal-canvas-h:    3200px;
}

/* ── Mount ────────────────────────────────────────────────── */
.tsq-gal-mount {
  margin-top: 80px;
}

/* ── Shell — the clipping / morphing container ────────────── */
.tsq-gal-shell {
  position: relative;
  width: 100%;
  height: 460px;
  overflow: hidden;
  border-radius: var(--radius-xl);
  will-change: transform;
  background-color: var(--gray-1);
  /* Subtle vignette so tiles fade toward the edges */
  box-shadow: inset 0 0 80px 20px rgba(0, 0, 0, 0.55);
}

/* Fullscreen state (applied by GSAP Flip transition — Phase 4) */
.tsq-gal-shell--fullscreen {
  position: fixed;
  inset: 0;
  height: auto;
  width: auto;
  border-radius: 0;
  z-index: var(--tsq-gal-z-btn);
  box-shadow: none;
  /* Only while GALLERY is fullscreen — not on the base .tsq-gal-shell,
     where PREVIEW's camera is locked (see gallery.controller.js) and a
     touch-drag starting here should still scroll the page normally.
     Without this, a touch-drag/pinch on a real phone fights the
     browser's own native scroll/page-zoom instead of driving
     gallery.camera.js's pointer-based pan/zoom — no visible effect on
     desktop (mouse/trackpad aren't touch-action-gated), only shows up
     on a real touchscreen. */
  touch-action: none;
}

/* ── Canvas layer — the pannable surface ──────────────────── */
.tsq-gal-canvas-layer {
  position: absolute;
  top: 0;
  left: 0;
  width: var(--tsq-gal-canvas-w);
  height: var(--tsq-gal-canvas-h);
  transform-origin: 0 0;
  will-change: transform;
}

/* Dot grid — deliberately NOT a giant element covering canvas-space.
   Tried that (a ~15-45k px child of canvas-layer): Chrome silently stops
   painting the repeating background far from the layer's centre once
   it's that large — a real rasterisation ceiling, not fixable by tuning
   the size. Instead this stays exactly shell-sized (inset: 0, resizes
   with the shell) and gallery.camera.js recomputes background-size +
   background-position on every camera update — the same "infinite grid"
   trick canvas tools like Figma use: one small repeating tile, scaled
   and phase-shifted in JS, rather than one huge painted area. */
.tsq-gal-dotgrid {
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: 0.55;
  background-image: radial-gradient(circle, var(--gray-6) 1.2px, transparent 1.2px);
  background-size: 28px 28px;
}

/* ── Cluster label ────────────────────────────────────────── */
.tsq-gal-cluster-label {
  position: absolute;
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  color: var(--gray-7);
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
}

/* ── Tile ─────────────────────────────────────────────────── */
/* Same card shape as the lightbox (padded, media inset, byline below),
   but the frame itself — background + caption — is invisible until
   hover/focus, so idle browsing reads as a clean field of floating
   images, and hovering "opens" the card to reveal context. The media
   itself always keeps its own subtle shadow so it doesn't look inert
   while idle. Card grows OUTWARD from the original tile.w/h footprint
   (see renderTiles' TILE_PAD) rather than shrinking the media area
   inward, so the media box keeps the exact width/height gallery.data.js
   computed from each image's real aspect ratio — nothing gets cropped. */
.tsq-gal-tile {
  position: absolute;
  display: flex;
  flex-direction: column;
  padding: 20px;
  border-radius: var(--radius-xl);
  background: transparent;
  box-shadow: none;
  cursor: pointer;
  transition: background 0.2s ease, box-shadow 0.25s, transform 0.25s;
  outline: none;
  -webkit-user-select: none;
  user-select: none;
}

.tsq-gal-tile:hover,
.tsq-gal-tile:focus-visible {
  background: var(--gray-2); /* darker than the old always-on gray-3 */
  /* Scoped to :hover/:focus-visible only, not the base .tsq-gal-tile —
     with ~60 tiles on the canvas, promoting all of them to their own
     compositor layer permanently would cost real GPU memory for no
     benefit (only one is ever interacted with at a time). */
  will-change: transform;
}

.tsq-gal-tile:hover {
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 2px 6px rgba(0, 0, 0, 0.35);
  transform: translateY(-3px) scale(1.01);
  z-index: 2;
}

.tsq-gal-tile:focus-visible {
  box-shadow: var(--ring-focus), 0 8px 32px rgba(0, 0, 0, 0.5);
}

.tsq-gal-tile-media {
  position: relative;
  flex-shrink: 0;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--gray-1);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.5), 0 1px 3px rgba(0, 0, 0, 0.4);
}

.tsq-gal-tile-media img,
.tsq-gal-tile-media video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  pointer-events: none;
  -webkit-user-drag: none;
  user-select: none;
}

/* Muted-speaker cue on every video tile — always visible (not gated
   behind :hover like the footer below) since the point is telling
   people BEFORE they interact that there's sound waiting behind a
   click, not confirming it after the fact. Brightens + lifts slightly
   on tile hover for a touch of feedback, same spring the tile itself
   uses. */
.tsq-gal-tile-soundbadge {
  position: absolute;
  right: 10px;
  bottom: 10px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-full);
  background: rgba(10, 10, 11, 0.65);
  backdrop-filter: blur(2px);
  color: var(--gray-12);
  opacity: 0.82;
  transition: opacity 0.2s ease, transform 0.2s ease, background 0.2s ease;
}

.tsq-gal-tile:hover .tsq-gal-tile-soundbadge,
.tsq-gal-tile:focus-visible .tsq-gal-tile-soundbadge {
  opacity: 1;
  background: var(--orange-10);
  color: var(--orange-ink);
  transform: scale(1.08);
}

/* One overlay, not two: title + optional case-study shortcut fade in
   together as a single footer row on hover/focus, riding the same
   opacity transition the caption always had. No separate floating card
   duplicating the title elsewhere on screen. */
.tsq-gal-tile-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-shrink: 0;
  padding: 16px 6px 4px;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.tsq-gal-tile:hover .tsq-gal-tile-footer,
.tsq-gal-tile:focus-visible .tsq-gal-tile-footer {
  opacity: 1;
}

.tsq-gal-tile-caption {
  min-width: 0;
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 500;
  color: var(--gray-9); /* muted gray — fits the dark canvas better than a bold near-white */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.tsq-gal-tile-csbtn {
  flex-shrink: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
  /* Real padding, not just a text label: the canvas itself is under a
     camera zoom transform (can be well under 1x), which shrinks a plain
     text hit target to just a few CSS px tall — easy to miss and fall
     through to the tile's own tap-to-open-lightbox handler underneath.
     Padding (plus the matching negative margin, so it doesn't visually
     push the caption further left) gives it a forgiving click area
     without changing how it reads next to the caption. */
  padding: 7px 9px;
  margin: -7px -9px -7px 0;
  border-radius: var(--radius-full);
  font-family: var(--font-mono);
  font-size: 11.5px;
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--orange-11);
  cursor: pointer;
  transition: color 0.15s, background 0.15s;
  /* Only actually clickable while its footer is visible — see
     .tsq-gal-tile-footer's own opacity toggle above. Otherwise an
     invisible link would sit clickable inside the tile's hitbox. */
  pointer-events: none;
}

.tsq-gal-tile:hover .tsq-gal-tile-csbtn,
.tsq-gal-tile:focus-visible .tsq-gal-tile-csbtn {
  pointer-events: auto;
}

.tsq-gal-tile-csbtn:hover {
  color: var(--orange-9);
  background: rgba(255, 255, 255, 0.06);
}

.tsq-gal-tile-csbtn svg {
  flex-shrink: 0;
}

/* Self-contained mini-carousel for a multi-slide social post tile (see
   media.type:'carousel' in gallery.data.js) — same idea as the
   case-study page's .cs-carousel-arrow/.cs-carousel-dot, sized up a bit
   since the canvas can be viewed at less than 1x camera zoom (see the
   .tsq-gal-tile-csbtn comment on hit-target shrinkage). Always visible,
   not hover-gated: unlike the csbtn shortcut, browsing the post IS this
   tile's whole point. These are <span>s, not real buttons — same ARIA
   reasoning as csbtn — and hit-tested via the shell's own pointerup
   handler (see gallery.controller.js init()), not native click, since
   pointer capture during a canvas drag retargets every click away from
   the tile's real children. */
.tsq-gal-tile-carousel-arrow {
  position: absolute;
  top: 50%;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  margin-top: -17px;
  border-radius: var(--radius-full);
  background: rgba(10, 10, 11, 0.6);
  backdrop-filter: blur(2px);
  color: var(--gray-12);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease, transform 0.15s ease;
}
.tsq-gal-tile-carousel-arrow:hover {
  background: var(--orange-10);
  color: var(--orange-ink);
  transform: scale(1.08);
}
.tsq-gal-tile-carousel-arrow--prev { left: 10px; }
.tsq-gal-tile-carousel-arrow--next { right: 10px; }

.tsq-gal-tile-carousel-dots {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 10px;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
}
.tsq-gal-tile-carousel-dot {
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: rgba(255, 255, 255, 0.4);
  transition: background 0.15s ease, transform 0.15s ease;
}
.tsq-gal-tile-carousel-dot.active {
  background: var(--orange-9);
  transform: scale(1.3);
}

/* Placeholder shimmer for tiles without media yet */
.tsq-gal-tile--placeholder .tsq-gal-tile-media {
  background: linear-gradient(
    110deg,
    var(--gray-2) 30%,
    var(--gray-3) 50%,
    var(--gray-2) 70%
  );
  background-size: 200% 100%;
  animation: tsq-gal-shimmer 1.6s linear infinite;
}

.tsq-gal-tile--placeholder .tsq-gal-tile-media::after {
  content: attr(data-label);
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--gray-7);
}

@keyframes tsq-gal-shimmer {
  to { background-position: -200% 0; }
}

/* ── Explore button ───────────────────────────────────────── */
.tsq-gal-explore-btn {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: var(--tsq-gal-z-btn);
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 14px 26px;
  background: var(--orange-10);
  color: var(--orange-ink);
  font-family: var(--font-mono);
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.03em;
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.5), var(--glow-accent);
  transition: background 0.15s, box-shadow 0.15s, transform 0.15s;
}

.tsq-gal-explore-btn:hover {
  background: var(--orange-9);
  box-shadow: 0 6px 32px rgba(0, 0, 0, 0.55), 0 0 36px 8px rgba(245, 130, 31, 0.28);
  transform: translate(-50%, -53%);
}

.tsq-gal-explore-btn:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
}

/* Hidden when gallery is fullscreen (Phase 4) */
.tsq-gal-shell--fullscreen .tsq-gal-explore-btn {
  display: none;
}

/* ── Close button — shown only in fullscreen (Phase 4) ────── */
.tsq-gal-close-btn {
  position: absolute;
  top: 20px;
  right: 24px;
  z-index: var(--tsq-gal-z-btn);
  width: 44px;
  height: 44px;
  border-radius: var(--radius-full);
  background: var(--gray-3);
  border: 1px solid var(--gray-5);
  color: var(--gray-12);
  display: none;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s;
  flex-shrink: 0;
}

.tsq-gal-close-btn:hover {
  background: var(--gray-4);
  border-color: var(--gray-6);
}

.tsq-gal-close-btn:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
}

.tsq-gal-shell--fullscreen .tsq-gal-close-btn {
  display: flex;
}

/* ── Drag cursor states (Phase 2) ─────────────────────────── */
.tsq-gal-shell--dragging {
  cursor: grabbing;
}

.tsq-gal-shell--dragging .tsq-gal-tile {
  pointer-events: none;
  transition: none;
}

/* ── Gallery mini menu (Phase 6) ───────────────────────────────
   Same hide/show recipe as the site's own .nav-float (fixed to the
   viewport bottom, translateY + opacity + pointer-events, no display:
   none) so the two feel like one paired motion — nav slides down as
   this slides up. Lives fixed to the viewport rather than inside the
   (overflow:hidden) shell box so it's unaffected by shell's own sizing. */
.tsq-gal-menu {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%) translateY(calc(100% + 40px));
  z-index: var(--tsq-gal-z-menu);
  display: flex;
  align-items: center;
  gap: 4px;
  padding: 8px;
  background: rgba(18, 18, 20, .97);
  border: 1px solid var(--gray-4);
  border-radius: var(--radius-lg);
  opacity: 0;
  pointer-events: none;
  transition: transform .4s cubic-bezier(.34, 1.2, .64, 1), opacity .3s ease;
  white-space: nowrap;
}

.tsq-gal-menu--visible {
  transform: translateX(-50%) translateY(0);
  opacity: 1;
  pointer-events: all;
}

.tsq-gal-menu-back {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 14px;
  background: transparent;
  border: none;
  border-radius: var(--radius-md);
  color: var(--gray-11);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: .02em;
  cursor: pointer;
  transition: color .15s, background .15s;
}

.tsq-gal-menu-back:hover {
  color: var(--gray-12);
  background: var(--gray-3);
}

.tsq-gal-menu-sep {
  width: 1px;
  height: 20px;
  background: var(--gray-5);
  margin: 0 4px;
}

.tsq-gal-menu-toggle {
  display: flex;
  align-items: center;
  gap: 2px;
  background: var(--gray-2);
  border-radius: var(--radius-md);
  padding: 3px;
}

.tsq-gal-menu-toggle-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 7px 14px;
  background: transparent;
  border: none;
  border-radius: calc(var(--radius-md) - 3px);
  color: var(--gray-11);
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: .02em;
  cursor: pointer;
  transition: color .15s, background .15s;
}

.tsq-gal-menu-toggle-btn:hover {
  color: var(--gray-12);
}

.tsq-gal-menu-toggle-btn.is-active {
  background: var(--orange-10);
  color: var(--orange-ink);
}

/* ── Lightbox (Phase 7) ──────────────────────────────────────
   One design at a time, full image visible (object-fit: contain, not
   cover), arrow navigation either side. Sits above the canvas but below
   the menu, which stays usable as the switcher regardless of which
   sub-view is showing. Card uses the same dark gray scale as the rest of
   the gallery chrome (menu, close button, etc.) rather than a white
   lightroom-style card. */
.tsq-gal-lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--tsq-gal-z-lightbox);
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 24px;
  padding: 0 88px;
  background: rgba(10, 10, 11, .92);
  -webkit-backdrop-filter: blur(var(--tsq-gal-blur));
  backdrop-filter: blur(var(--tsq-gal-blur));
  opacity: 0;
  pointer-events: none;
  transition: opacity .3s ease;
}

.tsq-gal-lightbox--visible {
  opacity: 1;
  pointer-events: all;
}

.tsq-gal-lightbox-card {
  display: flex;
  flex-direction: column;
  background: var(--gray-3);
  border-radius: var(--radius-xl);
  padding: 20px;
  max-width: min(1100px, 100%);
  max-height: calc(100vh - 160px);
  box-shadow: 0 24px 80px rgba(0, 0, 0, .6);
}

.tsq-gal-lightbox-media {
  flex: 1;
  min-height: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--gray-2);
}

.tsq-gal-lightbox-media img,
.tsq-gal-lightbox-media video {
  display: block;
  max-width: 100%;
  max-height: calc(100vh - 260px);
  width: auto;
  height: auto;
  object-fit: contain;
}

/* YouTube embeds (marketing cluster) — an <iframe> has no intrinsic
   aspect ratio like img/video do, so it needs an explicit one. All
   current embeds are 16:9 sources; width caps the same way img/video
   do above, height derives from the ratio instead of max-height. */
.tsq-gal-lightbox-media .tsq-gal-lightbox-yt {
  display: block;
  width: min(100%, calc((100vh - 260px) * 16 / 9));
  aspect-ratio: 16 / 9;
  height: auto;
  border: 0;
  border-radius: var(--radius-lg);
}

.tsq-gal-lightbox-caption {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 16px 6px 4px;
}

.tsq-gal-lightbox-caption-top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
}

.tsq-gal-lightbox-title {
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 500;
  color: var(--gray-12);
}

.tsq-gal-lightbox-index {
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .04em;
  color: var(--gray-9);
  flex-shrink: 0;
}

.tsq-gal-lightbox-cs-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  width: fit-content;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: .02em;
  color: var(--orange-9);
  cursor: pointer;
  transition: color .15s;
}

.tsq-gal-lightbox-cs-link:hover { color: var(--orange-10); }
.tsq-gal-lightbox-cs-link:focus-visible { outline: none; box-shadow: var(--ring-focus); }

.tsq-gal-lightbox-arrow {
  flex-shrink: 0;
  width: 48px;
  height: 48px;
  border-radius: var(--radius-full);
  background: var(--gray-3);
  border: 1px solid var(--gray-5);
  color: var(--gray-12);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background .15s, border-color .15s;
}

.tsq-gal-lightbox-arrow:hover {
  background: var(--gray-4);
  border-color: var(--gray-6);
}

.tsq-gal-lightbox-arrow:focus-visible,
.tsq-gal-menu-back:focus-visible,
.tsq-gal-menu-toggle-btn:focus-visible {
  outline: none;
  box-shadow: var(--ring-focus);
}

/* ── Intro nudge (Phase 5) ─────────────────────────────────────
   Sits above the mini menu (bottom:24px + its own height) — pill-bar
   language matches the menu bar rather than a separate full-screen scrim,
   since dimming the canvas fights the point of showing it off right when
   it's opening. Wrapper itself is pointer-events:none so the drag/scroll/
   pinch it's demonstrating is never blocked; only the dismiss button
   opts back in. */
.tsq-gal-intro {
  position: fixed;
  left: 50%;
  bottom: 96px;
  transform: translateX(-50%) translateY(12px);
  z-index: var(--tsq-gal-z-intro);
  max-width: min(680px, calc(100vw - 48px));
  opacity: 0;
  pointer-events: none;
  transition: opacity .35s ease, transform .35s cubic-bezier(.34, 1.2, .64, 1);
}

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

.tsq-gal-intro-bar {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 12px 12px 12px 20px;
  background: rgba(18, 18, 20, .97);
  border: 1px solid var(--gray-4);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-popover);
}

.tsq-gal-intro-text {
  display: flex;
  align-items: center;
  gap: 14px;
  white-space: nowrap;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.4;
  color: var(--gray-11);
}

.tsq-gal-intro-item {
  display: flex;
  align-items: center;
  gap: 7px;
  flex-shrink: 0;
}

.tsq-gal-intro-item svg {
  flex-shrink: 0;
  color: var(--gray-9);
}

.tsq-gal-intro-divider {
  flex-shrink: 0;
  width: 1px;
  height: 14px;
  background: var(--gray-5);
}

.tsq-gal-intro-dismiss {
  flex-shrink: 0;
  padding: 8px 16px;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 500;
  color: var(--orange-ink);
  background: var(--orange-10);
  border: none;
  border-radius: var(--radius-full);
  cursor: pointer;
  pointer-events: auto;
  transition: background .15s;
}

.tsq-gal-intro-dismiss:hover { background: var(--orange-9); }
.tsq-gal-intro-dismiss:focus-visible { outline: none; box-shadow: var(--ring-focus); }

@media (max-width: 640px) {
  .tsq-gal-intro-bar {
    flex-direction: column;
    align-items: stretch;
    gap: 10px;
    padding: 14px;
    text-align: center;
  }
  .tsq-gal-intro-text {
    flex-wrap: wrap;
    white-space: normal;
    justify-content: center;
  }
  .tsq-gal-intro-divider { display: none; }
  .tsq-gal-intro-dismiss { width: 100%; }

  /* .tsq-gal-lightbox's desktop layout (48px arrows flanking the card,
     0 88px side padding, 24px gaps) needs ~200px of chrome around the
     card alone — more than a phone viewport has to spare, crushing the
     card into a sliver. Below this width, arrows come out of the flex
     flow and overlay the card's edges instead, so the card gets nearly
     the full width. */
  .tsq-gal-lightbox {
    padding: 0 8px;
    gap: 0;
  }
  .tsq-gal-lightbox-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 40px;
    height: 40px;
    z-index: 1;
    background: rgba(24, 24, 27, .8);
  }
  .tsq-gal-lightbox-arrow--prev { left: 8px; }
  .tsq-gal-lightbox-arrow--next { right: 8px; }
  .tsq-gal-lightbox-card {
    max-width: 100%;
  }

  /* .tsq-gal-menu's desktop layout (icon + text label on 4 separate
     buttons — "Back to offer", "Explore", "Gallery", "Case Studies" —
     plus a separator) adds up to ~500px, wider than most phones. Labels
     stay — icon-only was tried and rejected as not enough context on a
     first-glance mobile menu. Instead: tighter padding/font/gap
     everywhere, "Back to offer" shortened to "Back" (its own real text
     hidden, swapped for a ::after — keeps the full string for anyone
     inspecting markup/screen readers via the button's own aria-label,
     just shorter on screen), and the whole bar becomes horizontally
     scrollable as a safety net so it degrades to "swipe to see more"
     on a very narrow device instead of ever overlapping/clipping. */
  .tsq-gal-menu {
    max-width: calc(100vw - 24px);
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .tsq-gal-menu::-webkit-scrollbar { display: none; }
  .tsq-gal-menu-back span {
    display: none;
  }
  .tsq-gal-menu-back::after {
    content: "Back";
    font-family: var(--font-mono);
    font-size: 12px;
    letter-spacing: .02em;
  }
  .tsq-gal-menu-back,
  .tsq-gal-menu-toggle-btn {
    padding: 7px 9px;
    gap: 4px;
    font-size: 12px;
    flex-shrink: 0;
  }
}

/* ── Reduced motion overrides ─────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
  .tsq-gal-tile,
  .tsq-gal-explore-btn,
  .tsq-gal-close-btn {
    transition: none;
  }
  .tsq-gal-tile--placeholder .tsq-gal-tile-media {
    animation: none;
    background: var(--gray-2);
  }
  .tsq-gal-menu,
  .tsq-gal-lightbox,
  .tsq-gal-tile-footer,
  .tsq-gal-intro {
    transition: none;
  }
}
