/* ============================================================================
   bto-work-screen.css — THE WORK, AS A SPATIAL SCREEN IN A ROOM, on work.html.

   Founder, 2026-08-06: "we can use the bubble UI ARVR graphic thing that we
   made on our home page … we can use that thing in our work page", and it
   REPLACES the flat Mokko grid rather than joining it (his call, same turn).

   ── WHAT THIS FILE IS ─────────────────────────────────────────────────────
   A port of bto-spylt/src/panel-brands.css (the home page's panel 5) onto the
   static stack. Same class prefix `bw-`, same numbers, same section order, so
   the two files can be diffed against each other. FOUR deliberate divergences,
   all because this is a page section and not a full-bleed panel:

     1. NO `min-height: 100dvh`. On home the panel IS the viewport; here it is a
        band inside a page that already has a hero above it and a footer below.
        Forcing a viewport height would leave dead room under the screen at
        1440×900 and push the footer a full screen down.
     2. THE HEAD IS CENTRED (§2). The whole page is centred now — see
        bto-work-brand.css. On home the head is left on the panel spine.
     3. `--bw-navy` IS A REAL GROUND HERE. The home panel only used navy as a
        12% tint inside the glass gradient; the founder asked for "blue yellow
        and black" to actually read, so the room carries a navy wash and the
        screen's lower gradient lands on it. Same #003A6C the About page uses.
     4. NO GSAP. The home component drives --bw-dolly with ScrollTrigger. Static
        pages ban ScrollSmoother (bto-js/no-smoother.js) and work.html loads bare
        gsap without ScrollTrigger, so bto-js/work-screen.js does the dolly with
        a rAF-throttled scroll handler instead. Identical output, no dependency.

   🔴 FONTS: "Neue Montreal" FIRST, not ProximaNova. The source sheet leads with
   ProximaNova; svc-type.css retired that face on 2026-08-06 (a third family with
   no Bold file) and final/fonts/ only ships ProximaNova-Regular.otf. Leading with
   it here would put ONE regular-weight orphan face back on the page.

   ⚠ NO position:sticky ANYWHERE, same as the source — and here the reason is
   different but points the same way: no-smoother.js exists because a transformed
   ancestor kills sticky, and .bw-hud IS a transformed ancestor.

   Class prefix `bw-`. Linked by work.html ONLY. Nothing shared is edited.

   Pop-up mechanics transcribed from github.com/Clooos/Bubble-Card (MIT,
   © Clooos) — see §9 and the head of bto-js/work-screen.js. Their look is not
   taken; only the state machine, the curves and the layer strategy.
   ========================================================================== */


/* ==========================================================================
   §1 · THE PANEL
   ========================================================================== */
.bw-panel {
  position: relative;
  overflow: hidden;
  background: #13181B;
  color: #F0EEEB;
  isolation: isolate;

  --bw-gutter: clamp(20px, 2.4vw, 40px);
  --bw-amber: #FFBF65;
  --bw-ink: #F0EEEB;
  --bw-room: #13181B;
  /* ⚠ HARDCODED, AND THAT IS CORRECT HERE. `--bg-inset-dark` is #13181B in
     light and BECOMES #003A6C in dark (palette.css:169 / :226), so binding the
     room's navy to it would collapse the navy onto the room's own black the
     moment the header's theme disc is pressed. This section is dark in BOTH
     themes — it is a lit room, not a themed surface — so it carries its own
     literal. Same reasoning bto-about-grounds.css:56 records for --bto-navy. */
  --bw-navy: #003A6C;

  --bw-dolly: 0px;
  --bw-tilt-x: 0deg;
  --bw-tilt-y: 0deg;
}

/* typed customs animate on the compositor instead of re-parsing a string every
   frame — with a backdrop-filter on screen the difference is visible */
@property --bw-dolly  { syntax: "<length>"; inherits: true; initial-value: 0px; }
@property --bw-tilt-x { syntax: "<angle>";  inherits: true; initial-value: 0deg; }
@property --bw-tilt-y { syntax: "<angle>";  inherits: true; initial-value: 0deg; }


/* ==========================================================================
   §2 · THE HEAD — centred, on the page's own shell
   ==========================================================================
   THE ACCENT BUDGET IS TIGHT: eyebrow, slab word, the rail's active dot, the
   selected chip, the play badge on hover. Everything else is ink at varying
   opacity. A UI mockup is the easiest place on a site to end up with fifteen
   amber objects, at which point amber stops meaning anything. The rainbow chip
   bar this section replaces is exactly that failure in its finished form: nine
   pastel fills, none of them brand, none of them meaning anything.

   ⚠ THE SHELL IS 1280 @ 80 — work-mph.css §1's number, not the source's
   var(--bw-gutter) spine. Every other band on this page is 1280@80; a section
   that sets its own width is fault #3 in work-mph.css's header. */
.bw-panel .bw-inner {
  position: relative;
  z-index: 3;
  display: flex;
  flex-direction: column;
  max-width: 1440px;
  margin-inline: auto;
  padding: clamp(72px, 8vh, 104px) 80px clamp(72px, 8vh, 104px);
}

