* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, -apple-system, sans-serif;
  color: #1a1a1a;
  /* env(safe-area-inset-top) is ~0 in a normal browser tab and only becomes
     meaningful when installed standalone on iOS (behind the status bar/
     Dynamic Island) — this self-adjusts per device instead of a blind fixed
     offset that would over-pad the browser case. The small base gap is
     breathing room below the status bar, not part of the inset itself.
     One place this is defined; reused by the header bar's own height AND
     main's compensating padding-top (and the reels feed's height calc
     below), so none of them can drift out of sync with each other. */
  --site-header-top-inset: calc(env(safe-area-inset-top, 0px) + 8px);
  --site-header-height: calc(80px + var(--site-header-top-inset));
}

body.nav-open {
  overflow: hidden;
}

main {
  padding-top: var(--site-header-height);
}

/* Side padding matches the feed/hero/etc. convention (1rem) elsewhere on
   the page, so the copyright line isn't flush against the left edge. */
footer {
  padding: 1.5rem 1rem 0;
  /* Same thin, low-contrast weight as the feed-item dividers (border-bottom:
     1px solid #eee) — consistent rule style, just separating content from
     the footer instead of one feed item from the next. */
  border-top: 1px solid #eee;
}

/* Header bar + offcanvas nav */

.site-header {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 1000;
  /* Clips .site-header__bg to the bar's own height — same reasoning as
     .slider/.hero below. */
  overflow: hidden;
  /* On every page EXCEPT home, the header's background is not meant to
     read as a continuation of whatever's underneath (category hero,
     article body, gallery, ...) — it's the same photo for brand
     consistency, but the boundary should stay visibly a boundary. Home is
     the one page where the nav and the section below deliberately share
     one unbroken backdrop (see .slider__bg), so it cancels this shadow
     back out below. */
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.55);
}

.is-home .site-header {
  box-shadow: none;
}

/* Same source photo as .slider__bg (see below), shown behind the nav on
   EVERY page — this is what gives the brand mark and links a dark, legible
   backdrop instead of sitting on flat black. Sampled the top of the image
   for its brightest spot before picking the scrim: even the palest pixels
   up there are near-black (RGB ~45,45,45), so the same light 15% scrim used
   on the slider/hero is already more than enough here — no separate,
   stronger treatment needed just for the header. Deliberately NOT part of
   the JS parallax target list: this bar is position:fixed (never moves on
   scroll) and only 80px tall, so translating its background wouldn't read
   as parallax — it'd just be a few pixels of jitter behind static content.
   It stays put; only the in-flow slider/hero backgrounds animate. */
.site-header__bg {
  position: absolute;
  inset: 0;
  background-image: linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15)), var(--bg-url);
  background-size: 100% auto, 100% auto;
  background-position: top center, top center;
  background-repeat: no-repeat, no-repeat;
  z-index: 0;
  pointer-events: none;
}

.site-header__bar {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* min-height (not height) is the total bar box, including the top inset —
     with border-box, padding-top below eats into a plain 80px min-height,
     which would squeeze the logo/hamburger row rather than push it down
     while keeping their original 80px of room. Growing min-height by the
     same inset keeps that 80px intact below the padding. */
  min-height: var(--site-header-height);
  padding: var(--site-header-top-inset) 20px 0;
}

.site-header__brand {
  display: flex;
  align-items: center;
}

/* Height-constrained, width auto, so the logo (a wide icon+wordmark+tagline
   lockup, ~4.8:1) scales proportionally to fit the 80px bar without
   distorting or overflowing it. 2.75rem still leaves 18px of vertical
   breathing room top/bottom in that 80px bar — comfortably under
   min-height, so it never pushes the bar taller — and at ~211px wide it
   still clears the 50px hamburger button on the narrowest common phone
   widths (checked at 320px). Nothing here scales with screen width, so
   it's the same fit on mobile and desktop. */
.site-header__logo {
  height: 2.75rem;
  width: auto;
  display: block;
}

/* Installed-PWA-only bump — a normal browser tab keeps the 2.75rem size
   above untouched. display-mode is the primary signal; html.is-standalone
   (set by the inline script in layout.php's <head>, before first paint) is
   the fallback for older iOS Safari, which never supported the media
   query. Either one is sufficient, so both rules just apply the same size.
   52px still sits well under the bar's fixed 80px content band (see
   --site-header-height) — the bar itself doesn't grow — and at ~249px wide
   still clears the hamburger with room to spare (checked at 390px and
   320px widths). Height-constrained, width auto, same as above — no
   distortion at either size. */
@media (display-mode: standalone) {
  .site-header__logo {
    height: 3.25rem;
  }
}

html.is-standalone .site-header__logo {
  height: 3.25rem;
}

.nav-toggle {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  gap: 5px;
  width: 50px;
  height: 50px;
  border: none;
  border-radius: 50%;
  background: linear-gradient(145deg, #1e2024, #23272b);
  cursor: pointer;
}

.nav-toggle__bar {
  width: 20px;
  height: 2px;
  background: #d9e5e8;
}

.nav-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.8);
  z-index: 1001;
}

