/*
Theme Name: The Refinery
Theme URI: https://therefinerycreative.com
Author: The Refinery
Description: Custom theme for The Refinery — Entertainment + Brands. Ports the static site into WordPress with Key Art and Brand Campaign custom post types.
Version: 0.52.0
Requires at least: 6.4
Requires PHP: 8.0
Text Domain: refinery
*/

/*
  Most styling lives in assets/styles.css, which is a COPY of the site's shared
  styles.css kept in step by bin/sync-theme-assets. Do not edit that file here —
  the next sync overwrites it.

  This file is for WordPress-only adjustments that must survive the sync. It is
  enqueued after the shared sheet, so anything below wins.
*/

/* ---------------------------------------------------------------------------
   Dark by default
   ---------------------------------------------------------------------------
   The whole site went dark on 2026-08-07, and the shared sheet handles that by
   putting `bg-dark` on individual sections. Every WordPress template was ported
   BEFORE that change and none of them carried it, so `.section` — which sets
   padding and nothing else — left a white background under `.post-body p`,
   which hard-codes rgba(255,255,255,0.68). White text on white.

   The templates are fixed, but relying on seven files each remembering a class
   is the same shape of bug as a version number written down twice: it only has
   to be forgotten once. So the default lives here, and the light bands
   (.bg-chartreuse, .cta-section, .logos-section) override it explicitly as they
   always did.
   --------------------------------------------------------------------------- */
body {
  background: var(--dark);
  color: var(--white);
}

/* The editor's blocks land inside .post-body / .entry-content. The shared sheet
   styles .post-body, but Gutenberg output also needs headings and links to be
   legible on dark. */
.entry-content,
.post-body {
  color: rgba(255, 255, 255, 0.78);
}

.entry-content h1,
.entry-content h2,
.entry-content h3,
.entry-content h4,
.post-body h1,
.post-body h2,
.post-body h3,
.post-body h4 {
  color: var(--white);
}

.entry-content a,
.post-body a {
  color: var(--chartreuse);
}

.entry-content blockquote,
.post-body blockquote {
  border-left: 3px solid var(--chartreuse);
  padding-left: 1.25rem;
  color: rgba(255, 255, 255, 0.85);
}

.entry-content code,
.post-body code {
  background: rgba(255, 255, 255, 0.08);
  padding: 0.1em 0.35em;
  border-radius: 3px;
}

.entry-content img,
.post-body img {
  max-width: 100%;
  height: auto;
}

/* ==========================================================================
   Accent words — Poppins Light, replacing the brush script (2026-08-13)
   ==========================================================================

   The vector brush script is GONE. Accent words are now Poppins Light 300,
   chartreuse, at exactly the size of the type they sit inside.

   This overrides `.accent-brush` in assets/styles.css, which is a synced copy
   of the static build and cannot be edited here. Every value it sets exists to
   compensate for the brush: caps rendered ~0.73x Poppins at the same em, so it
   sized up to 1.18em, pulled the baseline with vertical-align, tightened
   word-spacing because the font's space advance was ~0.378em, and crushed
   line-height. All of that is wrong for a Poppins accent and is unset here.

   `font-family: inherit` rather than naming Poppins: the accent should take
   whatever family its heading uses, so it can never drift from the heading it
   sits in. Only weight and colour differ.

   .brush-default is neutralised rather than stripped from the markup — it
   appears on 12 accents across the templates and its only job was loading the
   brush family and its kern/calt features. Leaving the class inert is safer
   than a find-and-replace across every template, and the rule below documents
   why it is a no-op.

   THE LOWERCASE-MARKUP RULE IS LIFTED. Accents had to be written lowercase
   because the brush font's kern table covered A-Z only, so a lowercase accent
   rendered completely unkerned. Poppins has no such gap — markup casing is now
   free, and the uppercase look comes from text-transform as it does for every
   other display heading. */