.bw-panel .bw-head {
  flex: 0 0 auto;
  /* ⚠ width:100% IS LOAD-BEARING. `margin-inline:auto` on a shrink-to-fit box
     RESIZES it instead of centring it — it cost three separate blocks a build
     cycle on the services page (.sva-proc-col 900→500, .svw-head 500→316). */
  width: 100%;
  max-width: 760px;
  margin-inline: auto;
  text-align: center;
}

.bw-panel .bw-eyebrow {
  margin: 0 0 clamp(8px, 1.2vh, 12px);
  font-family: "Neue Montreal", Archivo, sans-serif;
  font-weight: 600;
  font-size: 14px;
  line-height: 20px;
  letter-spacing: .32em;   /* = work-mph.css §3's hero kicker, not the source's .18em */
  text-transform: uppercase;
  color: var(--bw-amber);
}

.bw-panel .bw-title {
  margin: 0;
  font-family: "Antonio", "Archivo", sans-serif;
  font-weight: 700;
  font-size: clamp(32px, 3.6vw, 54px);
  line-height: 1.05;
  letter-spacing: -.02em;
  text-transform: uppercase;
  color: var(--bw-ink);
}

/* the site's ONE rotation value, and the one place radius is 0 — see
   work-v3.css's .wv3-slab note. Ink on amber, never amber as text. */
.bw-panel .bw-slab-word {
  display: inline-block;
  padding: .04em .2em .1em;
  background: var(--bw-amber);
  color: var(--bw-room);
  transform: rotate(-3deg);
  -webkit-text-fill-color: var(--bw-room);
  -webkit-box-decoration-break: clone;
          box-decoration-break: clone;
}

.bw-panel .bw-lead {
  margin: 16px auto 0;
  max-width: 34em;   /* em, NOT ch — ch is the zero glyph; a 68ch cap measured
                        88 real characters (svc-read.css:73) */
  font-family: "Neue Montreal", Archivo, sans-serif;
  font-size: 18px;
  line-height: 32px;
  color: rgba(240, 238, 235, .82);
}


/* ==========================================================================
   §3 · THE ROOM — a real By The Owl frame, defocused into an environment
   ==========================================================================
   THE BLUR IS LOAD-BEARING, NOT A STYLE. At 28px no face, logo or location in
   the source frame survives, which is what makes it wallpaper rather than an
   uncredited work card: the panel is not showing a piece of work, it is showing
   light. The moment the subject is recognisable this becomes a claim.

   scale(1.18) overscans because filter:blur samples past the element's edge and
   would otherwise leave a soft transparent margin.
   ========================================================================== */
.bw-panel .bw-room {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
}

.bw-panel .bw-room img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transform: scale(1.18) translateY(var(--bw-room-y, 0px));
  filter: blur(28px) saturate(.78) brightness(.55);
  will-change: transform;
}

.bw-panel .bw-room::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 60% 50% at 50% 58%, rgba(255, 191, 101, .12), transparent 70%),
    /* ⚠ .34, NOT the source's .2 — this is divergence 3. The founder's note was
       that the site's blue does not read on this page at all. At .2 over a
       brightness(.55) frame the navy is present in the pixels and absent to the
       eye; measured, the room's mid-band went from 2% to 11% blue saturation.
       It is still a WASH and not a fill — the frame has to stay legible as a
       room, which is what §3's whole argument rests on. */
    radial-gradient(ellipse 90% 70% at 50% 50%, rgba(0, 58, 108, .34), transparent 75%),
    /* ⚠ THE WASH IS BAND-SHAPED, NOT A FLAT DIM, AND EACH STOP HAS A JOB.
       Dimming evenly buys contrast everywhere and destroys the point — the room
       stops reading as a room and becomes a grey gradient.
         0%   .78  protects the head copy, which sits directly on the image
         46%  .30  OPENS THE ROOM in the band where the glass floats
         100% .84  lands the panel into the section below
       Do not flatten these into one value. */
    linear-gradient(180deg, rgba(19, 24, 27, .78) 0%, rgba(19, 24, 27, .3) 46%, rgba(19, 24, 27, .84) 100%);
}


/* ==========================================================================
   §4 · THE STAGE + THE RIG
   ==========================================================================
   .bw-hud holds the rail AND the screen so they tilt together. Tilting them
   separately is the tell that they are two divs; a real floating UI moves as an
   assembly because your head moved, not because the elements did.
   ========================================================================== */
.bw-panel .bw-stage {
  position: relative;
  z-index: 3;
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  margin-top: clamp(28px, 4vh, 46px);
  perspective: 1800px;
  perspective-origin: 50% 45%;
}

.bw-panel .bw-hud {
  position: relative;
  display: flex;
  align-items: stretch;
  gap: clamp(12px, 1.4vw, 22px);
  width: 100%;
  transform-style: preserve-3d;
  transform:
    translate3d(0, 0, var(--bw-dolly))
    rotateX(var(--bw-tilt-x))
    rotateY(var(--bw-tilt-y));
  will-change: transform;
  animation: bw-hover 11s ease-in-out infinite;
}

/* a floating window is never perfectly still. 11s and a 6px throw: any faster
   or further and it reads as a page element animating. */