.nav-panel {
  position: fixed;
  top: 0;
  left: 0;
  bottom: 0;
  width: 375px;
  max-width: 80%;
  background: #212428;
  box-shadow: 0 0 10px 0 rgba(0, 0, 0, 0.7);
  transform: translateX(-100%);
  transition: transform 0.5s ease-in-out;
  z-index: 1002;
  padding: 25px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

.nav-panel.is-open {
  transform: translateX(0);
}

.nav-panel__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  /* Same --site-header-top-inset used by the fixed top bar (app.css, near
     the top of this file) — the panel header needs to clear the exact same
     iOS status bar, so it reuses that one variable rather than a second,
     possibly-drifting offset. ~0 in a browser tab, the real inset when
     installed standalone. */
  padding: var(--site-header-top-inset) 0 20px;
}

.nav-panel__brand {
  display: flex;
  align-items: center;
}

.nav-panel__logo {
  /* Smaller than the top bar's 2.75rem — the panel itself is capped at
     max-width: 80% (.nav-panel above), so on an ordinary ~390px phone it's
     only ~312px wide; at the top bar's full height this wide logo (its
     natural aspect ratio is ~4.8:1) would sit almost flush against the
     close button. 2rem keeps the same proportions (height-constrained,
     width auto — never distorted) while leaving it real breathing room
     next to the X. Still reads as the same mark, just sized to its actual
     available space rather than blindly copying the top bar's number. */
  height: 2rem;
  width: auto;
  display: block;
}

/* Installed-PWA-only bump, same mechanism as .site-header__logo above. A
   smaller step than the header's (+0.25rem vs +0.5rem) since the panel has
   much less spare width to give — at 2.25rem this is still ~40px clear of
   the close button on an ordinary ~390px phone (panel capped at
   max-width: 80%), checked alongside the safe-area-inset-top padding on
   .nav-panel__head so the larger logo still sits correctly below the
   status bar rather than needing separate spacing. */
@media (display-mode: standalone) {
  .nav-panel__logo {
    height: 2.25rem;
  }
}

html.is-standalone .nav-panel__logo {
  height: 2.25rem;
}