.accent-brush,
.brush-default {
  font-family: inherit;
  font-weight: 300;
  font-style: normal;

  /* Chartreuse here is the FALLBACK ONLY — the swap below repaints it white.
     Left as the old value on purpose so a browser without :has() keeps today's
     look rather than losing the accent entirely. See the block below. */
  color: var(--chartreuse);

  /* Same size as the surrounding bold/black type — the whole point of the
     change. No optical compensation, because there is nothing to compensate. */
  font-size: 1em;
  line-height: inherit;
  vertical-align: baseline;
  letter-spacing: inherit;
  word-spacing: normal;

  /* The brush shipped kern + calt features and swapped repeated letters for
     alternates. Poppins needs none of it, and leaving them on would split the
     shaping run for no benefit. */
  font-feature-settings: normal;
  font-kerning: auto;
}

/* ==========================================================================
   Accent / heading colour swap — light = white, heavy = chartreuse (2026-08-17)
   ==========================================================================

   Kay: "swap the chartreuse/white font combos, so lightweight = white, heavy =
   green." The WEIGHTS do not move — the colours trade places:

     before   THE (300, chartreuse)  PORTFOLIO (900, white)
     after    THE (300, white)       PORTFOLIO (900, chartreuse)

   SCOPE: ONLY HEADINGS THAT ACTUALLY CONTAIN AN ACCENT.
   Confirmed with Kay 2026-08-17. A display heading with no accent word is not
   a "combo" and stays white — about half of them. Turning every 900-weight
   heading chartreuse was the other reading and is a much larger change; if that
   is ever wanted, drop the `:has()` and the fallback block with it.

   WHY :has() AND NOT A CLASS IN THE MARKUP
   The condition is "this heading contains an accent", which is exactly what
   `:has()` asks. The alternative is a second class on ten headings that must be
   kept in sync with the presence of an `<em>` inside them — two facts about one
   thing, which is the failure this codebase keeps writing down. `:has()` is
   already relied on in the shared sheet (`.keyart-item:has(.rfn-slot-tag)`).

   THE @supports GUARD IS NOT CEREMONY.
   Without it, a browser that cannot parse `:has()` drops the whole selector
   list, keeps the heading white, and still applies a white accent — white on
   white, and the accent silently disappears. Inside the guard, both halves land
   together or neither does, so an unsupporting browser simply keeps the old
   chartreuse-accent look. Nothing is ever unreadable.

   SPECIFICITY: `.t-display-lg:has(.accent-brush)` is (0,2,0) and beats the
   `.text-white` utility (0,1,0) that all ten of these headings carry, whatever
   the source order. That is why the markup does not need touching.

   NOT INCLUDED, DELIBERATELY:
   `.cta-title` — it is 900 weight, but it sits on a CHARTREUSE band and is
   `color: var(--dark)` for that reason. Chartreuse-on-chartreuse would erase
   it. None of the four cta-titles carries an accent anyway, so the rule below
   would never match one; it is called out here so nobody "completes" the set
   later without reading this paragraph. Same goes for anything inside
   `.bg-chartreuse`. */

@supports selector(:has(*)) {

  /* The light word: white. */
  .accent-brush,
  .brush-default {
    color: var(--white);
  }

  /* Everything around it: chartreuse. `.accordion-title` is in the list because
     it is 900 uppercase like the display classes and holds six of the site's
     seventeen accents — leaving it out would have made the Entertainment page
     the one place the old pairing survived. */
  .t-display-lg:has(.accent-brush),
  .t-display-md:has(.accent-brush),
  .accordion-title:has(.accent-brush) {
    color: var(--chartreuse);
  }
}

/* ==========================================================================
   WordPress admin bar offset (2026-08-13)
   ==========================================================================

   Logged-in only, and invisible to visitors — but it makes the site look broken
   to anyone editing it, which is most of the time during a build.

   WordPress pushes the document down with `html { margin-top: 32px !important }`
   when the admin bar is showing. `.site-nav` is `fixed; top: 0` and `.sub-nav`
   is `sticky; top: var(--nav-height)`, and BOTH are anchored to the viewport, so
   neither moves with that margin. Flow content does. The result is that the
   chrome and the content desynchronise by exactly the admin bar's height:

     - the fixed nav slides under the admin bar and its logo is clipped
     - `.ent-reel` gains a 32px black band between the sub-nav and the video,
       because the reel's `padding-top: var(--nav-height)` now starts 32px lower
       than the sub-nav renders

   Measured on the Entertainment page: sub-nav bottom 112, video top 144.
   Re-anchoring the chrome by the same amount closes it exactly.

   The admin bar is not one height. WordPress: 32px normally, 46px at <=782px,
   and at <=600px it stops being fixed and scrolls away with the page — so
   below that the offset must be REMOVED or the chrome floats 46px too low. */