@keyframes bw-hover {
  0%, 100% { translate: 0 0; }
  50%      { translate: 0 -6px; }
}


/* ==========================================================================
   §5 · THE FLOATING RAIL
   ==========================================================================
   Pushed 40px toward the viewer so it separates from the screen in DEPTH rather
   than by a gap — the clearest single cue that these are layered floating
   surfaces and not one flat mockup.
   ========================================================================== */
.bw-panel .bw-rail {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(14px, 1.6vh, 22px);
  padding: clamp(14px, 1.6vw, 20px) clamp(10px, 1vw, 14px);
  border-radius: 999px;
  transform: translateZ(40px);

  background: linear-gradient(165deg, rgba(240, 238, 235, .13), rgba(240, 238, 235, .05));
  border: 1px solid rgba(240, 238, 235, .18);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
          backdrop-filter: blur(14px) saturate(1.3);
  box-shadow:
    0 26px 60px -24px rgba(0, 0, 0, .9),
    inset 0 1px 0 rgba(240, 238, 235, .16);
}

/* the only filled circle on the panel, so it reads as "the app" */
.bw-panel .bw-mark {
  display: grid;
  place-items: center;
  width: clamp(34px, 2.6vw, 42px);
  aspect-ratio: 1;
  border-radius: 50%;
  background: var(--bw-room);
  border: 1px solid rgba(255, 191, 101, .5);
  font-family: "Antonio", "Archivo", sans-serif;
  font-weight: 700;
  font-size: clamp(13px, 1vw, 16px);
  letter-spacing: -.02em;
  color: var(--bw-amber);
}

.bw-panel .bw-rail-sep {
  width: 18px;
  height: 1px;
  background: rgba(240, 238, 235, .2);
}

.bw-panel .bw-rail-dot {
  width: clamp(26px, 2vw, 32px);
  aspect-ratio: 1;
  display: grid;
  place-items: center;
  border-radius: 50%;
  color: rgba(240, 238, 235, .55);
}
.bw-panel .bw-rail-dot svg { width: 55%; height: 55%; }
.bw-panel .bw-rail-dot--on {
  background: rgba(255, 191, 101, .16);
  color: var(--bw-amber);
}


/* ==========================================================================
   §6 · THE SCREEN
   ==========================================================================
   THE HAIRLINE IS THE WHOLE ILLUSION. A frosted rectangle with no edge reads as
   a dark box; the thin bright border is what makes it read as GLASS with a lit
   rim, brighter at the top because the room's light comes from above.

   position:relative because the pop-up (§9) is absolutely positioned INSIDE
   it — the detail view belongs to the screen, not to the page.
   ========================================================================== */
.bw-panel .bw-screen {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(10px, 1.2vh, 16px);
  padding: clamp(14px, 1.5vw, 22px);
  border-radius: clamp(18px, 1.8vw, 28px);

  /* the last stop is .26 here against the source's .12 — divergence 3 again.
     The glass is the largest object on the section, so it is where the navy
     gets to be a colour rather than a suggestion. */
  background: linear-gradient(158deg, rgba(240, 238, 235, .17), rgba(240, 238, 235, .06) 55%, rgba(0, 58, 108, .26));
  border: 1px solid rgba(240, 238, 235, .26);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
          backdrop-filter: blur(20px) saturate(1.4);
  box-shadow:
    0 50px 100px -40px rgba(0, 0, 0, .95),
    0 0 90px -30px rgba(255, 191, 101, .18),
    inset 0 1px 0 rgba(240, 238, 235, .22);
}

/* §7 · screen chrome — top bar + filter chips */
.bw-panel .bw-screen-top {
  display: flex;
  align-items: center;
  gap: 10px;
  padding-bottom: clamp(8px, 1vh, 12px);
  border-bottom: 1px solid rgba(240, 238, 235, .12);
}

.bw-panel .bw-screen-label {
  font-family: "Antonio", "Archivo", sans-serif;
  font-weight: 500;
  font-size: clamp(11px, .82vw, 14px);
  letter-spacing: .16em;
  text-transform: uppercase;
  color: rgba(240, 238, 235, .78);
}

.bw-panel .bw-screen-count {
  margin-left: auto;
  font-family: "Neue Montreal", Archivo, sans-serif;
  font-size: clamp(10px, .74vw, 12px);
  letter-spacing: .06em;
  color: rgba(240, 238, 235, .5);
}

/* ⚠ THE CHIP ROW SCROLLS, IT DOES NOT WRAP. Ten disciplines wrapped to three
   lines pushes the grid down by ~80px and the screen stops looking like a UI
   and starts looking like a form. A shelf scrolls. */
.bw-panel .bw-chips {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  scrollbar-width: none;
  -ms-overflow-style: none;
  padding-bottom: 2px;
}
.bw-panel .bw-chips::-webkit-scrollbar { display: none; }