.nav-close {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 50px;
  height: 50px;
  border: none;
  border-radius: 50%;
  background: linear-gradient(145deg, #1e2024, #23272b);
  cursor: pointer;
}

.nav-close__bar {
  width: 20px;
  height: 2px;
  background: #d9e5e8;
}

.nav-close__bar:first-child {
  transform: rotate(45deg);
  margin-bottom: -2px;
}

.nav-close__bar:last-child {
  transform: rotate(-45deg);
}

.nav-panel__tagline {
  color: #878e99;
  margin-top: 20px;
}

.nav-panel__tagline span {
  color: #fff;
}

.nav-panel__links {
  list-style: none;
  margin: 20px 0 0;
  padding: 0;
  overflow-y: auto;
}

.nav-panel__links a {
  display: block;
  padding: 10px 0;
  color: #fff;
  text-decoration: none;
  text-transform: uppercase;
  font-size: 1.1rem;
}

.nav-panel__links a:hover,
.nav-panel__links a:focus {
  opacity: 0.7;
}

/* Home: slider */

.slider {
  position: relative;
  background: #000;
  /* Clips the oversized/translated .slider__bg so the parallax range never
     peeks past this section's own edges — no horizontal scroll, no reveal
     of the bg's extra top/bottom margin into neighboring sections. */
  overflow: hidden;
}

/* Shared background layer (slider + hero, see below). Sits behind the real
   content (z-index 0 vs 1) and is oversized top/bottom by a fixed 40px —
   comfortably more than the JS parallax's ±32px clamp — so translating it
   for the parallax effect never exposes an empty edge. Never
   background-attachment: fixed (iOS repaint/jank risk); a real, separately
   transformed element keeps this on the GPU-compositing path instead. */
.slider__bg,
.hero__bg {
  position: absolute;
  top: -40px;
  bottom: -40px;
  left: 0;
  right: 0;
  /* The source photo is already quite dark on its own — a light scrim is
     plenty for text-safety without crushing it to near-black. */
  background-image: linear-gradient(rgba(0, 0, 0, 0.15), rgba(0, 0, 0, 0.15)), var(--bg-url);
  background-repeat: no-repeat, no-repeat;
  will-change: transform;
  z-index: 0;
  pointer-events: none;
}

.hero__bg {
  background-size: cover, cover;
  background-position: center, center;
}

/* Home page only. .site-header__bg and .slider__bg are the SAME photo at
   the SAME width-locked scale (both full-bleed, so `100% auto` scales them
   identically) instead of independent `cover` crops — cover would zoom each
   one differently based on its own box's aspect ratio and create a visible
   seam right at the nav/slider boundary. -40px continues the image from
   exactly where the header's crop leaves off: the header shows image
   y:[0, 80] (its own height, no oversize); this div's own top edge sits
   40px ABOVE the slider's visible top (the shared -40px oversize above),
   so an offset of (40 - 80) = -40px lands image-y:80 precisely at the
   slider's visible top edge — no gap, no repeat. */
.slider__bg {
  background-size: 100% auto, 100% auto;
  background-position: center -40px, center -40px;
}

.slider__track {
  position: relative;
  z-index: 1;
  display: flex;
  gap: 1rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  /* 4rem at the bottom specifically — breathing room before the pinned/feed
     section below; top and sides stay at the original 1rem. */
  padding: 1rem 1rem 4rem;
  -webkit-overflow-scrolling: touch;
}

/* Mobile: .slider__item is a single slide filling most of the screen, and
   scroll-snap-align: center needs room on either side of the FIRST/LAST
   slide to actually reach true center — with only the flat 1rem above,
   there's nowhere further to scroll into, so the edge slides sit off-
   center while every middle slide (which has real neighbors to scroll
   past) centers fine. The fix is symmetric inline padding equal to
   (viewport width − this slide's own width) / 2 — 90vw here MUST mirror
   .slider__item's own mobile width below exactly (same value, same
   breakpoint) so this stays correct at every phone width, not just one
   tuned px number. Gated to mobile portrait widths — beyond 480px,
   .slider__item reverts to its smaller desktop/tablet sizing (60vw capped
   at 280px) where several slides show at once and this single-slide
   centering doesn't apply. */
@media (max-width: 480px) {
  .slider__track {
    padding-inline: calc((100% - 90vw) / 2);
  }
}

.slider__item {
  flex: 0 0 auto;
  scroll-snap-align: center;
  position: relative;
  display: block;
  width: 60vw;
  max-width: 280px;
  aspect-ratio: 820 / 1080;
  overflow: hidden;
  text-decoration: none;
}

/* Mobile portrait: nearly edge-to-edge (90vw) so a slide reads as "the"
   thing on screen, with just enough margin (the other 10vw, split by the
   centering padding above) for a sliver of the next slide to peek in and
   signal it's swipeable. max-width: none removes the 280px desktop cap —
   without it, 90vw on anything wider than ~311px would get clamped right
   back down to 280px, undoing this. Bounded by the same 480px breakpoint
   as the padding fix above, so the widest this gets is 480px * 0.9 = 432px
   — still squarely "one big slide", never blowing up to desktop sizing. */
@media (max-width: 480px) {
  .slider__item {
    width: 90vw;
    max-width: none;
  }
}

.slider__item img,
.slider__placeholder {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.slider__placeholder {
  background: #333;
}

/* Standard sr-only clip pattern: content stays in the accessibility tree
   and is indexable by crawlers, but is clipped to nothing visually. Never
   display:none / visibility:hidden — those remove it from both. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Home: pinned */

.pinned {
  max-width: 640px;
  margin: 0 auto;
  padding: 1.5rem 1rem 0;
}

.pinned__title {
  margin: 0 0 1rem;
}

.pinned__item {
  display: flex;
  gap: 1rem;
  margin-bottom: 1rem;
  text-decoration: none;
  color: inherit;
}

.pinned__item img,
.pinned__placeholder {
  width: 120px;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: #3a3a3a;
  flex: 0 0 auto;
}

.pinned__text h3 {
  margin: 0 0 0.25rem;
}

.pinned__text p {
  margin: 0;
  color: #666;
  font-size: 0.875rem;
}

.feed {
  max-width: 640px;
  margin: 0 auto;
  /* Extra top padding so the first item isn't crowded against whatever's
     above the feed (slider on home, hero on category pages) — sides/bottom
     stay at the original 1rem. */
  padding: 2rem 1rem 1rem;
}

.feed-item {
  /* Split the old flat 2rem gap into padding-before + margin-after so a
     divider line can sit in between without changing the total space
     between items. */
  padding-bottom: 1rem;
  margin-bottom: 1rem;
}

.feed-item:not(:last-child) {
  border-bottom: 1px solid #eee;
}

/* Breathing room above each item's own content — skipped on the first item
   since .feed's padding-top already gives it that space; adding this on
   top of that would over-space it relative to the rest. */
.feed-item:not(:first-child) {
  padding-top: 0.5rem;
}

.facade {
  position: relative;
  aspect-ratio: 16 / 9;
  background: #222;
  overflow: hidden;
}

.facade img,
.facade-placeholder,
.facade iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.facade img,
.facade iframe {
  border: 0;
  object-fit: cover;
}

.facade-placeholder {
  background: #3a3a3a;
}

/* Root cause of the squashed/oval play button: the icon used to be a raw
   Unicode glyph ("&#9658;") relying on the button's native text layout
   (default UA padding, line-height) to position it — that layout is
   whatever the font/browser decides, not a fixed shape, so the glyph
   rendered warped. Fixed with a real inline SVG, flex-centered, with the
   button's own box locked to a circle two ways (explicit equal width/height
   AND aspect-ratio as a defensive fallback) and flex-shrink:0 so it can
   never be squashed if this button ever ends up inside a flex/grid parent. */
.facade-play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 64px;
  height: 64px;
  aspect-ratio: 1;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  color: #fff;
  line-height: 0;
  cursor: pointer;
}

.facade-play__icon {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  fill: currentColor;
}

.facade.is-playing .facade-play,
.facade.is-playing img,
.facade.is-playing .facade-placeholder {
  display: none;
}

.article-card {
  display: block;
  color: inherit;
  text-decoration: none;
}

.article-card img,
.article-card .facade-placeholder {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: #3a3a3a;
}

.feed-item h2 {
  margin: 0.5rem 0 0.25rem;
}

.feed-item h2 a {
  color: inherit;
  text-decoration: none;
}

.feed-item h2 a:hover {
  text-decoration: underline;
}

.meta {
  color: #666;
  font-size: 0.875rem;
}

/* Category page: hero */

.hero {
  position: relative;
  width: 100%;
  aspect-ratio: 1920 / 1080;
  background: #222;
  /* Clips .hero__bg's oversized top/bottom margin — same reasoning as
     .slider above. */
  overflow: hidden;
}

.hero__image {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.hero--placeholder {
  /* Fallback only — .hero__bg (the shared background image) is what
     actually shows here now; this is just the color visible for the
     instant before it loads, or if it fails to. */
  background: #2a2a2a;
}

.category-title {
  max-width: 640px;
  margin: 1.5rem auto 0;
  padding: 0 1rem;
}

.not-found {
  max-width: 640px;
  margin: 3rem auto 0;
  padding: 0 1rem;
  text-align: center;
}

.not-found__logo {
  display: block;
  width: 84px;
  height: 84px;
  object-fit: contain;
  margin: 0 auto 1.5rem;
}

.not-found__title {
  margin: 0 0 0.75rem;
  font-size: 1.4rem;
  font-weight: 800;
  color: #212428;
}

.not-found__copy {
  margin: 0 0 2rem;
  color: #5b6470;
  font-size: 0.95rem;
  line-height: 1.5;
}

.not-found__links {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.75rem;
  margin: 0 0 2.5rem;
}

@media (min-width: 480px) {
  .not-found__links {
    grid-template-columns: repeat(4, 1fr);
  }
}

.not-found__link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.5rem;
  padding: 1.25rem 0.5rem;
  border: 1px solid #e5e5e5;
  border-radius: 10px;
  background: #fff;
  color: #212428;
  text-decoration: none;
  font-weight: 600;
  font-size: 0.85rem;
}

.not-found__link:hover {
  border-color: #2563a8;
}

.not-found__icon {
  width: 28px;
  height: 28px;
  color: #2563a8;
}

.not-found__categories {
  padding-top: 1.5rem;
  border-top: 1px solid #eee;
}

.not-found__categories-title {
  margin: 0 0 1rem;
  font-size: 1rem;
  font-weight: 700;
  color: #212428;
}

.not-found__categories-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.5rem;
  margin: 0;
  padding: 0;
  list-style: none;
}