body.admin-bar .site-nav {
  top: 32px;
}

body.admin-bar .sub-nav {
  top: calc(var(--nav-height) + 32px);
}

@media screen and (max-width: 782px) {
  body.admin-bar .site-nav {
    top: 46px;
  }

  body.admin-bar .sub-nav {
    top: calc(var(--nav-height) + 46px);
  }
}

@media screen and (max-width: 600px) {
  /* Admin bar becomes position:absolute here and scrolls out of the way, so the
     fixed chrome goes back to its normal anchoring. */
  body.admin-bar .site-nav {
    top: 0;
  }

  body.admin-bar .sub-nav {
    top: var(--nav-height);
  }
}

/* ---------------------------------------------------------------------------
   Production groups — the +N badge (2026-08-18)
   ---------------------------------------------------------------------------
   A tile fronting a production group (refinery_group_gallery_posts()) carries
   .keyart-item--group and this badge, so a visitor can tell "there is more
   behind this one" before clicking. Lives here rather than portfolio.css
   because groups render anywhere refinery_gallery_tile() does — the
   entertainment accordions included.
   --------------------------------------------------------------------------- */
.keyart-item .keyart-count {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  z-index: 3;
  padding: 0.18rem 0.5rem;
  border-radius: 999px;
  background: rgba(10, 10, 10, 0.72);
  color: var(--white);
  font-size: 0.68rem;
  font-weight: 800;
  letter-spacing: 0.06em;
  font-variant-numeric: tabular-nums;
  pointer-events: none;
}

.keyart-item:hover .keyart-count,
.keyart-item:focus-visible .keyart-count {
  background: var(--chartreuse);
  color: var(--dark);
}

/* ---------------------------------------------------------------------------
   Post-header roundel — blog + case study singles (2026-08-20)
   ---------------------------------------------------------------------------
   The same offset R roundel every chartreuse band carries top-right (see the
   .cta-section::before block in assets/styles.css — same mask, same -70px
   crop geometry, for the same reason: the R only reads as the logo with both
   the stem and the eye in frame). Here it sits on the DARK page heros, in
   near-black — a watermark, not a badge.

   Same @supports discipline as the original: ungated, the mask rule would
   paint a 420px black SQUARE in an old browser. The fallback is a soft
   circle; browsers that can mask get the mark.
   --------------------------------------------------------------------------- */
body.single-post .page-hero,
body.single-case_study .page-hero {
  position: relative;
  overflow: hidden;
}

/* The watermark must sit UNDER the title. An absolutely-positioned
   pseudo-element paints above static in-flow text, so at any alpha it dims
   whatever it overlaps — positioning the container above it is the fix, not
   a lower alpha. */
body.single-post .page-hero .container,
body.single-case_study .page-hero .container {
  position: relative;
  z-index: 1;
}

body.single-post .page-hero::before,
body.single-case_study .page-hero::before {
  content: '';
  position: absolute;
  top: 0;
  right: -60px;
  /* Sized to the HERO, capped at the family's 420px: a two-line title makes
     a short hero, and a fixed-height mark was hard-cut at its bottom edge
     there while showing whole on four-line heros — the "clips on some
     pages" report of 2026-08-20. min(…, 100%) keeps it in frame anywhere. */
  height: min(400px, 100%);
  aspect-ratio: 1;
  width: auto;
  background: rgba(0, 0, 0, 0.1);
  border-radius: 50%;
  pointer-events: none;
}