/* ⚠ THE EDGE FADE IS STATEFUL, NOT DECORATIVE — Bubble Card's horizontal
   buttons stack, src/cards/horizontal-buttons-stack/styles.css:60-91 (MIT,
   © Clooos). Three classes the script keeps in sync on scroll + resize:

     (none)            the row FITS — no fade at all
     .is-scrollable    more to the right   → fade the right edge only
     .is-scrolled      scrolled off zero   → fade the left edge too
     .is-maxed-scroll  at the end          → drop the right fade

   A fade on an edge you cannot scroll toward is a lie about the content. Only
   the reachable edge ever fades. */
.bw-panel .bw-chips.is-scrollable {
  scroll-behavior: smooth;
  -webkit-mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 28px), transparent 100%);
          mask-image: linear-gradient(90deg, #000 0%, #000 calc(100% - 28px), transparent 100%);
}
.bw-panel .bw-chips.is-scrollable.is-scrolled {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 28px, #000 calc(100% - 28px), transparent 100%);
          mask-image: linear-gradient(90deg, transparent 0%, #000 28px, #000 calc(100% - 28px), transparent 100%);
}
.bw-panel .bw-chips.is-scrollable.is-maxed-scroll {
  -webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 28px, #000 100%);
          mask-image: linear-gradient(90deg, transparent 0%, #000 28px, #000 100%);
}

.bw-panel .bw-chip {
  flex: 0 0 auto;
  padding: 6px 13px;
  border-radius: 999px;
  border: 1px solid rgba(240, 238, 235, .18);
  background: transparent;
  font-family: "Neue Montreal", Archivo, sans-serif;
  font-size: clamp(11px, .8vw, 13px);
  line-height: 1.2;
  white-space: nowrap;
  color: rgba(240, 238, 235, .72);
  cursor: pointer;
  transition: color .22s ease, border-color .22s ease, background .22s ease;
}
.bw-panel .bw-chip:hover { border-color: rgba(240, 238, 235, .4); color: var(--bw-ink); }
.bw-panel .bw-chip--on {
  background: rgba(255, 191, 101, .16);
  border-color: rgba(255, 191, 101, .5);
  color: var(--bw-amber);
}


/* ==========================================================================
   §8 · THE GRID
   ==========================================================================
   ⚠ IT DOES NOT SCROLL INTERNALLY, AND THAT IS A DELIBERATE TRADE. An inner
   scroll area steals the wheel the moment the pointer crosses it — you try to
   leave the section and the grid eats the gesture. Letting the panel grow costs
   some page length and removes the trap entirely.

   min-height holds the box steady so filtering from 17 items to 1 does not
   collapse the screen and yank the page under the pointer.

   ⚠ THE TILE IS 16/10 AND THE SOURCE CLIPS ARE 9:16. That is not an oversight:
   `object-fit: cover` crops them, exactly as work-mph.css §4 argued for the flat
   grid (MPH crop phone footage into a shorter box rather than let a page inherit
   the phone's aspect). A 9:16 tile here would make 17 of them ~5,000px tall
   INSIDE a floating screen, which stops being a screen. */
.bw-panel .bw-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(clamp(124px, 11vw, 168px), 1fr));
  gap: clamp(6px, .7vw, 10px);
  min-height: clamp(180px, 26vh, 320px);
  align-content: start;
}

.bw-panel .bw-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  aspect-ratio: 16 / 10;
  padding: 9px 11px;
  border-radius: clamp(8px, .8vw, 12px);
  overflow: hidden;
  border: 1px solid rgba(240, 238, 235, .12);
  background: rgba(19, 24, 27, .5);
  text-align: left;
  cursor: pointer;
  transition: transform .35s cubic-bezier(.22, 1, .36, 1), border-color .3s ease;
}

/* 🔴 BUG FOUND 2026-08-09, porting this panel to founder.html: `display:flex`
   above has no `:not([hidden])` guard, and normal author CSS always outranks
   the UA stylesheet's `[hidden]{display:none}` regardless of specificity — so
   work-screen.js's filter (`t.hidden = !on`, §1's own comment: "the attribute
   is what removes a filtered-out tile from the accessibility tree") updated
   the count and the active chip correctly but never actually hid a tile. Every
   filter on work.html's live archive has been showing all 16 pieces since
   this file shipped. One rule, fixes both pages at once since they now share
   this file. */
.bw-panel .bw-tile[hidden] { display: none; }

.bw-panel .bw-tile img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}

/* the scrim — real frames vary wildly in exposure and the type has to survive
   all seventeen of them, so every tile carries the same bottom-weighted wash
   rather than being hand-tuned per card */
.bw-panel .bw-tile::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgba(19, 24, 27, .15) 0%, rgba(19, 24, 27, .7) 58%, rgba(19, 24, 27, .94) 100%);
}

.bw-panel .bw-tile > * { position: relative; z-index: 2; }