.not-found__categories-list a {
  display: inline-block;
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  background: #f2f4f7;
  color: #212428;
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 500;
}

.not-found__categories-list a:hover {
  background: #e5e9ef;
}

/* Post detail page */

.post-detail {
  max-width: 640px;
  margin: 1.5rem auto 0;
  padding: 0 1rem 2rem;
}

.post-detail__breadcrumb {
  margin: 0 0 0.5rem;
  font-size: 0.875rem;
}

.post-detail__breadcrumb a {
  color: inherit;
}

.post-detail h1 {
  margin: 0 0 0.25rem;
}

.post-detail .facade {
  margin: 1rem 0;
}

.post-detail__description {
  white-space: pre-line;
}

.post-detail__thumbnail {
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  margin: 1rem 0;
  background: #3a3a3a;
}

.post-detail__body {
  line-height: 1.6;
}

.post-detail__body img {
  max-width: 100%;
  height: auto;
}

/* PWA install */

.install-button {
  display: inline-block;
  width: 100%;
  margin: 1.25rem 0 0;
  padding: 0.7rem;
  border: none;
  border-radius: 6px;
  background: #fff;
  color: #212428;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
}

/* Root cause of "the install/notify buttons do nothing": this class's own
   display: inline-block above silently wins over the browser's built-in
   [hidden] { display: none } rule — author CSS always beats the UA
   stylesheet, regardless of specificity, so both #installButton and
   #notifyButton (both use this class) were rendering fully visible and
   tappable even while .hidden was true. Neither JS file was ever buggy;
   every eligibility/state check was setting `hidden` correctly, it just
   had no visual effect. This is the one place that needs to win it back. */
.install-button[hidden] {
  display: none;
}

.nav-panel__notify-status {
  margin: 1.25rem 0 0;
  font-size: 0.85rem;
  line-height: 1.4;
  color: #cfd3d8;
}

.ios-install-prompt {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 2000;
  background: #fff;
  color: #1a1a1a;
  padding: 1.5rem 1.25rem 1.75rem;
  box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
  border-radius: 16px 16px 0 0;
  max-width: 480px;
  margin: 0 auto;
}

.ios-install-prompt__close {
  position: absolute;
  top: 0.75rem;
  right: 0.75rem;
  width: 36px;
  height: 36px;
  border: none;
  border-radius: 50%;
  background: #eee;
  color: #1a1a1a;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;
}

.ios-install-prompt__title {
  margin: 0 2.5rem 1.1rem 0;
  font-size: 1.2rem;
  font-weight: 700;
}

.ios-install-prompt__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 1.1rem;
}