@supports ((-webkit-mask-image: none) or (mask-image: none)) {
  body.single-post .page-hero::before,
  body.single-case_study .page-hero::before {
    /* Kay's dial 2026-08-20: fully in frame vertically (top 0), lighter
       touch than the chartreuse bands' 0.15-on-light — 0.1 black on the
       dark hero is barely-there, which is the point. */
    top: 0;
    right: -60px;
    height: min(420px, 100%);
    aspect-ratio: 1;
    width: auto;
    background: none;
    background-color: rgba(0, 0, 0, 0.1);
    border-radius: 0;
    -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 184.03 184.03'%3E%3Cpath d='M92.02,0C41.2,0,0,41.2,0,92.02s41.2,92.02,92.02,92.02,92.02-41.2,92.02-92.02S142.84,0,92.02,0ZM90.47,128.8c0,10.55-8.55,19.1-19.1,19.1s-18.4-7.82-19.1-17.74V59.23c0-10.55,8.55-19.1,19.1-19.1s19.1,8.55,19.1,19.1v69.57ZM124.7,78.34c-10.55,0-19.1-8.55-19.1-19.11s8.55-19.1,19.1-19.1,19.1,8.55,19.1,19.1-8.55,19.11-19.1,19.11Z'/%3E%3C/svg%3E") center / contain no-repeat;
    mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 184.03 184.03'%3E%3Cpath d='M92.02,0C41.2,0,0,41.2,0,92.02s41.2,92.02,92.02,92.02,92.02-41.2,92.02-92.02S142.84,0,92.02,0ZM90.47,128.8c0,10.55-8.55,19.1-19.1,19.1s-18.4-7.82-19.1-17.74V59.23c0-10.55,8.55-19.1,19.1-19.1s19.1,8.55,19.1,19.1v69.57ZM124.7,78.34c-10.55,0-19.1-8.55-19.1-19.11s8.55-19.1,19.1-19.1,19.1,8.55,19.1,19.1-8.55,19.11-19.1,19.11Z'/%3E%3C/svg%3E") center / contain no-repeat;
  }
}

/* --------------------------------------------------------------------------
   Missing utility: .justify-center                          (2026-08-21)

   The shared sheet ships `.flex`, `.items-center` and `.justify-between` but
   never defined `.justify-center` — so the two templates that use it
   (archive-key_art.php and archive-case_study.php, both the button row under
   the grid) had `display:flex` with the default `justify-content: normal` and
   packed their buttons against the LEFT edge of the container.

   It reads as a centring bug rather than a missing class, because the row is
   full-width and the text above it really is centred, so the buttons look
   nudged off-centre rather than left-aligned. Kay spotted it on /portfolio/,
   where the effect was doubled: two of the three buttons were invisible
   (btn-outline-dark on bg-dark), so the one you could see sat a third of the
   way across with nothing to explain why.

   Defined here rather than in assets/styles.css, which bin/sync-theme-assets
   overwrites from the static build.
   -------------------------------------------------------------------------- */
.justify-center { justify-content: center; }

/* --------------------------------------------------------------------------
   Card thumbnails with a REAL image                          (2026-08-21)

   `.blog-card-thumb` in the synced sheet is placeholder furniture from the
   static build: a dashed-bottom grey box, flex-centred, with the words
   "Image placeholder" in an `::after`. Every card on the static site was
   empty, so that was the whole component.

   The templates have emitted `.has-image` alongside it since the port —
   home.php, archive-case_study.php and single.php all do — but NOTHING has
   ever styled it. So a card with a real featured image rendered the
   placeholder box, the placeholder text AND the image on top of each other:

     - the `::after` still printed "Image placeholder", which showed to the
       right of any image narrower than the card;
     - the image was a flex item at its intrinsic size with `object-fit: fill`
       (the default), so it overflowed the box by ~46px and defeated the
       `aspect-ratio: 16 / 10` — a portrait card came out 595px tall next to a
       landscape one at 283px, which is the "cropped weird / odd heights" Kay
       photographed on the blog;
     - the dashed rule stayed under it.

   Fixed here rather than in assets/styles.css, which bin/sync-theme-assets
   overwrites from the static build.

   The image is absolutely positioned rather than `width:100%; height:100%`.
   That is deliberate: it was intrinsic sizing that beat `aspect-ratio` in the
   first place, and an out-of-flow image cannot contribute any intrinsic size
   back to the box. The 16/10 box then always wins.
   -------------------------------------------------------------------------- */