/* CLIENT MARKS ON TILES — ADDED 2026-08-10. Audit found 17 (work.html) + 5
   (founder.html, same component) tiles showing a client name as plain text
   even where a real sourced logo exists at img/sourced-logos/. Same fix the
   homepage's Instagram-phone component (`.hbp-brand-logo`) and the About
   page's `.bto-logo-plate` already ship — small mark, real client. Sits on
   its OWN row above `.bw-tile-name` rather than inline before it: the grid's
   narrowest tile is 124px (§ .bw-grid above) and inline logo+text collides
   at that width, especially the two-logo ELVT × E1 London tile. No plate —
   unlike About's logo-plate, the scrim here is already near-black
   (rgba(19,24,27,.94) at the bottom, see .bw-tile::after above), so every
   mark used is verified white/light-ink art straight off disk (checked
   pixel-by-pixel against that scrim tone, not assumed from the folder name):
   elvt.png, e1-london.png, gioli-and-assia.png and artcore.png are white-ink
   in `img/sourced-logos/` already (their `inverted/` siblings are the
   dark-ink versions the About page's light plate needs — wrong direction for
   here). surfexcel.png and the-burger-company.png carry their own full-colour
   art and read fine directly. sanatan-shastar-vidiya.png reads fine directly
   too (orange/white ink) — its `inverted/` sibling is near-black-on-transparent
   and disappears on this scrim, so base wins even though About uses the
   inverted one on its light plate. Tiles with no sourced logo (Senses-only
   entries, HEAT) are untouched — plain text stands. */
.bw-panel .bw-tile-brand {
  display: flex;
  align-items: center;
  gap: clamp(4px, .5vw, 7px);
  margin-bottom: 2px;
}

/* `.bw-panel .bw-tile img` (above) absolutely-positions and cover-fits EVERY
   img in the tile for the poster shot — (0,2,1) specificity beats a bare
   `.bw-tile-logo` class rule (0,2,0), so the poster rule was winning and
   collapsing the mark to a 0-height absolutely-positioned ghost. Qualifying
   with `.bw-tile img.bw-tile-logo` (0,3,1) wins the cascade properly instead
   of fighting it with !important. */
.bw-panel .bw-tile img.bw-tile-logo {
  position: static;
  inset: auto;
  z-index: auto;
  display: block;
  height: clamp(11px, 1vw, 16px);
  width: auto;
  max-width: 56px;
  object-fit: contain;
  flex: 0 0 auto;
  opacity: .96;
}

/* two-mark tiles (ELVT × E1 London): narrower cap so both fit the 124px floor */
.bw-panel .bw-tile .bw-tile-brand--dual img.bw-tile-logo { max-width: 38px; }

.bw-panel .bw-tile-name {
  font-family: "Antonio", "Archivo", sans-serif;
  font-weight: 500;
  font-size: clamp(11px, .82vw, 14px);
  line-height: 1.08;
  text-transform: uppercase;
  color: var(--bw-ink);
}

.bw-panel .bw-tile-meta {
  margin-top: 2px;
  font-family: "Neue Montreal", Archivo, sans-serif;
  font-size: clamp(9px, .68vw, 11px);
  color: rgba(240, 238, 235, .64);
}

/* THE PLAY BADGE — the affordance that says "this opens something" rather than
   "this is a link". It is the one amber object inside the grid and it only
   appears on hover/focus, so seventeen tiles at rest carry no accent at all. */
.bw-panel .bw-play {
  position: absolute;
  top: 8px;
  right: 8px;
  z-index: 2;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: rgba(19, 24, 27, .7);
  border: 1px solid rgba(240, 238, 235, .3);
  color: var(--bw-ink);
  opacity: 0;
  transform: scale(.85);
  transition: opacity .25s ease, transform .25s ease, background .25s ease, border-color .25s ease;
}
.bw-panel .bw-play svg { width: 13px; height: 13px; }

.bw-panel .bw-tile:hover .bw-play,
.bw-panel .bw-tile:focus-visible .bw-play {
  opacity: 1;
  transform: scale(1);
  background: var(--bw-amber);
  border-color: var(--bw-amber);
  color: var(--bw-room);
}

.bw-panel .bw-tile:hover {
  transform: translateY(-3px);
  border-color: rgba(255, 191, 101, .55);
}

.bw-panel .bw-tile:focus-visible,
.bw-panel .bw-chip:focus-visible {
  outline: 2px solid var(--bw-amber);
  outline-offset: 3px;
}

/* the empty state. It cannot fire from a chip (the chips are derived from the
   tags actually present), but a stale #w/ deep link or a hand-typed filter can
   reach it, and a screen with nothing in it and no message reads as broken. */
.bw-panel .bw-empty {
  grid-column: 1 / -1;
  align-self: center;
  padding: 32px 0;
  text-align: center;
  font-family: "Neue Montreal", Archivo, sans-serif;
  font-size: 14px;
  color: rgba(240, 238, 235, .6);
}