.ios-install-prompt__steps li {
  display: flex;
  align-items: center;
  gap: 0.85rem;
  font-size: 1.1rem;
  line-height: 1.4;
}

.ios-install-prompt__step-number {
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  border-radius: 50%;
  background: #1a1a1a;
  color: #fff;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 1.05rem;
}

.ios-install-prompt__share-icon {
  vertical-align: middle;
  margin: 0 0.1rem;
  color: #007aff;
}

/* Photo gallery */

.gallery-title {
  max-width: 960px;
  margin: 1.5rem auto 1rem;
  padding: 0 1rem;
}

.gallery-empty {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 1rem;
}

.gallery-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 2px;
  max-width: 960px;
  margin: 0 auto;
  padding: 0 2px;
}

@media (min-width: 640px) {
  .gallery-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: 4px;
    padding: 0 4px;
  }
}

.gallery-grid__item {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  padding: 0;
  border: none;
  background: #3a3a3a;
  cursor: pointer;
}

.gallery-grid__item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.gallery-viewer {
  position: fixed;
  inset: 0;
  z-index: 3000;
  background: #000;
}

.gallery-viewer__close {
  position: fixed;
  top: 1rem;
  right: 1rem;
  z-index: 3001;
  width: 44px;
  height: 44px;
  border: none;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
  font-size: 1.75rem;
  line-height: 1;
  cursor: pointer;
}

.gallery-viewer__track {
  height: 100%;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  -webkit-overflow-scrolling: touch;
}

.gallery-viewer__slide {
  height: 100vh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  padding: 1.5rem;
  box-sizing: border-box;
}

.gallery-viewer__img {
  max-width: 100%;
  max-height: 80vh;
  object-fit: contain;
}

.gallery-viewer__caption {
  color: #fff;
  text-align: center;
  margin: 1rem 0 0;
  font-size: 0.95rem;
}

body.gallery-viewer-open {
  overflow: hidden;
}

/* Hearts + comments (feed cards, post detail, gallery viewer) */

.heart-button {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border: none;
  background: none;
  padding: 0.25rem 0;
  font-size: 1rem;
  color: inherit;
  cursor: pointer;
}

.heart-button__icon {
  font-size: 1.25rem;
  line-height: 1;
}

.heart-button.is-hearted .heart-button__icon {
  color: #e0245e;
}

.heart-button__count {
  font-size: 0.875rem;
  color: #666;
  min-width: 1ch;
}

/* Shared action row (heart, share, comment) — one partial (action-row.php)
   used by both the feed and the post detail page, so this is the one place
   its layout is defined too. */
.action-row {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.feed-item .action-row {
  margin-top: 0.5rem;
}

.post-detail .action-row {
  margin: 1rem 0;
}

/* 44px min tap target on all three actions, scoped to this row only (the
   bare .heart-button rule above is shared with reels/gallery-viewer/
   advertiser contexts that already size it deliberately for their own
   layouts — this must not change those). */
.action-row .heart-button {
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.25rem;
}

.action-row__button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-width: 44px;
  min-height: 44px;
  padding: 0 0.25rem;
  border: none;
  background: none;
  color: inherit;
  text-decoration: none;
  cursor: pointer;
}

.action-row__icon {
  width: 22px;
  height: 22px;
  flex-shrink: 0;
}

.action-row__count {
  font-size: 0.875rem;
  color: #666;
  min-width: 1ch;
}

.share-toast {
  position: fixed;
  left: 50%;
  bottom: calc(5rem + env(safe-area-inset-bottom, 0px));
  z-index: 1500;
  transform: translate(-50%, 0.5rem);
  background: rgba(20, 20, 20, 0.92);
  color: #fff;
  padding: 0.65rem 1.1rem;
  border-radius: 999px;
  font-size: 0.9rem;
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.share-toast.is-visible {
  opacity: 1;
  transform: translate(-50%, 0);
  pointer-events: auto;
}

.comment-section {
  margin-top: 1.5rem;
}

.comment-section__title {
  font-size: 1.1rem;
  margin: 0 0 0.75rem;
}

.comment-section__empty {
  color: #666;
  font-size: 0.9rem;
}

.comment-notice {
  background: #eafaf1;
  color: #1e6b3c;
  padding: 0.6rem 0.85rem;
  border-radius: 4px;
  font-size: 0.875rem;
  margin: 0 0 1rem;
}

.comment-list {
  list-style: none;
  margin: 0 0 1rem;
  padding: 0;
}

.comment-list__item {
  padding: 0.75rem 0;
  border-bottom: 1px solid #eee;
}

.comment-list__author {
  margin: 0 0 0.15rem;
  font-weight: 600;
  font-size: 0.875rem;
}

.comment-list__body {
  margin: 0;
  white-space: pre-line;
}

.comment-form {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  max-width: 480px;
}

.comment-form label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.875rem;
}

.comment-form input,
.comment-form textarea {
  padding: 0.5rem;
  border: 1px solid #ccc;
  border-radius: 4px;
  font-size: 1rem;
  font-family: inherit;
}

.comment-form textarea {
  min-height: 5rem;
  resize: vertical;
}