.blog-card-thumb.has-image {
  display: block;
  position: relative;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.04);
  border-bottom: 0;
}

.blog-card-thumb.has-image::after {
  content: none;
}

.blog-card-thumb.has-image img {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* --------------------------------------------------------------------------
   Author byline — blog cards and the single post
   ..........................................................................
   Picture plus name, never a date: the site omits post dates everywhere on
   purpose (see the headers of home.php and single.php). Lives here rather
   than in article.css because the blog INDEX uses it too, and the index does
   not load the article sheet.
   -------------------------------------------------------------------------- */
.author-byline {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 1rem 0 0;
}

.author-byline-avatar {
  border-radius: 50%;
  object-fit: cover;
  flex: 0 0 auto;
  /* A ring rather than a border: the avatars are circular crops on a dark
     ground and without it a dark-edged photo dissolves into the card. */
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.18);
}

.author-byline-name {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  color: rgba(255, 255, 255, 0.72);
}

/* On a post hero the byline sits under a display heading, so it can carry a
   little more weight and air than it does on a card. */
.author-byline--hero {
  margin-top: 1.5rem;
}
.author-byline--hero .author-byline-name {
  font-size: 0.9375rem;
  color: rgba(255, 255, 255, 0.8);
}

/* ==========================================================================
   MB-2 — the client logo strip on a phone (2026-09-02)
   ==========================================================================
   Adam: "Bar with clients is way too thick, and glitchy."

   Two separate faults, both desktop values reaching mobile unchanged. Neither
   is a glitch — nothing is actually animating wrong.

   THICK. The shared sheet's mobile block only trims .logos-section padding to
   2rem (64px). On top of that sit .logos-label's 2rem margin-bottom (32px),
   the label itself, and .logo-item's 78px row height: about 174px of chartreuse
   band on a screen 844px tall. That is a fifth of the viewport spent on a
   supporting element.

   "GLITCHY". .logos-track-wrapper::before / ::after are the chartreuse edge
   fades and they are 120px WIDE EACH. On a 390px phone that is 240px of 390px —
   62% of the screen — leaving a clear window of about 150px in the middle. One
   logo shows, the rest sit under the fades, and it reads as a bar that keeps
   losing its content. The fades exist to soften the track entering and leaving
   the viewport; at phone widths they have to be a fraction of that.

   Scoped to the theme sheet rather than the shared one on purpose: the shared
   styles.css is a synced copy and the next bin/sync-theme-assets would drop
   anything written there.
   ========================================================================== */
@media (max-width: 768px) {
  .logos-section {
    padding: 1.25rem 0;
  }

  .logos-label {
    margin-bottom: 0.9rem;
  }

  .logo-item {
    /* The logos carry their own heights inline and are scaled 1.5x in the
       markup; this row height tracks that scale, so bringing it down is the
       one value that shrinks every logo together and keeps the optical
       compensation from build-logos.py intact. */
    height: 46px;
  }

  .logo-item img {
    /* The inline height is the 1.5x desktop figure. Cap it to the row so a
       tall logo (Sony) cannot push past the shorter band. */
    max-height: 100%;
  }

  .logos-track {
    gap: 2.25rem;
  }

  .logos-track-wrapper::before,
  .logos-track-wrapper::after {
    width: 36px;
  }
}

/* ==========================================================================
   PAGE HEROES — one measure, one rhythm (theme 0.34.0, Kay 2026-09-08)
   ==========================================================================

   THE PROBLEM. Every page hero had invented its own width and its own
   padding. Across nine templates the title caps were 18ch, 20ch, 24ch,
   760px and 820px; the body caps were 60ch, 62ch, 560px, 600px, 620px and
   640px; and the wrapper divs were 780px, 820px and 1200px. Three of the
   headers were not even .page-hero — they were bare <header> elements with
   the background and padding inline. That is what reads as "clipped to the
   left at odd widths": the text stops at a different place on every page,
   and none of those places relate to anything.

   Most of those caps are a fossil. These heroes originally had artwork on
   the right, so the copy was squeezed into a column beside it. The artwork
   went; the squeeze stayed.

   THE RULE. Title and body share ONE measure — 95% of the container — and
   stay left-aligned. One number, applied to both, so a hero cannot drift
   again. It was 75% when this landed on 2026-09-08 ("around 3/4 of the
   width"); Kay widened it to 95% the same day, once the heroes were
   consistent enough to judge the number on its own.

   ⚠️ EVERY INLINE max-width IN A HERO HAD TO GO, not just be overridden.
   An inline style beats a class no matter how specific the class is, and
   this project has already lost a day to exactly that (the --off-white
   background audit; see CLAUDE.md). If you add a hero, give it
   .hero-measure and set NO width on it.
   -------------------------------------------------------------------- */