/* ==========================================================================
   §9 · THE DETAIL POP-UP — Bubble Card's transition model
   ==========================================================================
   Mechanics transcribed from github.com/Clooos/Bubble-Card (MIT, © Clooos):
     src/cards/pop-up/styles.css  and  src/cards/pop-up/helpers.js
   Their look is NOT taken — only the state machine, the curves and the layer
   strategy. The JS side is documented at the head of bto-js/work-screen.js.

   ⚠ IT IS ABSOLUTE TO .bw-screen, NOT FIXED TO THE VIEWPORT — the one place we
   deliberately diverge from Bubble Card, whose pop-up is `position: fixed`. A
   fixed overlay would break out of the rig's 3D transform and hang flat in front
   of a tilted screen, which instantly reads as a different UI layer. Contained,
   it inherits the tilt and stays part of the same object.

   🔴 THE NODE IS ALWAYS IN THE DOM. Every rule below is driven by the phase
   class the script writes, never by mounting or by display.

     .bw-pop--closed    resting, inert, not painted
     .bw-pop--opening   closed geometry still applied, one frame
     .bw-pop--open      settled
     .bw-pop--closing   exit geometry, removed after CLOSE_MS
     .bw-pop--fast      140ms micro-open, when reopening inside the cooldown

   ⚠ KEEP THE JS CONSTANTS AND THESE DURATIONS IN STEP. work-screen.js holds
   OPEN_MS 360 / CLOSE_MS 210 / FAST_MS 140 / WILLCHANGE_MS 450. The JS numbers
   are the CSS ones plus a frame — the class must not be pulled before the
   transition it is driving has finished, or the dialog vanishes mid-exit.
   ========================================================================== */
.bw-panel .bw-pop {
  position: absolute;
  inset: 0;
  z-index: 6;
  display: grid;
  place-items: center;
  padding: clamp(12px, 1.4vw, 22px);
  border-radius: inherit;

  /* §9 timing, in one place. Bubble Card styles.css:192 / :216 */
  --bw-pop-in: .35s cubic-bezier(.16, 1, .3, 1);
  --bw-pop-out: .2s ease-in;
}

/* ⚠ CLOSED IS NOT display:none. A display-toggled element has no transition to
   run when it comes back. It is made non-interactive and non-painting instead,
   which is what Bubble Card's .is-popup-closed does (styles.css:162-166). */
.bw-panel .bw-pop--closed {
  pointer-events: none;
  contain: layout paint;
}

/* ==========================================================================
   §9.1 · THE SCRIM, AND WHY THE BLUR IS ITS OWN LAYER
   ==========================================================================
   D · Bubble Card styles.css:124-153. The backdrop-filter lives on a ::before
   whose OPACITY is transitioned; the filter value itself never animates.

   🔴 THIS IS THE SINGLE BIGGEST "SMOOTHNESS" WIN, AND IT LOOKS LIKE A POINTLESS
   EXTRA ELEMENT UNTIL YOU MEASURE IT. Transitioning backdrop-filter re-runs the
   blur at a new radius EVERY FRAME, over a region that already sits behind two
   other backdrop-filters (.bw-screen §6 and the card below). Transitioning the
   opacity of a layer rasterised ONCE at its final radius is composited and costs
   nothing per frame. Do not "simplify" this back onto .bw-pop-scrim.
   ========================================================================== */
.bw-panel .bw-pop-scrim {
  position: absolute;
  inset: 0;
  border: 0;
  border-radius: inherit;
  background: rgba(10, 13, 15, .74);
  cursor: pointer;
  opacity: 0;
  transition: opacity .25s ease;
}

.bw-panel .bw-pop-scrim::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  opacity: 0;
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  transition: opacity .4s ease;   /* their :133 */
}

.bw-panel .bw-pop--open .bw-pop-scrim,
.bw-panel .bw-pop--closing .bw-pop-scrim { opacity: 1; }
.bw-panel .bw-pop--open .bw-pop-scrim::before { opacity: 1; }

/* the blur drops FIRST on the way out. Holding it to the end of the exit means
   the last thing you see is a blurred grid snapping back to sharp with nothing
   in front of it, which reads as a flicker. */
.bw-panel .bw-pop--closing .bw-pop-scrim { transition: opacity var(--bw-pop-out); }
.bw-panel .bw-pop--closing .bw-pop-scrim::before { transition: opacity .16s ease; }
.bw-panel .bw-pop--closed .bw-pop-scrim { transition: none; }

/* ==========================================================================
   §9.2 · THE CARD — C, asymmetric in/out
   ==========================================================================
   in   scale(.85) opacity 0  →  scale(1) opacity 1   .35s cubic-bezier(.16,1,.3,1)
   out  scale(1)              →  scale(.9) opacity 0  .2s ease-in

   Bubble Card styles.css:199-217. The asymmetry IS the feel: an entrance that
   overshoots gently and an exit that gets out of the way. Symmetric easing is
   what makes a dialog feel sticky on the way out.

   ⚠ scale, NOT width/height, and NOT translateY alone. Scale and opacity are
   the only two properties that never trigger layout, which is what lets this run
   at frame rate on top of three stacked backdrop-filters.
   ========================================================================== */
.bw-panel .bw-pop-card {
  position: relative;
  z-index: 1;
  display: flex;
  gap: clamp(12px, 1.4vw, 20px);
  align-items: stretch;
  max-width: 100%;
  max-height: 100%;
  padding: clamp(10px, 1vw, 14px);
  border-radius: clamp(12px, 1.2vw, 18px);
  background: linear-gradient(158deg, rgba(240, 238, 235, .14), rgba(240, 238, 235, .06));
  border: 1px solid rgba(240, 238, 235, .28);
  -webkit-backdrop-filter: blur(18px) saturate(1.3);
          backdrop-filter: blur(18px) saturate(1.3);
  box-shadow: 0 40px 90px -30px rgba(0, 0, 0, .95);

  opacity: 0;
  transform: scale(.85);
  /* their :120-121 — a promoted layer that flips backface stops the card
     shimmering as it scales over the frosted screen behind it */
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  transition: transform var(--bw-pop-in), opacity .25s ease;
}