.comment-form button {
  align-self: flex-start;
  padding: 0.5rem 1rem;
  border: none;
  border-radius: 4px;
  background: #1a1a1a;
  color: #fff;
  font-size: 0.9rem;
  cursor: pointer;
}

.comment-form__status {
  font-size: 0.875rem;
  margin: 0;
}

.comment-form__status--error {
  color: #a4262c;
}

.cf-turnstile-container {
  min-height: 65px;
}

/* Gallery viewer stats/comments sit on a black background — override for contrast */

.gallery-viewer__stats {
  margin-top: 0.75rem;
}

.gallery-viewer__stats .heart-button {
  color: #fff;
}

.gallery-viewer__stats .heart-button__count {
  color: #ccc;
}

.gallery-viewer__comments {
  width: 100%;
  max-width: 480px;
  max-height: 30vh;
  overflow-y: auto;
  margin-top: 0.75rem;
  color: #fff;
}

.gallery-viewer__comments .comment-section__title {
  color: #fff;
}

.gallery-viewer__comments .comment-section__empty,
.gallery-viewer__comments .comment-count {
  color: #ccc;
}

.gallery-viewer__comments .comment-list__item {
  border-bottom-color: rgba(255, 255, 255, 0.2);
}

.gallery-viewer__comments .comment-list__author {
  color: #fff;
}

.gallery-viewer__comments .comment-list__body {
  color: #eee;
}

.gallery-viewer__comments .comment-form label {
  color: #fff;
}

/* Reels — full-screen vertical scroll-snap feed. Sits below the fixed
   header (see --site-header-height, main's own padding-top above) rather
   than overlaying it, so nav stays reachable; each slide fills exactly the
   remaining viewport height. Reuses the same variable as main's padding so
   this can't drift out of sync with the header's real (safe-area-aware)
   height. */

.reels-feed {
  height: calc(100vh - var(--site-header-height));
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  background: #000;
}

.reel-slide {
  position: relative;
  height: 100%;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
}

.reel-slide--empty {
  align-items: center;
  justify-content: center;
  color: #fff;
}

.reel-slide__media {
  position: absolute;
  inset: 0;
}

.reel-slide__media img,
.reel-slide__media iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
  object-fit: cover;
}

.reel-slide__title {
  position: relative;
  z-index: 1;
  color: #fff;
  margin: 0;
  padding: 1.5rem;
  padding-bottom: 2rem;
  width: 100%;
  box-sizing: border-box;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.65), transparent);
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}

.reel-slide__tap-target {
  position: absolute;
  inset: 0;
  z-index: 2;
}

.reels-mute-indicator {
  position: fixed;
  right: 1rem;
  bottom: 1.5rem;
  z-index: 10;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.9rem;
  border: none;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.55);
  color: #fff;
  font-size: 0.85rem;
  cursor: pointer;
}

.reels-mute-indicator__icon {
  font-size: 1rem;
  line-height: 1;
}

/* Reels — right-side overlay controls + the comment bottom sheet.
   Stacking order over a slide: media (auto) < title (z-index 1) <
   tap-target (z-index 2) < overlay controls (z-index 3) < sheet (z-index 4)
   when open. Controls sit above the tap-target so they're reachable; both
   the heart button (in reactions.js) and everything sheet-related (in
   reels.js) call stopPropagation() so tapping them never also toggles mute. */

.reel-overlay {
  position: absolute;
  right: 0.75rem;
  bottom: 5rem;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1.25rem;
}

.reel-overlay .heart-button {
  flex-direction: column;
  gap: 0.15rem;
  color: #fff;
}

.reel-overlay .heart-button__icon {
  font-size: 1.75rem;
}

.reel-overlay .heart-button__count {
  color: #fff;
  font-size: 0.75rem;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

.reel-overlay__comment-button {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.15rem;
  border: none;
  background: none;
  color: #fff;
  cursor: pointer;
  padding: 0;
}

.reel-overlay__comment-icon {
  font-size: 1.6rem;
}

.reel-overlay__comment-count {
  font-size: 0.75rem;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
}

.reel-sheet {
  position: absolute;
  inset: 0;
  z-index: 4;
}

.reel-sheet__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(0, 0, 0, 0.5);
  opacity: 0;
  transition: opacity 0.3s ease;
}

.reel-sheet.is-open .reel-sheet__backdrop {
  opacity: 1;
}

.reel-sheet__panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  max-height: 70%;
  overflow-y: auto;
  background: #fff;
  border-radius: 16px 16px 0 0;
  padding: 0.75rem 1.25rem 1.5rem;
  box-sizing: border-box;
  transform: translateY(100%);
  transition: transform 0.3s ease;
  -webkit-overflow-scrolling: touch;
}

.reel-sheet.is-open .reel-sheet__panel {
  transform: translateY(0);
}

.reel-sheet__handle {
  width: 40px;
  height: 4px;
  background: #ccc;
  border-radius: 999px;
  margin: 0 auto 0.75rem;
}

.reel-sheet__close {
  position: absolute;
  top: 0.5rem;
  right: 0.75rem;
  border: none;
  background: none;
  font-size: 1.5rem;
  line-height: 1;
  color: #666;
  cursor: pointer;
  padding: 0.25rem;
}