.hero-measure {
  max-width: 95%;
}

/* Six of the heroes have no wrapper element — they put the eyebrow, the
   headline and the body copy straight into .container. The measure is applied
   to those children BY NAME rather than with `> *`, because two of these
   headers also carry decorative absolutely-positioned children (the Portfolio
   hero's overflow layer; Merch's tiled wordmark did too, until that page was
   retired on 2026-09-09) and capping one of those at 75% would clip the
   decoration instead of the copy.

   The cap must not go on .container itself: it is `margin: 0 auto`, so
   narrowing it would re-centre the whole block and the copy would stop being
   left-aligned — which is the one thing Kay asked to keep. */
.page-hero .container > .section-label,
.page-hero .container > h1,
.page-hero .container > p,
.page-hero .container > .t-body-lg,
.page-hero .rfn-article-head {
  max-width: 95%;
}

/* Below ~900px the measure is released entirely rather than tapered. It
   exists only to stop a line running the full 1376px on a wide screen; on a
   phone there is no excess width to hold back, and at 95% the leftover 5%
   would just be a ragged margin. */
@media (max-width: 900px) {
  .hero-measure {
    max-width: 100%;
  }
}

/* The vertical rhythm, which was as inconsistent as the widths: bottom
   padding ran 1.5rem, 2rem, 3rem, 3.5rem, 4rem and 5rem across the same
   nine templates, and the top was a mix of `--nav-height + 4rem`,
   `+ 4.5rem`, `+ 5rem` and a couple that added --subnav-height on pages
   that have no sub-nav.

   Kay: "kill a lot of the empty space above and below the title blocks …
   consistent across all pages with the same header format."

   Top padding still has to clear the fixed nav, so it stays a calc; only
   the free space above and below the type is cut. The sub-nav is added by
   .page-hero--subnav rather than baked in, because only the About family
   and the two article templates carry one — adding it unconditionally put
   a sub-nav's worth of blank space on Portfolio and Case Studies, which
   have none. */
.page-hero {
  padding: calc(var(--nav-height) + 2.5rem) 0 2.5rem;
}

.page-hero--subnav {
  padding-top: calc(var(--nav-height) + var(--subnav-height) + 2.5rem);
}

/* The eyebrow sat 1.5rem off the headline via the shared .section-label
   rule, which is tuned for a section heading further down a page. In a
   hero it is a label on the line directly beneath it. */
.page-hero .section-label {
  margin-bottom: 0.85rem;
}

/* The gap under a hero headline was set per-template too (1.25rem inline
   on five of them, nothing at all on the rest). One value here; the inline
   ones are removed. */
.page-hero h1 {
  margin-bottom: 1rem;
}

.page-hero h1:last-child {
  margin-bottom: 0;
}

/* ==========================================================================
   FOOTER — one line (theme 0.34.0, Kay 2026-09-08)
   ==========================================================================
   Was two rows: [mark][Work With Us] ... [5 nav links], then a rule, then
   [copyright] ... [social]. Now a single row —
   [mark][Work With Us] .................... [social].

   The nav link row went 2026-09-08 (it repeated the header nav directly
   above it), and the copyright line went the same day (Kay) — with it gone
   the socials are the only thing on the right, which is where they were
   asked to sit. The dividing rule and the .footer-bottom band went with the
   second row.

   .footer-grid / .footer-bottom still exist in the shared styles.css (it is
   a synced copy of the static build and must not be edited), they are just
   no longer emitted. Nothing here overrides them; this is a new class. */