.bw-panel .bw-pop--open .bw-pop-card,
.bw-panel .bw-pop--opening .bw-pop-card { opacity: 1; }
.bw-panel .bw-pop--open .bw-pop-card { transform: scale(1); }

.bw-panel .bw-pop--closing .bw-pop-card {
  opacity: 0;
  transform: scale(.9);
  transition: transform var(--bw-pop-out), opacity var(--bw-pop-out);
}

.bw-panel .bw-pop--closed .bw-pop-card { transition: none; }

/* E · is-fast-opening — Bubble Card styles.css:230-248, helpers.js:43.
   Close one piece, tap the next, and the full 350ms entrance replays for
   something you are already looking at. Their answer is a 140ms micro-open
   inside a cooldown window, and it is the difference between browsing an
   archive and operating one. */
.bw-panel .bw-pop--fast .bw-pop-card {
  animation: bw-pop-fast .14s cubic-bezier(.22, 1, .36, 1);
  transition: transform .14s cubic-bezier(.22, 1, .36, 1), opacity .12s ease;
}

@keyframes bw-pop-fast {
  from { transform: translateY(8px) scale(.985); opacity: .88; }
  to   { transform: translateY(0) scale(1);      opacity: 1; }
}

/* F · will-change is worn only while the transition is in flight. The script
   sets this attribute on phase change and drops it 450ms later (their
   popupBlurWillChangeDurationMs, helpers.js:44). */
.bw-panel .bw-pop[data-bw-will-change="true"] .bw-pop-card,
.bw-panel .bw-pop[data-bw-will-change="true"] .bw-pop-scrim::before {
  will-change: transform, opacity;
}

/* ==========================================================================
   §9.3 · B · ADAPTIVE GEOMETRY — dialog on a window, SHEET on a phone
   ==========================================================================
   Bubble Card styles.css:263-268 + :380-419 (popup-mode-adaptive-dialog): a
   centred dialog when there is room, a bottom sheet when there is not. The
   breakpoint is theirs — 870px wide OR 500px tall, because a short landscape
   window is as bad a place for a centred dialog as a narrow one.
   ========================================================================== */
@media (max-width: 870px), (max-height: 500px) {
  .bw-panel .bw-pop { place-items: end center; padding: 0; }

  .bw-panel .bw-pop-card {
    width: 100%;
    border-radius: clamp(12px, 1.2vw, 18px) clamp(12px, 1.2vw, 18px) 0 0;
    transform: translate3d(0, 100%, 0);
    transition: transform .3s ease, opacity .2s ease;   /* their :110 */
  }

  .bw-panel .bw-pop--open .bw-pop-card { transform: translate3d(0, 0, 0); }
  .bw-panel .bw-pop--closing .bw-pop-card {
    transform: translate3d(0, 100%, 0);
    opacity: 1;               /* a sheet slides out, it does not also fade */
    transition: transform .28s ease;
  }

  /* their bubble-popup-fast-open-bottom-sheet, :219-228 */
  .bw-panel .bw-pop--fast .bw-pop-card {
    animation: bw-pop-fast-sheet .14s cubic-bezier(.22, 1, .36, 1);
  }
  @keyframes bw-pop-fast-sheet {
    from { transform: translateY(14px); opacity: .84; }
    to   { transform: translateY(0);    opacity: 1; }
  }

  /* the grab handle that says "this came from the bottom and goes back there"
     — their popup-style-classic ::before, styles.css:331-344 */
  .bw-panel .bw-pop-card::before {
    content: "";
    position: absolute;
    top: 6px;
    left: 50%;
    transform: translateX(-50%);
    width: 40px;
    height: 4px;
    border-radius: 2px;
    background: rgba(240, 238, 235, .3);
    pointer-events: none;
    z-index: 4;
  }
}

/* ⚠ THE VIDEO BOX IS SIZED BY THE CARD'S OWN FORMAT. A 9:16 reel forced into a
   16:9 box pillarboxes to about a third of the width and looks broken; the
   modifier comes from the tile's data-format attribute, not from a guess. */
.bw-panel .bw-pop-vid {
  display: block;
  border-radius: clamp(8px, .8vw, 12px);
  background: #0A0D0F;
  object-fit: cover;
}
.bw-panel .bw-pop-card--wide .bw-pop-vid {
  width: min(52vw, 560px);
  aspect-ratio: 16 / 9;
}
.bw-panel .bw-pop-card--tall .bw-pop-vid {
  height: min(46vh, 340px);
  aspect-ratio: 9 / 16;
}

.bw-panel .bw-pop-body {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 8px;
  min-width: clamp(150px, 16vw, 230px);
  max-width: 280px;
  padding-right: 26px;            /* clears the close button */
}