/* Advertiser directory + detail */

.advertiser-directory {
  max-width: 640px;
  margin: 1.5rem auto 0;
  padding: 0 1rem 2rem;
}

.advertiser-directory__title {
  margin: 0 0 1.5rem;
}

.advertiser-directory__empty {
  color: #666;
}

.advertiser-directory__category {
  margin-bottom: 2rem;
}

.advertiser-directory__category h2 {
  border-bottom: 1px solid #eee;
  padding-bottom: 0.5rem;
  margin: 0 0 0.75rem;
}

.advertiser-directory__list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.advertiser-directory__item {
  border-bottom: 1px solid #f2f2f2;
}

.advertiser-directory__item a {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding: 0.75rem 0;
  color: inherit;
  text-decoration: none;
}

.advertiser-directory__item img {
  width: 44px;
  height: 44px;
  object-fit: contain;
  flex-shrink: 0;
}

.advertiser-directory__info {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}

.advertiser-directory__name-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.advertiser-directory__name {
  font-weight: 600;
}

.advertiser-directory__type {
  color: #666;
  font-size: 0.875rem;
}

.advertiser-directory__offer {
  color: #1e6b3c;
  font-size: 0.85rem;
  font-weight: 600;
}

.advertiser-directory__badge {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  background: #fff4d6;
  color: #8a6100;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* Featured-or-above tiers render as a larger, visually distinct card. */
.advertiser-directory__item--featured {
  background: #fffdf6;
  border: 1px solid #f5e6b8;
  border-radius: 8px;
  margin: 0.5rem 0;
}

.advertiser-directory__item--featured a {
  padding: 1rem;
}

.advertiser-directory__item--featured img {
  width: 60px;
  height: 60px;
}

.advertiser-detail {
  max-width: 640px;
  margin: 1.5rem auto 0;
  padding: 0 1rem 2rem;
}

.advertiser-detail__breadcrumb {
  margin: 0 0 0.75rem;
  font-size: 0.875rem;
}

.advertiser-detail__breadcrumb a {
  color: inherit;
}

.advertiser-detail__logo {
  max-width: 160px;
  max-height: 160px;
  object-fit: contain;
  display: block;
  margin: 0 0 1rem;
}

.advertiser-detail h1 {
  margin: 0 0 0.25rem;
}

.advertiser-detail .facade {
  margin: 1rem 0;
}

.advertiser-detail__description {
  white-space: pre-line;
  line-height: 1.6;
}

.advertiser-detail__facts {
  margin: 1.5rem 0;
}

.advertiser-detail__facts dt {
  font-weight: 600;
  font-size: 0.8rem;
  color: #666;
  margin-top: 0.75rem;
}

.advertiser-detail__facts dd {
  margin: 0.15rem 0 0;
}

.advertiser-detail__socials {
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
}

.advertiser-detail__socials a {
  display: inline-block;
  padding: 0.4rem 0.9rem;
  border-radius: 999px;
  background: #f2f2f2;
  color: inherit;
  text-decoration: none;
  font-size: 0.85rem;
}

.advertiser-detail__photos {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.75rem;
}

.advertiser-detail__photos img {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 6px;
}

.advertiser-detail__offer {
  background: #eafaf1;
  border: 1px solid #c9ecd7;
  border-radius: 8px;
  padding: 0.85rem 1rem;
  margin: 1rem 0;
}

.advertiser-detail__offer-text {
  margin: 0;
  color: #1e6b3c;
  font-weight: 700;
}

.advertiser-detail__offer-details {
  margin: 0.35rem 0 0;
  font-size: 0.85rem;
  color: #3d6b4d;
}

.advertiser-detail__recommend {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0.75rem 0;
}

/* Thumbs-up "recommend" reads oddly in the heart's pink — blue instead. */
.advertiser-detail__recommend .heart-button.is-hearted .heart-button__icon {
  color: #1c5aa6;
}

.advertiser-detail__recommend-label {
  font-size: 0.9rem;
  color: #666;
}

/* Ad placements — shared .ad-card core, wrapped differently per feed. */

.ad-card__link {
  display: block;
  position: relative;
  color: inherit;
  text-decoration: none;
}

.ad-card__image {
  width: 100%;
  display: block;
  object-fit: cover;
  background: #3a3a3a;
}

.ad-card__sponsored {
  position: absolute;
  top: 0.5rem;
  left: 0.5rem;
  background: rgba(0, 0, 0, 0.65);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  padding: 0.2rem 0.5rem;
  border-radius: 4px;
}

.ad-card__name {
  display: block;
  margin-top: 0.4rem;
  font-size: 0.9rem;
  font-weight: 600;
}

/* Content feed: same 16:9 card shape as a video/article facade. */
.feed-item--ad .ad-card__image {
  aspect-ratio: 16 / 9;
}

/* Gallery grid: same square cell as a real photo. Name has no room as a
   caption below (the grid never shows captions), so it's an overlay strip
   instead — still visibly present, per "image + name + Sponsored". */
.gallery-grid__ad {
  position: relative;
  aspect-ratio: 1;
  overflow: hidden;
  background: #3a3a3a;
}

.gallery-grid__ad .ad-card__link,
.gallery-grid__ad .ad-card__image {
  height: 100%;
}

.gallery-grid__ad .ad-card__name {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  margin: 0;
  background: rgba(0, 0, 0, 0.65);
  color: #fff;
  font-size: 0.65rem;
  font-weight: 600;
  padding: 0.2rem 0.35rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Reels: full-screen black slide, centered card — never a live player
   (no data-reel-vimeo-id on this slide, see ad-slot-reel.php). */
.reel-slide--ad {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #000;
}

.reel-slide--ad .ad-card__link {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  color: #fff;
  padding: 2rem;
}

.reel-slide--ad .ad-card__image {
  max-width: 80%;
  max-height: 60vh;
  object-fit: contain;
  border-radius: 8px;
}

.reel-slide--ad .ad-card__sponsored {
  position: static;
  display: inline-block;
  margin-top: 1rem;
}

.reel-slide--ad .ad-card__name {
  color: #fff;
  font-size: 1.1rem;
}

/* Series Sponsor credit — category page + its post detail pages. */
.series-sponsor-credit {
  background: #f7f3ea;
  border: 1px solid #ecdfc0;
  border-radius: 6px;
  padding: 0.5rem 0.85rem;
  font-size: 0.85rem;
  color: #6b5a2e;
  margin: 0.5rem 0 1rem;
}

.series-sponsor-credit a {
  color: inherit;
  font-weight: 700;
}

/* ==========================================================================
   Bottom tab bar — quick access to the 4 main sections on tablet and
   smaller. Companion to (not a replacement for) the offcanvas nav, which
   still owns categories and everything else.

   Breakpoint: 1024px, the common tablet/desktop line (landscape tablets and
   down get the bar; laptop/desktop widths don't) — the top nav carries
   navigation on its own above that width, so a bottom bar would just be
   redundant chrome there.

   Black bar, white icons/labels (21:1 contrast) so the bar reads cleanly
   against any page background; the active tab switches to a brighter
   accent blue (#4da3ff, ~8:1 contrast against the black bar) plus a bolder
   stroke and filled icon so "where I am" is obvious against the white
   default. This accent is a lightened variant of the admin dashboard's
   #2563a8 (same ~211° hue as the brand mark's ring, im-logo.png) — #2563a8
   itself reads fine on white but is too dark to pop against black.
   ========================================================================== */

.bottom-tab-bar {
  display: none;
}

@media (max-width: 1024px) {
  body {
    /* One place this is defined; reused below for both the bar's own
       height and the footer's compensating bottom padding, so the two
       can never drift out of sync. The extra 8px is breathing room below
       the icons/labels, ON TOP OF env(safe-area-inset-bottom) — it stacks
       with the safe area rather than replacing it, so the bar still clears
       the iPhone home indicator. Falls back to 0px safe-area on browsers
       without the env() function. */
    --bottom-tab-bar-height: calc(56px + 8px + env(safe-area-inset-bottom, 0px));
  }

  .bottom-tab-bar {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 900;
    height: var(--bottom-tab-bar-height);
    /* Bottom: safe-area inset + 8px extra room. Top: the same 8px so the
       bar reads balanced rather than lopsided (top has no notch to clear,
       so it doesn't need the env() term). */
    padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 8px);
    padding-top: 8px;
    background: #000;
    border-top: 1px solid rgba(255, 255, 255, 0.15);
    box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.35);
  }

  /* Last in-flow element on every normal page — padding here (rather than
     on <main>) guarantees nothing before it, including main's own last
     child, can ever end up hidden behind the fixed bar, regardless of
     which page's content happens to be shortest. */
  footer {
    padding-bottom: var(--bottom-tab-bar-height);
  }

  /* Hidden in the two immersive full-viewport views instead of resizing
     them to fit around it: the reels feed IS the whole page (no non-
     immersive state to preserve the bar for), and the gallery's full-
     screen photo viewer (body.gallery-viewer-open, toggled by
     gallery-viewer.js) already sits above everything at z-index 3000 — the
     bar would be invisible behind it anyway, this just also removes it
     from the tab order while that overlay is open. */
  body.is-reels .bottom-tab-bar,
  body.gallery-viewer-open .bottom-tab-bar {
    display: none;
  }

  body.is-reels footer,
  body.gallery-viewer-open footer {
    padding-bottom: 0;
  }
}

.bottom-tab-bar__tab {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 44px;
  color: #fff;
  text-decoration: none;
}

.bottom-tab-bar__icon {
  width: 24px;
  height: 24px;
  stroke-width: 1.8;
}

.bottom-tab-bar__icon-fill {
  fill-opacity: 0;
}

.bottom-tab-bar__label {
  font-size: 0.6875rem;
  font-weight: 500;
  line-height: 1;
}

.bottom-tab-bar__tab.is-active {
  color: #4da3ff;
}

.bottom-tab-bar__tab.is-active .bottom-tab-bar__icon {
  stroke-width: 2.2;
}

.bottom-tab-bar__tab.is-active .bottom-tab-bar__icon-fill {
  fill-opacity: 0.15;
}