.footer-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 1.25rem 2rem;
}

/* align-items: center, not stretch. The old .footer-brand stretched so the
   roundel matched the button height automatically, but that rule lived in a
   two-item row; a stretch here makes the icon links tall and their hit areas
   ragged. */
.footer-bar .footer-brand {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.footer-bar .footer-logo {
  display: flex;
  align-items: center;
}

.footer-bar .footer-social {
  display: flex;
  align-items: center;
  gap: 0.9rem;
}

/* The socials are the only thing on the right now. margin-left:auto rather
   than relying on space-between alone: once the row wraps on a narrow screen
   space-between stops applying per-line, and without this they would sit
   left under the mark. */
.footer-bar .footer-social {
  margin-left: auto;
}

/* NO STACKING BREAKPOINT (Kay, 2026-09-08: "doesn't need to be stacked on the
   left, social icons will fit on the right and everything can stay on one
   line, like desktop").

   There WAS a ≤700px block here turning the row into a left-aligned column.
   It was written defensively before the copyright line came out, when the row
   held four things; with three — mark, button, socials — it fits a 390px
   phone with room over, and stacking just made the footer taller and put the
   socials in a place nothing else on the site puts them.

   The row keeps `flex-wrap: wrap`, so this is not a promise that it can never
   wrap — it is the removal of a rule that FORCED it to. If a longer button
   label ever pushes it over, it wraps on its own and margin-left:auto still
   holds the socials right on the line they land on. */

/* Removing the stack rule was necessary but not sufficient — MEASURED at
   390px: container 350, content 301 (logo 44 + button 171 + socials 86), gaps
   24 + 32 = 357. Seven pixels over, so it wrapped anyway and the socials
   dropped to a second line looking exactly like the stack that had just been
   removed.

   The gaps are the entire overage; nothing needs to be smaller. Tightened
   here rather than globally because at desktop width the wider gaps are doing
   real work — there the row is 1376px and 32px of separation reads as
   deliberate rather than cramped. */
@media (max-width: 700px) {
  .footer-bar {
    gap: 0.75rem;
  }

  .footer-bar .footer-brand {
    gap: 0.75rem;
  }

  .footer-bar .footer-social {
    gap: 0.6rem;
  }
}

/* Measured: with the gaps tightened the row holds one line down to 375px and
   wraps at 360 — and 360 is one of the most common Android widths, so it is
   worth closing rather than writing off.

   At 360 the container is 296px and the content is 301 before any gap at all,
   so gaps have nothing left to give. The two biggest items come down instead:
   the mark, and the button's horizontal padding (it is 171px, over half the
   row). Scoped to ≤374px so the sizes everywhere this ALREADY fits are
   untouched — this is a last-resort tier, not a phone style. */
@media (max-width: 374px) {
  .footer-bar .footer-logo svg {
    width: 34px;
    height: 34px;
  }

  .footer-bar .footer-cta {
    padding-left: 1.1rem;
    padding-right: 1.1rem;
    font-size: 0.8125rem;
  }

  .footer-bar .footer-social svg {
    width: 16px;
    height: 16px;
  }
}

/* ---------------------------------------------------------------------------
   Custom cursor — give the mark its own compositor layer
   ---------------------------------------------------------------------------
   Reported 2026-09-09: the cursor feels laggy for some people, "especially in
   Chrome". Measured on /entertainment/: the dot's computed `will-change` was
   `auto`, i.e. it was NOT promoted, while the same page carries 15 <video>
   elements and 16 running CSS animations — all of which Chrome does put on
   their own layers.

   That combination is the whole bug. An unpromoted `position: fixed` element
   whose transform changes every frame lives in the page's own layer, so each
   move dirties the tiles underneath it and Chrome re-rasters whatever
   composited content it happens to be passing over. On a gallery page that is
   video. The cost therefore scales with what is BEHIND the cursor, which is why
   it reads as "fine on the About page, awful on Entertainment", and why a
   machine under memory pressure (many tabs → GPU process contention) feels it
   first. Firefox and Safari promote more eagerly, hence "especially in Chrome".

   `will-change: transform` moves the mark onto its own 30x30 layer, so the
   per-frame update is a compositor transform and paints nothing. That is the
   fix; the rest here is belt and braces.

   Deliberately in the theme sheet, not in assets/styles.css: that file is a
   synced copy and bin/sync-theme-assets would overwrite this on the next run.

   ⚠️ This does NOT make the cursor immune to a busy main thread. Position is
   still lerped in a rAF loop in script.js, so a long task still stalls it while
   the real pointer keeps moving — that is inherent to any JS cursor. It only
   stops the cursor from being the thing CAUSING the work.
   -------------------------------------------------------------------------- */
.cursor-dot {
  will-change: transform;
  /* Belt and braces: some Chrome builds still fold a 2D translate back into the
     parent layer. A 3D transform cannot be folded, and the JS only ever writes
     the `transform` property, so this stays as the layer hint rather than being
     overwritten. */
  backface-visibility: hidden;
}

/* The SVG inside repaints on the fill transition (hover, and crossing into a
   chartreuse band). Isolating it keeps that repaint inside the 30px layer
   instead of invalidating the ancestor. */
.cursor-dot svg {
  will-change: fill;
}

/* ---------------------------------------------------------------------------
   Client marquee — the seam jumped by exactly half a gap
   ---------------------------------------------------------------------------
   Reported 2026-09-09: "an abrupt jump on the client marquee bar… when Bleeker
   Street entered from the right on a 1460px wide display", plus reports of the
   same on mobile.

   THE ARITHMETIC. `initLogoTrack()` clones the 25 logos once, so the track
   holds 50 items, and `scrollLogos` animates `translateX(-50%)`. With
   `gap: 4rem` those two do not describe the same distance:

     track width   W  = 2·Σw + (2N−1)·gap      ← 49 gaps between 50 items
     one set STRIDE S =   Σw +      N·gap      ← what a seamless loop must move
     W / 2            =   Σw + (N − ½)·gap

     S − W/2 = ½·gap

   `-50%` lands **half a gap short**, so the reset snaps the row forward by that
   much, once per cycle, forever. Measured on the live page at 1460px: 50 items,
   64px gap, track 8760.33px — required translate 4412.16px, actual 4380.16px,
   **jump 32px**, exactly ½·gap as predicted.

   It is width-independent in SIZE and width-dependent in WHERE, which is why it
   is hard to pin on a logo: the jump is always 32px, but which logo is crossing
   the right edge when it fires depends on the viewport. At 1460px that is
   Peacock/Universal, a few places along from Bleecker Street.

   It is worse per-pixel on a phone, which is the mobile report: ½ of 2.25rem is
   18px on a ~390px screen.

   THE FIX. Move by the real stride. The gap is now a single custom property
   that BOTH the flex gap and the keyframe read, so the two can never drift —
   change `--logo-gap` alone and the loop stays seamless.

   Redefining `@keyframes scrollLogos` here is deliberate and legal: a later
   sheet fully replaces an earlier animation of the same name, and
   assets/styles.css is the synced copy that bin/sync-theme-assets overwrites,
   so the original cannot be edited in place.
   -------------------------------------------------------------------------- */
.logos-track {
  --logo-gap: 4rem;
  gap: var(--logo-gap);
}

@media (max-width: 768px) {
  .logos-track {
    --logo-gap: 2.25rem;
  }

  /* The Entertainment strip's slim variant sets its own gap in
     entertainment.css. Two selectors deep so this still outranks it — same
     reason that sheet's own comment gives for using the extra class. */
  .logos-section--slim .logos-track {
    --logo-gap: 1.75rem;
    gap: var(--logo-gap);
  }
}

@keyframes scrollLogos {
  from { transform: translateX(0); }
  to   { transform: translateX(calc(-50% - (var(--logo-gap, 4rem) / 2))); }
}

/* An infinite 50s crawl is precisely what this query is for, and the strip was
   not covered — the only reduced-motion rule in the synced sheet is the cursor.
   Stop it and leave the logos where they are; nothing about the row depends on
   movement to be readable. */
@media (prefers-reduced-motion: reduce) {
  .logos-track {
    animation: none;
  }
}