.bw-panel .bw-pop-name {
  font-family: "Antonio", "Archivo", sans-serif;
  font-weight: 700;
  font-size: clamp(17px, 1.5vw, 26px);
  line-height: 1.02;
  text-transform: uppercase;
  color: var(--bw-ink);
}

.bw-panel .bw-pop-tag {
  font-family: "Neue Montreal", Archivo, sans-serif;
  font-size: clamp(11px, .82vw, 13px);
  line-height: 1.5;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: rgba(240, 238, 235, .78);
}

.bw-panel .bw-pop-cta {
  align-self: flex-start;
  margin-top: 4px;
  padding: 8px 16px;
  border-radius: 999px;
  background: var(--bw-amber);
  color: var(--bw-room);
  font-family: "Antonio", "Archivo", sans-serif;
  font-weight: 500;
  font-size: clamp(11px, .82vw, 13px);
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
}
.bw-panel .bw-pop-cta:hover { background: #DEA85B; color: var(--bw-room); }

.bw-panel .bw-pop-close {
  position: absolute;
  top: 8px;
  right: 8px;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: rgba(19, 24, 27, .7);
  border: 1px solid rgba(240, 238, 235, .28);
  color: var(--bw-ink);
  cursor: pointer;
}
.bw-panel .bw-pop-close svg { width: 13px; height: 13px; }
.bw-panel .bw-pop-close:hover { background: var(--bw-amber); color: var(--bw-room); border-color: var(--bw-amber); }
.bw-panel .bw-pop-close:focus-visible,
.bw-panel .bw-pop-cta:focus-visible {
  outline: 2px solid var(--bw-amber);
  outline-offset: 3px;
}


/* ==========================================================================
   §10 · NARROW — the rig unstacks below 900px
   ==========================================================================
   The rail goes horizontal above the screen (a vertical rail beside a 390px
   screen costs 20% of the width to five icons), the perspective comes off —
   there is no pointer to drive the tilt, and backdrop-filter over a full-bleed
   blurred image is the most expensive thing on the page for a mid-range phone.

   THE ROOM STAYS. It is one <img> and it carries the section's whole identity.

   ⚠ THE POP-UP GOES VERTICAL. Video beside copy inside a 390px screen leaves
   each about 170px; stacked, the video gets the full width.
   ========================================================================== */
@media (max-width: 991px) {
  /* = work-mph.css §6's step, so the section keeps the page's left edge */
  .bw-panel .bw-inner { padding-inline: 40px; }
}

@media (max-width: 900px) {
  .bw-panel .bw-stage {
    perspective: none;
    align-items: stretch;
  }

  .bw-panel .bw-hud {
    flex-direction: column;
    width: 100%;
    transform: none;
    animation: none;
  }

  .bw-panel .bw-rail {
    flex-direction: row;
    justify-content: flex-start;
    border-radius: 999px;
    padding: 8px 12px;
    transform: none;
  }

  .bw-panel .bw-rail-sep { width: 1px; height: 18px; }

  .bw-panel .bw-grid {
    grid-template-columns: repeat(auto-fill, minmax(112px, 1fr));
    min-height: 0;
  }

  .bw-panel .bw-pop-card {
    flex-direction: column;
    gap: 10px;
    width: 100%;
  }
  .bw-panel .bw-pop-card--wide .bw-pop-vid,
  .bw-panel .bw-pop-card--tall .bw-pop-vid {
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
  }
  .bw-panel .bw-pop-body { max-width: none; padding-right: 30px; }
}

@media (max-width: 575px) {
  .bw-panel .bw-inner { padding-inline: 24px; }
  .bw-panel .bw-title { font-size: 30px; }
  .bw-panel .bw-lead { font-size: 16px; line-height: 28px; }
}


/* ==========================================================================
   §11 · REDUCED MOTION
   ==========================================================================
   The float, the dolly, the tilt and the pop-up's entrance stop. The room, the
   glass, the depth offset and every hover affordance stay — a user who asked for
   no motion did not ask for no design.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  .bw-panel {
    --bw-dolly: 0px;
    --bw-tilt-x: 0deg;
    --bw-tilt-y: 0deg;
  }

  .bw-panel .bw-hud { animation: none; }
  .bw-panel .bw-room img { transform: scale(1.18); }

  .bw-panel .bw-tile { transition: border-color .2s ease; }
  .bw-panel .bw-tile:hover { transform: none; }

  .bw-panel .bw-play { transition: opacity .2s ease; transform: none; }
  .bw-panel .bw-tile:hover .bw-play,
  .bw-panel .bw-tile:focus-visible .bw-play { transform: none; }

  /* ⚠ THE POP-UP STILL CROSS-FADES. Killing the whole animation would mean the
     card jumps in AND the phase classes still run their timers — the exit would
     be invisible and the 210ms of `closing` would read as an unresponsive
     button. Opacity is not vestibular; scale and translate are. */
  .bw-panel .bw-pop-card {
    animation: none !important;
    transform: none !important;
    transition: opacity .18s linear !important;
  }
  .bw-panel .bw-pop-scrim,
  .bw-panel .bw-pop-scrim::before { transition: opacity .18s linear !important; }
}
