/* Slide theme tokens — used by both admin preview and player.
   Each theme defines: --bb-st-bg (background), --bb-st-fg (text), --bb-st-accent.

   Every .bb-slide is a size container (named "slide") so cqmin-based typography
   inside every widget scales with the WIDGET box — not the viewport. A small
   widget on a 4K display gets sensibly sized fonts; a full-slide widget on a
   small display still looks bold. */

/* Universal border-box inside any slide. The admin app sets this globally via
   studio.css, but the player (display.html) only loads slide-themes.css — so
   without this reset, .bb-slide's padding adds OUTSIDE its 100% box, the
   widget container clips the overflow, and the slide's right/bottom padding
   disappears in the published / fullscreen player. */
.bb-slide, .bb-slide *, .bb-slide *::before, .bb-slide *::after { box-sizing: border-box; }

.bb-slide {
  width: 100%; height: 100%;
  padding: clamp(12px, 3%, 64px);
  display: flex; flex-direction: column; justify-content: center;
  position: relative; overflow: hidden;
  container-type: size; container-name: slide;
  /* Background is owned by the background tool (slide/widget bg layers sit
     behind). A plugin theme only supplies text/accent color + font. */
  background: transparent;
  color: var(--bb-st-fg, #f1f1f4);
  font-family: var(--bb-st-font, Inter, sans-serif);
}
.bb-h1 { font: 800 clamp(28px, 8cqmin, 96px)/1.15 var(--bb-display); margin: 0 0 .4em; letter-spacing: -.01em; }
.bb-h2 { font: 600 clamp(20px, 5cqmin, 52px)/1.3 var(--bb-display); margin: 0 0 .4em; color: var(--bb-st-accent, #8b5cf6); }
.bb-body { font-size: clamp(18px, 4.4cqmin, 64px); line-height: 1.5; }
/* Text widget — full-width body so WYSIWYG text-align inside has something
   to align against, centred by default until inline alignment overrides. */
.bb-slide-text .bb-body { width: 100%; text-align: center; }

/* Rich-text rendering inside the Announcement (.bb-slide-text .bb-body).
   The WYSIWYG can produce headings, blockquotes, lists, tables, links, code,
   sub/sup, and horizontal rules — style them so they match the surrounding
   theme without needing per-theme overrides. Colours come from currentColor
   and the theme's --bb-st-accent, so every theme inherits sensible defaults. */
.bb-slide-text .bb-body h2 {
  font: 700 clamp(20px, 4.6cqmin, 56px)/1.25 var(--bb-st-font, inherit);
  margin: .4em 0 .3em;
}
.bb-slide-text .bb-body h3 {
  font: 600 clamp(18px, 4cqmin, 44px)/1.25 var(--bb-st-font, inherit);
  margin: .35em 0 .25em;
}
.bb-slide-text .bb-body blockquote {
  margin: .4em auto; padding: .25em .9em;
  max-width: 90%;
  border-left: .2em solid var(--bb-st-accent, currentColor);
  text-align: left;
  font-style: italic;
  opacity: .92;
}
.bb-slide-text .bb-body pre {
  display: inline-block;
  background: rgba(255,255,255,.08);
  border-radius: .35em;
  padding: .5em .8em;
  text-align: left;
  font: .85em/1.4 "JetBrains Mono", ui-monospace, monospace;
  white-space: pre-wrap;
  margin: .4em 0;
}
.bb-slide-text .bb-body code {
  background: rgba(255,255,255,.08);
  padding: .05em .35em;
  border-radius: .25em;
  font: .9em "JetBrains Mono", ui-monospace, monospace;
}
.bb-slide-text .bb-body pre code { background: transparent; padding: 0; font-size: inherit; }
.bb-slide-text .bb-body hr {
  border: 0;
  border-top: 2px solid currentColor;
  opacity: .35;
  margin: .8em auto;
  width: 60%;
}
.bb-slide-text .bb-body a {
  color: var(--bb-st-accent, currentColor);
  text-decoration: underline;
  text-underline-offset: .15em;
}
.bb-slide-text .bb-body ul,
.bb-slide-text .bb-body ol {
  display: inline-block;
  text-align: left;
  margin: .3em 0;
  padding-left: 1.4em;
}
.bb-slide-text .bb-body table {
  border-collapse: collapse;
  margin: .5em auto;
  font-size: .92em;
}
.bb-slide-text .bb-body th,
.bb-slide-text .bb-body td {
  border: 1px solid currentColor;
  padding: .3em .7em;
  vertical-align: top;
}
.bb-slide-text .bb-body th {
  background: rgba(255,255,255,.1);
  font-weight: 700;
  text-align: left;
}
.bb-slide-text .bb-body sub,
.bb-slide-text .bb-body sup { font-size: .7em; line-height: 0; }
.bb-slide-text .bb-body img {
  max-width: 100%; height: auto;
  border-radius: .25em;
  vertical-align: middle;
}

/* Themes — each is a coherent (bg, fg, accent) TRIPLE.

   Every theme pins --bb-st-fg, not just bg + accent. This is deliberate: a widget
   paints its OWN theme background, but text colour was previously inherited from
   the slide-level auto-contrast (applySlideContrast in background.js), which keys
   off the SLIDE background. A dark-themed widget on a light slide then inherited
   DARK text → unreadable on its own dark background (the countdown bug). Pinning
   fg per theme keeps every widget readable on its own bg out of the box, and is
   consistent with how the theme already owns --bb-st-bg / --bb-st-accent at the
   widget level. fg values are high-contrast near-whites, subtly tinted toward each
   theme's hue (and near-black for the light editorial-mono).

   Cascade: a per-widget override (shared/widget-color.js) sets fg/accent INLINE on
   the widget root and still wins; applySlideContrast governs un-themed slide chrome
   + custom slide backgrounds; Brand-Kit fg — like brand-kit bg/accent — is
   overridden by an explicit widget theme (consistent across all three vars). */
.bb-theme-gradient-purple { --bb-st-bg: radial-gradient(circle at 30% 20%, #4c1d95 0%, #1e0c4d 60%, #0a0220 100%); --bb-st-fg: #f4eefe; --bb-st-accent: #c4b5fd; }
.bb-theme-gradient-blue   { --bb-st-bg: radial-gradient(circle at 20% 30%, #1e3a8a 0%, #0c1b48 60%, #02061c 100%); --bb-st-fg: #eaf1ff; --bb-st-accent: #93c5fd; }
.bb-theme-gradient-orange { --bb-st-bg: radial-gradient(circle at 25% 25%, #7c2d12 0%, #261004 60%, #0d0500 100%); --bb-st-fg: #fff2e6; --bb-st-accent: #fdba74; }
.bb-theme-dark-minimal    { --bb-st-bg: #09090b; --bb-st-fg: #f1edf7; --bb-st-accent: #8b5cf6; }
.bb-theme-minimal-dark    { --bb-st-bg: #0a0a10; --bb-st-fg: #eaf1f5; --bb-st-accent: #06b6d4; }

.bb-theme-bistro-warm     {
  --bb-st-bg: linear-gradient(135deg, #2d1810, #4a1f0e 50%, #1c0d08 100%);
  --bb-st-fg: #f8ead9; --bb-st-accent: #f59e0b;
  --bb-st-font: "Playfair Display", Georgia, serif;
}
.bb-theme-corporate-blue  {
  --bb-st-bg: linear-gradient(135deg, #0c1b48, #142b6e 50%, #0a173f 100%);
  --bb-st-fg: #edf3fb; --bb-st-accent: #38bdf8;
}
.bb-theme-medical-calm    {
  --bb-st-bg: linear-gradient(135deg, #0f2a2a, #14373a 50%, #07191c 100%);
  --bb-st-fg: #e7faf3; --bb-st-accent: #5eead4;
}
.bb-theme-industrial-steel{
  --bb-st-bg: linear-gradient(135deg, #1b1b22, #2c2c36 50%, #11111a 100%);
  --bb-st-fg: #eef1f6; --bb-st-accent: #fbbf24;
  --bb-st-font: "JetBrains Mono", monospace;
}
.bb-theme-neon-cyber      {
  --bb-st-bg: radial-gradient(circle at 30% 20%, #2a005f 0%, #1a0040 35%, #02041a 100%);
  --bb-st-fg: #f9e9ff; --bb-st-accent: #f0abfc;
  --bb-st-font: "Inter Tight", sans-serif;
}
.bb-theme-editorial-mono  {
  --bb-st-bg: #f4f3ef;
  --bb-st-fg: #1c1c1c;
  --bb-st-accent: #b91c1c;
  --bb-st-font: "Playfair Display", Georgia, serif;
}

/* ═══════════════════════════════════════════════════════════════════════════
   Weather widget — premium redesign
   ───────────────────────────────────────────────────────────────────────────
   Markup is identical across designs (the JS doesn't branch); each design
   below is a CSS personality. Five designs cover the common needs:

     classic   — Newsroom: icon + meta side-by-side, stats row with dividers,
                 7-day forecast strip. Polished default.
     minimal   — Ambient: temp + city only. Massive type. Generous void.
     hero      — Stage: cinematic. Icon top, huge temp, city under, sub-line.
     forecast  — Outlook: slim current header, premium forecast cards below.
     split     — Dashboard: current snapshot left, forecast list right.

   Design tokens (rhythm scale + radii + dividers) live on .bb-slide-weather
   so individual designs can override without touching base values. All sizes
   scale with the widget via cqmin so the same widget design works at every
   screen size and aspect ratio.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Generic SVG icon sizing — when the SVG sits inside a font-sized box (emoji
   icon slot in classic/hero/etc.), `1em / 1em` makes it scale with whatever
   font-size the parent uses for emojis. Dashboard's icon slots set explicit
   width/height with higher specificity, so this rule only kicks in for non-
   dashboard designs that opted into the SVG icon set. */
.bb-slide-weather .bb-weather-icon-svg {
  width: 1em;
  height: 1em;
  display: inline-block;
  vertical-align: middle;
}

.bb-slide-weather {
  --w-gap-xs:   clamp(2px, .6cqmin, 6px);
  --w-gap-sm:   clamp(6px, 1.4cqmin, 12px);
  --w-gap-md:   clamp(10px, 2.6cqmin, 24px);
  --w-gap-lg:   clamp(16px, 4cqmin, 40px);
  --w-radius-sm: clamp(6px, 1.2cqmin, 10px);
  --w-radius-md: clamp(10px, 1.8cqmin, 16px);
  --w-divider:  color-mix(in srgb, currentColor 16%, transparent);
  --w-surface:  color-mix(in srgb, currentColor 7%, transparent);
  --w-surface-strong: color-mix(in srgb, currentColor 14%, transparent);
  --w-shadow-soft: 0 4px 24px color-mix(in srgb, #000 35%, transparent);
}

/* Per-element opt-out — users can hide City / Temperature / Icon / Condition
   to compose stacked weather widgets (one shows only the temp, another only
   the forecast, etc.). Applies to every design.

   Selector compounds `.bb-slide-weather` with the opt-out class on the same
   element so specificity is (0,3,0) — beats per-design rules like
   `.bb-weather-design-dashboard .bb-weather-icon { display: inline-flex }`
   that would otherwise override our `display: none`. */
.bb-slide-weather.bb-weather-no-city .bb-weather-city { display: none; }
.bb-slide-weather.bb-weather-no-temp .bb-weather-temp { display: none; }
.bb-slide-weather.bb-weather-no-icon .bb-weather-icon { display: none; }
/* Condition description ("Clear sky", "Mainly clear" …) — English WMO label,
   hidden by default for international widgets. User opts in via the toggle. */
.bb-slide-weather.bb-weather-no-desc .bb-weather-desc { display: none; }

/* Base typography & shared elements — overridden per design as needed.
   Note: clamp() MIN values are calibrated for tablet readability (10" screen
   ≈ 220 PPI), not just TV signage. Pre-bump the body-tier text was 9–11px
   minimum which on a small widget at tablet scale rendered as 5–7 device-px.
   Floors now sit at 13–18px so even a tiny widget on a 10" device stays
   legible without losing the proportional cqmin scaling for big TV slots. */
.bb-slide-weather .bb-weather-current {
  display: flex; gap: var(--w-gap-lg);
  align-items: center; flex-wrap: wrap;
}
.bb-slide-weather .bb-weather-meta { min-width: 0; }
/* Sizing rule: cqmin coefficients are tuned so the temperature reaches
   25-30% of the widget's smaller dimension on big screens — that's where
   premium weather apps put it (Apple Weather, Google Weather, Carrot).
   Max-clamps cap the scale on ultra-large displays so the type doesn't grow
   into "billboard" territory beyond what looks intentional. */
.bb-slide-weather .bb-weather-icon {
  font-size: clamp(64px, 20cqmin, 320px);
  line-height: 1;
  filter: drop-shadow(0 6px 30px color-mix(in srgb, #000 50%, transparent));
}
.bb-slide-weather .bb-weather-temp {
  font: 800 clamp(56px, 22cqmin, 360px)/1 var(--bb-display);
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.03em;
}
.bb-slide-weather .bb-weather-city {
  font-size: clamp(20px, 5.4cqmin, 72px);
  font-weight: 600;
  letter-spacing: -.005em;
  opacity: .9;
}
.bb-slide-weather .bb-weather-desc {
  font-size: clamp(14px, 3cqmin, 36px);
  text-transform: uppercase;
  letter-spacing: .18em;
  opacity: .75;
  font-weight: 500;
}
.bb-slide-weather .bb-weather-stats {
  display: flex; flex-wrap: wrap;
  gap: 0;
  margin-top: var(--w-gap-md);
  font-size: clamp(16px, 3.4cqmin, 40px);
}
/* Stat card — keep selector to (0,2,0) specificity so design-specific
   overrides below (`.bb-weather-design-dashboard .bb-stat-card`) tie and win
   via source order. Adding `.bb-slide-weather` ancestor would push to (0,3,0)
   and beat the dashboard rule. The `.bb-stat-card` class only exists inside
   the weather widget, so the extra ancestor was redundant anyway. */
.bb-weather-stats > .bb-stat-card {
  display: flex; flex-direction: column; gap: var(--w-gap-xs);
  padding: var(--w-gap-xs) var(--w-gap-md);
  border-left: 1px solid var(--w-divider);
  font-variant-numeric: tabular-nums;
}
.bb-weather-stats > .bb-stat-card:first-child {
  border-left: 0; padding-left: 0;
}
/* Stat label — the small uppercase caption above the value. Targeted by
   class (`.bb-stat-label`) so we don't pick up extra spans from the new
   `.bb-stat-icon` and `.bb-stat-sub` siblings introduced for the dashboard
   design (which are hidden by default in non-dashboard designs). */
.bb-slide-weather .bb-weather-stats .bb-stat-label {
  font-size: clamp(12px, 2.2cqmin, 22px);
  text-transform: uppercase;
  letter-spacing: .14em;
  opacity: .65;
  font-weight: 500;
}
.bb-slide-weather .bb-weather-stats b {
  font-weight: 600; opacity: .95;
}
/* Icon + sub belong to the Dashboard design — hidden in every other design so
   the classic / hero / split / etc. layouts keep their original tight
   label+value pairing. */
.bb-slide-weather:not(.bb-weather-design-dashboard) .bb-stat-icon,
.bb-slide-weather:not(.bb-weather-design-dashboard) .bb-stat-sub {
  display: none;
}
/* Unit subscript inside a stat value (`12.4 <i>km/h</i>`). Kept subtle so
   non-dashboard designs read it as inline text; dashboard styles it smaller. */
.bb-slide-weather .bb-stat-unit {
  font-style: normal;
  font-weight: 500;
  opacity: .7;
  margin-left: .18em;
  font-size: .85em;
  letter-spacing: 0;
}

/* Section heads sit above the hourly + forecast blocks in dashboard mode
   ("HOURLY FORECAST · NEXT 12 HOURS" + right-aligned "Updated 2 min ago").
   Hidden in every other design — those layouts already show implicit context
   from the surrounding rhythm. */
.bb-weather-section-head { display: none; }

/* Date next to weekday on forecast tiles ("Tue 28.05"). Hidden by default —
   only the dashboard design uses it. Kept in DOM so the date is always
   available if a user switches a widget to dashboard. */
.bb-forecast-date { display: none; }
.bb-slide-weather .bb-weather-forecast {
  display: grid;
  /* `auto-fill` (NOT auto-fit) — auto-fit collapses unused tracks and lets
     remaining tiles stretch to fill the row, which made a 1-day forecast
     show as one huge tile spanning the entire widget width. auto-fill keeps
     phantom tracks so tiles stay at their declared natural width regardless
     of how many days were configured. Combined with justify-content:start
     we get: 1 day = tile-sized + empty rest, 7 days = filled row. */
  grid-template-columns: repeat(auto-fill, minmax(min(160px, 14cqw), 1fr));
  justify-content: start;
  gap: var(--w-gap-sm);
  margin-top: var(--w-gap-md);
}
/* Cap individual tile width so a stretched 1fr cell on a wide widget doesn't
   bloat the temperature bar to absurd lengths. */
.bb-slide-weather .bb-forecast-day { max-width: 360px; }
.bb-slide-weather .bb-forecast-day {
  background: var(--w-surface);
  border: 1px solid var(--w-divider);
  border-radius: var(--w-radius-sm);
  padding: var(--w-gap-md) var(--w-gap-sm);
  text-align: center;
  display: flex; flex-direction: column;
  gap: var(--w-gap-sm); align-items: center;
  font-size: clamp(15px, 2.8cqmin, 30px);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.bb-slide-weather .bb-forecast-day > span:first-child {
  font-size: clamp(13px, 2.2cqmin, 22px);
  text-transform: uppercase;
  letter-spacing: .12em;
  opacity: .75;
  font-weight: 500;
}
.bb-slide-weather .bb-forecast-icon { font-size: clamp(30px, 5.6cqmin, 72px); filter: drop-shadow(0 2px 6px rgba(0,0,0,.25)); }
.bb-slide-weather .bb-forecast-temps { opacity: .92; }

/* ── Design: classic ── Newsroom. Three sections (current / stats / forecast)
   distribute across the widget height — `space-evenly` so a wide widget
   doesn't leave a huge empty band at the bottom. Icon supports, temp leads;
   icon stays ~70% of temp height so the temperature is always the focal
   point even on full-screen widgets. */
.bb-weather-design-classic {
  justify-content: space-evenly;
  align-items: stretch;
}
.bb-weather-design-classic .bb-weather-current {
  gap: var(--w-gap-lg);
  align-items: center;
  flex-wrap: wrap;
}
.bb-weather-design-classic .bb-weather-icon {
  font-size: clamp(56px, 18cqmin, 280px);
  opacity: .95;
}
.bb-weather-design-classic .bb-weather-temp {
  font-size: clamp(72px, 28cqmin, 420px);
  letter-spacing: -0.035em;
  line-height: .95;
}
.bb-weather-design-classic .bb-weather-meta { flex: 1; min-width: 0; }

/* ── Design: minimal ── Ambient. Temp + city, centred, MASSIVE type.
   Icon is hidden — the temperature IS the design. Calm, decorative.
   ALL secondary panels are hidden: stats, forecast, hourly, hi/lo, sunrise,
   section heads. Minimal lives up to its name regardless of which toggles
   the user has on. */
.bb-weather-design-minimal { justify-content: center; align-items: center; }
.bb-weather-design-minimal .bb-weather-current {
  flex-direction: column;
  gap: var(--w-gap-sm);
  align-items: center; text-align: center;
}
/* Minimal shows the icon above the temperature — sized smaller than hero
   so it stays subordinate to the giant temp number. Emoji and SVG share
   the same .bb-weather-icon container; the nested .bb-weather-icon-svg
   rule below sizes the actual SVG to fill the box. */
.bb-weather-design-minimal .bb-weather-icon {
  font-size: clamp(56px, 14cqmin, 180px);
  width: clamp(56px, 14cqmin, 180px);
  height: clamp(56px, 14cqmin, 180px);
  line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  margin-bottom: calc(-1 * var(--w-gap-xs));
}
.bb-weather-design-minimal .bb-weather-icon .bb-weather-icon-svg {
  width: 100%; height: 100%;
}
.bb-weather-design-minimal .bb-weather-temp {
  font-size: clamp(96px, 38cqmin, 380px);
  letter-spacing: -0.045em;
  line-height: .95;
}
.bb-weather-design-minimal .bb-weather-city {
  font-size: clamp(16px, 2.8cqmin, 32px);
  text-transform: uppercase;
  letter-spacing: .25em;
  font-weight: 500;
  opacity: .65;
}
.bb-weather-design-minimal .bb-weather-desc,
.bb-weather-design-minimal .bb-weather-hilo,
.bb-weather-design-minimal .bb-weather-stats,
.bb-weather-design-minimal .bb-weather-sun,
.bb-weather-design-minimal .bb-weather-hourly,
.bb-weather-design-minimal .bb-weather-section-head,
.bb-weather-design-minimal .bb-weather-forecast { display: none; }

/* ── Design: hero ── Stage. Cinematic. Reading order: Icon → Temp → City →
   Desc, with CSS `order` reshuffling the meta block so the temperature is the
   immediate focal point under the icon (DOM order is city/temp/desc). The
   city becomes a small uppercase caption directly under the huge temp; desc
   sits below it. Stats anchor the bottom edge as a refined info strip. */
.bb-weather-design-hero {
  justify-content: space-between;
  align-items: stretch;
  text-align: center;
}
.bb-weather-design-hero .bb-weather-current {
  flex: 1;
  flex-direction: column;
  gap: var(--w-gap-sm);
  justify-content: center;
  align-items: center;
  padding: var(--w-gap-md) 0;
}
.bb-weather-design-hero .bb-weather-meta {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--w-gap-xs);
}
.bb-weather-design-hero .bb-weather-icon {
  font-size: clamp(56px, 22cqmin, 220px);
  margin-bottom: calc(-1 * var(--w-gap-sm));
  opacity: .95;
}
/* DOM is city/temp/desc; CSS `order` puts temp first so the eye reads
   icon → big temp → small city/desc, which is the canonical premium-weather
   reading order. */
.bb-weather-design-hero .bb-weather-temp {
  order: 1;
  font-size: clamp(96px, 38cqmin, 380px);
  letter-spacing: -0.05em;
  line-height: .92;
}
.bb-weather-design-hero .bb-weather-city {
  order: 2;
  font-size: clamp(13px, 2.6cqmin, 28px);
  text-transform: uppercase;
  letter-spacing: .22em;
  font-weight: 600;
  opacity: .8;
  margin-top: var(--w-gap-xs);
}
.bb-weather-design-hero .bb-weather-desc {
  order: 3;
  font-size: clamp(10px, 1.7cqmin, 16px);
  letter-spacing: .18em;
  opacity: .55;
}
.bb-weather-design-hero .bb-weather-stats {
  justify-content: center;
  margin-top: var(--w-gap-md);
  padding-top: var(--w-gap-md);
  border-top: 1px solid var(--w-divider);
  flex: 0 0 auto;
  opacity: .85;
}
.bb-weather-design-hero .bb-weather-forecast,
.bb-weather-design-hero .bb-weather-hourly,
.bb-weather-design-hero .bb-weather-section-head { display: none; }
/* On short widgets, hero hides supporting bits so the temperature owns the box. */
@container slide (max-height: 240px) {
  .bb-weather-design-hero .bb-weather-stats,
  .bb-weather-design-hero .bb-weather-desc { display: none; }
  .bb-weather-design-hero .bb-weather-current { padding: 0; }
}

/* ── Design: forecast ── Outlook. Slim current weather as a header row,
   then a premium forecast grid as the main attraction. Today's tile is
   highlighted so it reads as "where we are now". */
.bb-weather-design-forecast { gap: var(--w-gap-md); }
.bb-weather-design-forecast .bb-weather-current {
  gap: var(--w-gap-md);
  padding-bottom: var(--w-gap-md);
  border-bottom: 1px solid var(--w-divider);
  flex-wrap: nowrap;
}
.bb-weather-design-forecast .bb-weather-icon { font-size: clamp(56px, 10cqmin, 96px); }
.bb-weather-design-forecast .bb-weather-temp { font-size: clamp(48px, 9cqmin, 96px); letter-spacing: -.04em; }
.bb-weather-design-forecast .bb-weather-city { font-size: clamp(16px, 2.4cqmin, 26px); }
.bb-weather-design-forecast .bb-weather-desc { font-size: clamp(13px, 1.8cqmin, 18px); }
.bb-weather-design-forecast .bb-weather-stats {
  margin-top: var(--w-gap-xs);
  font-size: clamp(13px, 1.8cqmin, 18px);
  flex: 1;
}
.bb-weather-design-forecast .bb-weather-stats > div { padding: 0 var(--w-gap-sm); }
.bb-weather-design-forecast .bb-weather-forecast {
  flex: 1;
  margin-top: 0;
  gap: var(--w-gap-sm);
  grid-template-columns: repeat(auto-fit, minmax(min(120px, 16cqw), 1fr));
  align-content: start;
}
.bb-weather-design-forecast .bb-forecast-day {
  background: var(--w-surface);
  padding: var(--w-gap-md) var(--w-gap-sm);
  gap: var(--w-gap-sm);
  font-size: clamp(14px, 1.9cqmin, 18px);
}
.bb-weather-design-forecast .bb-forecast-day:first-child {
  /* "Today" highlight — subtle accent border so the first card reads as the
     current-day anchor without competing with the header strip above. */
  background: var(--w-surface-strong);
  border-color: color-mix(in srgb, var(--bb-st-accent) 50%, transparent);
}
.bb-weather-design-forecast .bb-forecast-day > span:first-child { font-size: clamp(13px, 1.6cqmin, 15px); }
.bb-weather-design-forecast .bb-forecast-icon { font-size: clamp(34px, 5cqmin, 52px); }
.bb-weather-design-forecast .bb-forecast-temps {
  font-size: clamp(16px, 2.4cqmin, 24px);
  font-weight: 700;
}

/* ── Design: split ── Dashboard. Current weather column-left, forecast list
   column-right, separated by a hairline. The grid is intentionally TWO rows:
   row 1 holds the current weather and the full-height forecast (which spans
   both rows), row 2 holds the stats below the current section.
   Previously the forecast lived only in row 2 — that left the upper-right
   completely empty. */
.bb-weather-design-split {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: 1fr auto;
  gap: var(--w-gap-lg) var(--w-gap-lg);
  align-items: stretch;
}
.bb-weather-design-split .bb-h1 { grid-column: 1 / -1; }
.bb-weather-design-split .bb-weather-current {
  grid-column: 1; grid-row: 1;
  flex-direction: column;
  gap: var(--w-gap-sm);
  align-items: flex-start;
  justify-content: center;
}
.bb-weather-design-split .bb-weather-icon { font-size: clamp(64px, 18cqmin, 180px); }
.bb-weather-design-split .bb-weather-temp { font-size: clamp(72px, 16cqmin, 160px); letter-spacing: -0.035em; }
.bb-weather-design-split .bb-weather-city { font-size: clamp(18px, 3cqmin, 32px); }
.bb-weather-design-split .bb-weather-stats {
  grid-column: 1; grid-row: 2;
  align-self: end;
  margin-top: 0;
}
.bb-weather-design-split .bb-weather-sun {
  grid-column: 1; grid-row: 2; align-self: end;
}
.bb-weather-design-split .bb-weather-forecast {
  grid-column: 2; grid-row: 1 / -1;
  margin-top: 0;
  display: flex; flex-direction: column;
  gap: var(--w-gap-xs);
  align-self: stretch;
  padding-left: var(--w-gap-lg);
  border-left: 1px solid var(--w-divider);
  overflow: hidden;
}
/* Each tile shares the column's height equally — 5 days = 5 tall rows, 7
   days = 7 medium rows. Distributes vertical space deliberately instead of
   leaving acres of whitespace at the bottom of the column. */
.bb-weather-design-split .bb-forecast-day {
  flex: 1 1 0; min-height: 0;
  flex-direction: row; justify-content: space-between; align-items: center;
  padding: var(--w-gap-sm) var(--w-gap-md);
  background: transparent;
  border: 0;
  border-radius: var(--w-radius-sm);
  font-size: clamp(15px, 2cqmin, 20px);
  text-align: left;
  gap: var(--w-gap-md);
}
.bb-weather-design-split .bb-forecast-day:hover { background: var(--w-surface); }
.bb-weather-design-split .bb-forecast-day > span:first-child { flex: 0 0 3em; opacity: .75; }
.bb-weather-design-split .bb-forecast-icon { font-size: clamp(26px, 3.6cqmin, 36px); flex: 0 0 auto; }
.bb-weather-design-split .bb-forecast-precip { flex: 0 0 auto; font-size: .85em; opacity: .75; }
.bb-weather-design-split .bb-forecast-temps {
  flex: 1; display: flex; align-items: center; gap: var(--w-gap-sm);
  justify-content: flex-end;
  font-weight: 600; font-variant-numeric: tabular-nums;
  min-width: 0;
}

/* Responsive collapse: split → vertical on portrait or narrow widgets. */
@container slide (max-aspect-ratio: 1/1) {
  .bb-weather-design-split { grid-template-columns: 1fr; grid-template-rows: auto auto auto; }
  .bb-weather-design-split .bb-weather-current { grid-column: 1; grid-row: 1; }
  .bb-weather-design-split .bb-weather-stats   { grid-column: 1; grid-row: 2; align-self: start; }
  .bb-weather-design-split .bb-weather-forecast {
    grid-column: 1; grid-row: 3;
    padding-left: 0; border-left: 0;
    padding-top: var(--w-gap-md); border-top: 1px solid var(--w-divider);
  }
}
@container slide (max-width: 480px) {
  .bb-weather-design-split .bb-weather-stats { display: none; }
}

/* Split layout doesn't have a natural placement for hourly (the right column
   is forecast list) or the sunrise inline row (it overlaps the stats cell in
   the grid). Hide both — users wanting hourly should pick the `hourly` or
   `dashboard` design which is built around it. */
.bb-weather-design-split .bb-weather-hourly,
.bb-weather-design-split .bb-weather-sun,
.bb-weather-design-split .bb-weather-section-head { display: none; }

/* ── Design: hourly ── Today-focused. Current weather as a tight header,
   then a horizontal hourly strip dominating. Forecast (if enabled) sits at
   the bottom as a compact row. */
.bb-weather-design-hourly { gap: var(--w-gap-md); }
.bb-weather-design-hourly .bb-weather-current {
  gap: var(--w-gap-md);
  padding-bottom: var(--w-gap-md);
  border-bottom: 1px solid var(--w-divider);
}
.bb-weather-design-hourly .bb-weather-icon { font-size: clamp(48px, 9cqmin, 88px); }
.bb-weather-design-hourly .bb-weather-temp { font-size: clamp(48px, 9cqmin, 96px); }
.bb-weather-design-hourly .bb-weather-city { font-size: clamp(16px, 2.4cqmin, 26px); }
.bb-weather-design-hourly .bb-weather-desc { font-size: clamp(13px, 1.8cqmin, 18px); }
.bb-weather-design-hourly .bb-weather-stats { font-size: clamp(13px, 1.8cqmin, 18px); margin-top: var(--w-gap-xs); }
.bb-weather-design-hourly .bb-weather-hourly { flex: 1; }
.bb-weather-design-hourly .bb-weather-forecast {
  margin-top: 0;
  grid-template-columns: repeat(auto-fit, minmax(min(90px, 14cqw), 1fr));
}
.bb-weather-design-hourly .bb-forecast-day { padding: var(--w-gap-sm); }

/* ── Design: dashboard ─────────────────────────────────────────────────────
   Premium TV-grade variant — designed for full-canvas signage on a 1080p+
   display, viewable from 3m+ away. Layout, colour tokens and typography are
   ported from the Claude Design reference (xqkk1LwifNZMI02hPJZqRQ).

   • Background: deep blue radial gradient + faint star/grain overlay +
     bottom vignette. Painted on the root so the theme stack stays out of the
     way — dashboard owns its look-and-feel.
   • Hero: icon left, city + huge warm-amber gradient temp right, desc/hilo
     baseline-aligned beneath. KPI cards (Wind · Humidity · Feels · Sun) line
     up across the top-right.
   • Hourly + Daily: labelled section heads with right-aligned meta strings,
     hourly tiles inside a soft glass panel, day cards with HEUTE/Today amber
     accent + subtle lift.
   • Sun-rays gently rotate (60s) and the sun-core breathes (4s) — both
     respect `prefers-reduced-motion`.

   Layout collapses to single column on portrait / narrow widgets via
   container queries lower in this file. */
.bb-weather-design-dashboard {
  /* Dashboard colour tokens — wired into the theme system so the Theme picker
     actually does something on this design. The warm-amber accent colour is
     derived from `--bb-st-accent` (each theme defines its own); the deep
     backdrop comes from `--bb-st-bg` and falls back to the design's
     signature deep-blue gradient if no theme is active.

     Ink colours are derived from `--bb-st-fg` (theme foreground) so light
     themes (Editorial Mono: #1c1c1c on #f4f3ef) render dark text on the
     light background instead of invisible white-on-white. Dim/faint/line/
     card tints cascade from --w-ink via color-mix so a single fg change
     repaints the whole card surface coherently.

     Semantic temperature colours (hot / cold / cool / rain) stay locked —
     "blue means cold" is a universal convention and shouldn't shift with a
     brand palette. */
  --w-accent:    var(--bb-st-accent, #f5a85a);
  --w-warm:      var(--w-accent);
  --w-warm-hi:   color-mix(in srgb, var(--w-accent) 55%, white);
  --w-warm-lo:   color-mix(in srgb, var(--w-accent) 70%, black);
  --w-hot:  #ff7a3d;
  --w-cool: #79b6ff;
  --w-cold: #bcd2ff;
  --w-rain: #6aa9ff;
  --w-ink:       var(--bb-st-fg, #eaf0ff);
  --w-ink-dim:   color-mix(in srgb, var(--w-ink) 60%, transparent);
  --w-ink-faint: color-mix(in srgb, var(--w-ink) 35%, transparent);
  --w-line:      color-mix(in srgb, var(--w-ink) 10%, transparent);
  --w-card:      color-mix(in srgb, var(--w-ink) 5%, transparent);

  color: var(--w-ink);
  display: grid;
  /* Stats column gets nearly as much room as the hero now — the bumped KPI
     labels and subtitles need ~1.15fr to avoid mid-word truncation. */
  grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr);
  /* Tighter gap + padding so the dashboard fits a 16:9 slide canvas even with
     all sections (hero+stats / hourly / forecast) at TV-readable text sizes. */
  gap: var(--w-gap-md);
  align-content: start;
  padding: var(--w-gap-md);
  position: relative;
  isolation: isolate;
  /* Transparent so the widget's bgLayer (custom background OR theme bg) shows
     through. The stars + vignette pseudo-elements below sit on top as
     decoration, not as the actual background paint. */
  background: transparent;
  overflow: hidden;
}
/* Star/grain layer + bottom vignette — purely decorative, painted via
   pseudo-elements so the DOM stays clean. Each radial-gradient is a single
   pixel "star" tiled across the surface. */
.bb-weather-design-dashboard::before {
  content: "";
  position: absolute; inset: 0;
  background-image:
    radial-gradient(1px 1px at 20% 30%, rgba(255,255,255,.35) 50%, transparent 51%),
    radial-gradient(1px 1px at 70% 60%, rgba(255,255,255,.22) 50%, transparent 51%),
    radial-gradient(1px 1px at 45% 80%, rgba(255,255,255,.18) 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 85% 20%, rgba(255,255,255,.28) 50%, transparent 51%),
    radial-gradient(1px 1px at 10% 70%, rgba(255,255,255,.2) 50%, transparent 51%),
    radial-gradient(1px 1px at 60% 15%, rgba(255,255,255,.3) 50%, transparent 51%);
  background-size: 800px 800px, 600px 600px, 700px 700px, 900px 900px, 500px 500px, 750px 750px;
  opacity: .5;
  pointer-events: none;
  z-index: -1;
}
.bb-weather-design-dashboard::after {
  content: "";
  position: absolute; inset: 0;
  background: radial-gradient(60% 50% at 50% 110%, rgba(0,0,0,.5), transparent 70%);
  pointer-events: none;
  z-index: -1;
}
/* No stats? Hero takes the full width. */
.bb-weather-design-dashboard:not(:has(> .bb-weather-stats)) {
  grid-template-columns: 1fr;
}
.bb-weather-design-dashboard > .bb-h1                   { grid-column: 1 / -1; }
.bb-weather-design-dashboard > .bb-weather-current      { grid-column: 1; }
.bb-weather-design-dashboard > .bb-weather-stats        { grid-column: 2; }
.bb-weather-design-dashboard > .bb-weather-section-head { grid-column: 1 / -1; display: flex; }
.bb-weather-design-dashboard > .bb-weather-hourly       { grid-column: 1 / -1; }
.bb-weather-design-dashboard > .bb-weather-forecast     { grid-column: 1 / -1; }

/* Hero column — icon left, meta block right with baseline-aligned
   desc/hilo under the giant temp. Layout uses CSS Grid on .bb-weather-meta
   so the city/temp span full width and desc/hilo share row 3. */
.bb-weather-design-dashboard .bb-weather-current {
  gap: clamp(16px, 3cqmin, 48px);
  align-items: flex-end;
  flex-wrap: nowrap;
  min-width: 0;
}
.bb-weather-design-dashboard .bb-weather-icon {
  width: clamp(96px, 18cqmin, 280px);
  height: clamp(96px, 18cqmin, 280px);
  /* font-size matches the box so the SVG fills (via 100%/100% override) AND
     emoji icons render at the same physical size — supports both iconSet
     options without breaking layout. */
  font-size: clamp(96px, 18cqmin, 280px);
  line-height: 1;
  flex: 0 0 auto;
  margin-bottom: clamp(8px, 1.5cqmin, 20px);
  filter: drop-shadow(0 clamp(6px, 1.4cqmin, 18px) clamp(12px, 2.8cqmin, 36px)
    color-mix(in srgb, var(--w-accent) 35%, transparent));
  display: inline-flex; align-items: center; justify-content: center;
}
.bb-weather-design-dashboard .bb-weather-icon .bb-weather-icon-svg {
  width: 100%; height: 100%;
}
.bb-weather-design-dashboard .bb-weather-meta {
  display: grid;
  grid-template-columns: auto auto;
  column-gap: clamp(12px, 2.2cqmin, 28px);
  row-gap: clamp(4px, .8cqmin, 12px);
  align-items: baseline;
  min-width: 0;
}
.bb-weather-design-dashboard .bb-weather-city,
.bb-weather-design-dashboard .bb-weather-temp { grid-column: 1 / -1; }
.bb-weather-design-dashboard .bb-weather-desc { grid-column: 1; }
.bb-weather-design-dashboard .bb-weather-hilo { grid-column: 2; }
.bb-weather-design-dashboard .bb-weather-city {
  /* Bumped past the reference (3.2u ≈ 36px @ 1080p) so the city carries
     more visual weight at TV viewing distance. */
  font-size: clamp(30px, 5.5cqmin, 88px);
  font-weight: 600;
  letter-spacing: -.01em;
  color: var(--w-ink);
}
.bb-weather-design-dashboard .bb-weather-temp {
  /* Warm-amber gradient text — defining tagline of the design. CSS specificity
     overrides any inline `style="color:..."` from the binding layer; dashboard
     ignores colorTemperature for the main temp.
     Sized to match the reference's 13u ≈ 147px at 1080p so the temp doesn't
     swallow the supporting text. */
  font-size: clamp(84px, 13cqmin, 240px);
  font-weight: 700;
  line-height: .85;
  letter-spacing: -.04em;
  background: linear-gradient(180deg, var(--w-warm-hi) 0%, var(--w-warm) 60%, var(--w-warm-lo) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  font-variant-numeric: tabular-nums;
}
/* Two cases want a flat-fill temp instead of the amber gradient:
     ① Brand text-colour override (textcolor-on) — flow --bb-st-fg through.
     ② Colour-coded temperature (colortemp-on) — let the JS-set inline
        style="color: hsl(...)" win for per-value cold→hot tinting.
   Both drop the background-clip:text trick. -webkit-text-fill-color is set
   to currentColor so the inline `color` (when present) reaches the glyphs;
   without the override classes, the original amber-gradient block keeps
   `-webkit-text-fill-color: transparent` and wins. */
.bb-weather-design-dashboard.bb-weather-textcolor-on .bb-weather-temp,
.bb-weather-design-dashboard.bb-weather-colortemp-on .bb-weather-temp {
  background: none;
  -webkit-background-clip: initial;
  background-clip: initial;
  -webkit-text-fill-color: currentColor;
  color: var(--bb-st-fg);
}
/* Dashboard's desc styling — visible only when the showDescription toggle is
   on. Hide rule is in the global `.bb-weather-no-desc` block above. */
.bb-weather-design-dashboard .bb-weather-desc {
  font-size: clamp(13px, 2.2cqmin, 26px);
  margin-top: 0;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: .12em;
  opacity: 1;
}
.bb-weather-design-dashboard .bb-weather-hilo {
  font-size: clamp(22px, 3.5cqmin, 52px);
  margin-top: 0;
  font-weight: 600;
  gap: clamp(6px, 1cqmin, 14px);
  align-items: center;
}
/* Hi/Lo colour-coding — only applied when the "Colour-code temperature"
   toggle is on. When off, hi/lo inherit the ink colour so the toggle has a
   visible effect (vivid blue+red vs. neutral white). */
.bb-weather-design-dashboard.bb-weather-colortemp-on .bb-weather-hi { color: var(--w-hot); }
.bb-weather-design-dashboard.bb-weather-colortemp-on .bb-weather-lo { color: var(--w-cold); }

/* Stat cards — KPI cards with icon+label header on top, big value below,
   subtle hint at the bottom. Glass treatment via backdrop-filter when
   supported. Cards form a 1×N grid that auto-wraps to 2×2 on medium
   widgets and 1-col on narrow widgets. */
.bb-weather-design-dashboard .bb-weather-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(140px, 100%), 1fr));
  gap: clamp(8px, 1.4cqmin, 20px);
  margin-top: 0;
  align-self: end;
  align-content: start;
  font-size: inherit;
  padding-bottom: clamp(4px, .8cqmin, 12px);
}
.bb-weather-design-dashboard .bb-stat-card {
  display: grid;
  /* International mode: icon on top, value beneath. Label + sub are hidden
     (rules below). The icon carries the meaning so the card reads in any
     language. */
  grid-template-areas:
    "icon"
    "value";
  grid-template-columns: 1fr;
  row-gap: clamp(6px, 1.2cqmin, 16px);
  align-items: center;
  justify-items: center;
  text-align: center;
  background: var(--w-card);
  border: 1px solid var(--w-line);
  border-radius: clamp(10px, 1.4cqmin, 20px);
  padding: clamp(14px, 1.8cqmin, 26px) clamp(12px, 1.4cqmin, 22px);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  font-variant-numeric: tabular-nums;
  min-width: 0;
  overflow: hidden;
}
/* Reset the base `.bb-weather-stats > .bb-stat-card:first-child` rule which
   stripped the border-left/padding-left for the inline label-divider layout
   used by classic/hero/etc. Dashboard cards are independent rectangles. */
.bb-weather-design-dashboard .bb-stat-card:first-child {
  border-left: 1px solid var(--w-line);
  padding-left: clamp(12px, 1.4cqmin, 22px);
}
.bb-weather-design-dashboard .bb-stat-card .bb-stat-icon {
  grid-area: icon;
  display: inline-flex; align-items: center; justify-content: center;
  /* Bumped large — it's now the only visual identifier (label hidden). */
  width: clamp(36px, 4.5cqmin, 72px);
  height: clamp(36px, 4.5cqmin, 72px);
  font-size: clamp(36px, 4.5cqmin, 72px);
  opacity: .85;
  color: var(--w-ink-dim);
}
.bb-weather-design-dashboard .bb-stat-card .bb-stat-icon .bb-weather-icon-svg {
  width: 100%; height: 100%;
}
.bb-weather-design-dashboard .bb-stat-card .bb-stat-label {
  /* Hidden in dashboard — icon carries the meaning. Kept in DOM for screen
     readers so the card still announces "Wind: 7.4 km/h" etc. */
  position: absolute;
  width: 1px; height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}
.bb-weather-design-dashboard .bb-stat-card b {
  grid-area: value;
  /* Bumped — with labels hidden, value is the prominent text and can grow. */
  font-size: clamp(28px, 4.4cqmin, 64px);
  font-weight: 600;
  line-height: 1;
  letter-spacing: -.01em;
  color: var(--w-ink);
  min-width: 0;
}
.bb-weather-design-dashboard .bb-stat-card .bb-stat-sub {
  /* Hidden in dashboard — international-friendly. "Comfortable" / "As actual"
     / "Northwest · Light" are English descriptors. Value alone is enough. */
  display: none;
}
/* Wind direction arrow is promoted to the stat-icon position (top of card)
   when the wind-vector toggle is on, so the value below is just the speed
   number. Arrow inherits the icon's clamp() size and color, with full
   opacity so it reads as a primary visual element. */
.bb-weather-design-dashboard .bb-stat-card-wind .bb-stat-icon .bb-weather-wind-arrow {
  width: 100%;
  height: 100%;
  opacity: 1;
  color: var(--w-warm);
  transition: transform .8s cubic-bezier(.2,.8,.2,1);
}
.bb-weather-design-dashboard .bb-stat-card .bb-stat-unit {
  /* Bumped from 0.48em — the unit subscripts were too small to read. */
  font-size: .55em;
  color: var(--w-ink-faint);
  margin-left: .3em;
  letter-spacing: 0;
  font-weight: 500;
}
/* Sun-card's time string ("05:24 · 21:00") sits in the value slot. Keep it
   smaller than numeric stats so two times fit on a single line, and force
   nowrap so the "·" separator doesn't break onto its own line. */
.bb-weather-design-dashboard .bb-stat-card-sun b {
  font-size: clamp(20px, 2.5cqmin, 38px);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Section heads — hidden in dashboard. "Hourly forecast · Next 12 hours"
   and "7-day forecast" are localized English phrases that don't translate
   without an i18n layer. Panel gap + visual rhythm already separate the
   sections. */
.bb-weather-design-dashboard .bb-weather-section-head {
  display: none;
}
.bb-weather-design-dashboard .bb-weather-section-title {
  font-size: clamp(19px, 2.5cqmin, 38px);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .16em;
  color: var(--w-ink-dim);
  opacity: 1;
}
.bb-weather-design-dashboard .bb-weather-section-meta {
  font-size: clamp(17px, 2.2cqmin, 32px);
  color: var(--w-ink-faint);
  font-weight: 500;
  font-family: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
  font-variant-numeric: tabular-nums;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  min-width: 0;
  letter-spacing: 0;
}

/* Hourly panel — soft glass card with subtle gradient bg, tiles fill the row. */
.bb-weather-design-dashboard .bb-weather-hourly {
  background: linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,255,255,.02));
  border: 1px solid var(--w-line);
  border-radius: clamp(12px, 2cqmin, 28px);
  padding: clamp(12px, 2cqmin, 26px) clamp(10px, 1.8cqmin, 22px);
  margin-top: 0;
  gap: clamp(4px, 1cqmin, 14px);
  align-items: stretch;
}
.bb-weather-design-dashboard .bb-weather-hour {
  background: transparent;
  border: 1px solid transparent;
  padding: clamp(8px, 1.2cqmin, 18px) clamp(4px, .4cqmin, 8px);
  border-radius: clamp(8px, 1cqmin, 14px);
  /* No `height: 100%` — that created a chicken-and-egg with the panel's
     auto-row height (panel = tile, tile = panel). Letting tiles size
     naturally means the panel grows to fit time + icon + temp + precip. */
  justify-content: space-around;
  transition: background .4s ease;
}
.bb-weather-design-dashboard .bb-weather-hour-now {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--w-accent) 18%, transparent),
    color-mix(in srgb, var(--w-accent) 4%, transparent));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--w-accent) 35%, transparent);
  border-color: transparent;
}
.bb-weather-design-dashboard .bb-weather-hour-now .bb-hour-time {
  color: var(--w-warm-hi);
  opacity: 1;
  font-weight: 600;
}
.bb-weather-design-dashboard .bb-hour-time {
  font-size: clamp(19px, 2.5cqmin, 36px);
  font-family: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
  color: var(--w-ink-dim);
  letter-spacing: .04em;
}
.bb-weather-design-dashboard .bb-hour-icon {
  width: clamp(40px, 5cqmin, 80px);
  height: clamp(40px, 5cqmin, 80px);
  font-size: clamp(36px, 4.5cqmin, 72px);
  line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  margin: clamp(2px, .3cqmin, 6px) 0;
}
.bb-weather-design-dashboard .bb-hour-icon .bb-weather-icon-svg {
  width: 100%; height: 100%;
}
.bb-weather-design-dashboard .bb-hour-temp {
  font-size: clamp(24px, 3.3cqmin, 52px);
  font-weight: 600;
  color: var(--w-ink);
}
.bb-weather-design-dashboard .bb-hour-precip {
  font-size: clamp(17px, 2cqmin, 30px);
  color: var(--w-rain);
  min-height: clamp(20px, 2.1cqmin, 32px);
  display: flex; align-items: center; gap: clamp(2px, .25cqmin, 4px);
  opacity: 1;
}
.bb-weather-design-dashboard .bb-hour-precip .bb-weather-icon-svg {
  width: clamp(17px, 2cqmin, 30px);
  height: clamp(17px, 2cqmin, 30px);
}

/* Forecast grid — 7 cards with HEUTE/Today amber highlight + subtle lift. */
.bb-weather-design-dashboard .bb-weather-forecast {
  margin-top: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(140px, 14cqw), 1fr));
  gap: clamp(8px, 1.3cqmin, 18px);
  justify-content: stretch;
}
.bb-weather-design-dashboard .bb-forecast-day {
  background: linear-gradient(180deg, rgba(255,255,255,.045), rgba(255,255,255,.015));
  border: 1px solid var(--w-line);
  border-radius: clamp(10px, 1.6cqmin, 24px);
  padding: clamp(14px, 2cqmin, 28px) clamp(10px, 1.6cqmin, 22px);
  /* Grid keeps the head row spanning both columns, places the icon + precip
     on a shared body row (matches the reference's compact middle), and
     leaves the temps row spanning both at the bottom. `1fr` on the body
     row makes the tile fill its allotted height even when precip is empty. */
  display: grid;
  grid-template-areas:
    "head head"
    "icon precip"
    "temps temps";
  grid-template-columns: auto 1fr;
  grid-template-rows: auto 1fr auto;
  gap: clamp(8px, 1.2cqmin, 16px);
  text-align: left;
  align-items: center;
  max-width: none;
  transition: transform .5s cubic-bezier(.2,.8,.2,1), background .5s, border-color .5s;
}
.bb-weather-design-dashboard .bb-forecast-day > span:first-child { grid-area: head; }
.bb-weather-design-dashboard .bb-forecast-day .bb-forecast-icon { grid-area: icon; }
.bb-weather-design-dashboard .bb-forecast-day .bb-forecast-precip {
  grid-area: precip;
  justify-self: end;
}
.bb-weather-design-dashboard .bb-forecast-day .bb-forecast-temps { grid-area: temps; }
/* Em-dash placeholder when there's no precipitation chance — keeps the tile
   height consistent so forecast cards / hourly tiles don't jump as some days
   show "%" and others omit it. Used by both hourly and forecast renders in
   all designs (dashboard, classic, forecast, hourly, split, ...). */
.bb-weather-precip-empty {
  color: currentColor;
  opacity: .35;
  font-weight: 500;
}
.bb-weather-design-dashboard .bb-forecast-day-today {
  background: linear-gradient(180deg,
    color-mix(in srgb, var(--w-accent) 16%, transparent),
    color-mix(in srgb, var(--w-accent) 3%, transparent));
  border-color: color-mix(in srgb, var(--w-accent) 35%, transparent);
  transform: translateY(clamp(-6px, -.4cqmin, -2px));
}
/* Weekday label sits on a row with the date — flex it out so they spread. */
.bb-weather-design-dashboard .bb-forecast-day > span:first-child {
  display: flex; align-items: center; justify-content: space-between;
  gap: clamp(4px, .8cqmin, 10px);
  font-size: clamp(22px, 2.9cqmin, 44px);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .12em;
  color: var(--w-ink-dim);
  opacity: 1;
}
.bb-weather-design-dashboard .bb-forecast-day-today > span:first-child {
  color: var(--w-warm-hi);
}
.bb-weather-design-dashboard .bb-forecast-date {
  display: inline;
  margin-left: auto;
  font-style: normal;
  /* Bumped from .82em — the date next to the weekday was too small to read. */
  font-size: .95em;
  font-weight: 500;
  color: var(--w-ink-faint);
  letter-spacing: 0;
  text-transform: none;
  font-family: ui-monospace, 'SF Mono', SFMono-Regular, Menlo, Consolas, monospace;
}
.bb-weather-design-dashboard .bb-forecast-icon {
  width: clamp(48px, 7cqmin, 108px);
  height: clamp(48px, 7cqmin, 108px);
  font-size: clamp(42px, 6.2cqmin, 96px);
  line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  filter: none;
  align-self: flex-start;
}
.bb-weather-design-dashboard .bb-forecast-icon .bb-weather-icon-svg {
  width: 100%; height: 100%;
}
.bb-weather-design-dashboard .bb-forecast-precip {
  color: var(--w-rain);
  font-size: clamp(20px, 2.5cqmin, 38px);
  font-weight: 500;
  display: inline-flex; align-items: center; gap: clamp(3px, .4cqmin, 6px);
  opacity: 1;
}
.bb-weather-design-dashboard .bb-forecast-precip .bb-weather-icon-svg {
  width: clamp(20px, 2.5cqmin, 36px);
  height: clamp(20px, 2.5cqmin, 36px);
}
.bb-weather-design-dashboard .bb-forecast-temps {
  display: flex; align-items: center;
  gap: clamp(6px, .8cqmin, 12px);
  margin-top: 0;
  width: 100%;
  justify-content: space-between;
}
.bb-weather-design-dashboard .bb-forecast-lo,
.bb-weather-design-dashboard .bb-forecast-hi {
  /* TV-distance daily lo/hi — most-glanced numbers in the 7-day strip, sized
     to read clearly across the room. */
  font-size: clamp(24px, 3.2cqmin, 56px);
  font-weight: 700;
  flex: 0 0 auto;
  letter-spacing: -.01em;
}
/* Forecast tile lo/hi — semantic colours only with colour-coding on. */
.bb-weather-design-dashboard .bb-forecast-lo { opacity: 1; }
.bb-weather-design-dashboard.bb-weather-colortemp-on .bb-forecast-lo { color: var(--w-cold); }
.bb-weather-design-dashboard.bb-weather-colortemp-on .bb-forecast-hi { color: var(--w-hot); }
.bb-weather-design-dashboard .bb-forecast-bar {
  /* `flex: 0 1 …` instead of `flex: 1` — the bar gets a base width and can
     shrink if needed, but doesn't STRETCH to fill the remaining gap between
     lo/hi. Matches the reference where the bar sits as a short chip between
     the temperature numbers, not as a full-width track. */
  flex: 0 1 clamp(32px, 5.5cqw, 90px);
  min-width: clamp(20px, 3cqw, 48px);
  /* Bumped back up so the bar reads as a chip rather than a hairline. */
  height: clamp(5px, .75cqmin, 12px);
  background: rgba(255,255,255,.08);
  border-radius: 999px;
  position: relative;
  overflow: hidden;
}
.bb-weather-design-dashboard .bb-forecast-bar::before {
  content: '';
  position: absolute;
  top: 0; bottom: 0;
  left: var(--bar-left, 0%);
  width: var(--bar-width, 100%);
  background: linear-gradient(90deg, var(--w-cool) 0%, var(--w-warm) 60%, var(--w-hot) 100%);
  border-radius: 999px;
}

/* Sun-ray + sun-core animations on the hero icon. Both honor reduced-motion
   below. The .bb-sun-rays / .bb-sun-core classes live inside the SVG symbol
   definition (weather-svg-icons.js). */
.bb-weather-design-dashboard .bb-sun-rays {
  transform-origin: center;
  transform-box: fill-box;
  animation: bb-sun-spin 60s linear infinite;
}
.bb-weather-design-dashboard .bb-sun-core {
  transform-origin: center;
  transform-box: fill-box;
  animation: bb-sun-breathe 4s ease-in-out infinite;
}
@keyframes bb-sun-spin {
  to { transform: rotate(360deg); }
}
@keyframes bb-sun-breathe {
  0%, 100% { transform: scale(1); }
  50%      { transform: scale(1.04); }
}
/* Rain / snow / bolt / fog motion — same scoping rules as sun: only active
   inside the dashboard layout, reduced-motion safe. Class names live in the
   SVG symbol definitions (weather-svg-icons.js). */
.bb-weather-design-dashboard .bb-rain-drop {
  animation: bb-rain-fall 1.1s linear infinite;
}
.bb-weather-design-dashboard .bb-rain-drop:nth-child(2) { animation-delay: -.35s; }
.bb-weather-design-dashboard .bb-rain-drop:nth-child(3) { animation-delay: -.70s; }
.bb-weather-design-dashboard .bb-snow-flake {
  transform-origin: center;
  transform-box: fill-box;
  animation: bb-snow-fall 3s linear infinite;
}
.bb-weather-design-dashboard .bb-snow-flake:nth-child(2) { animation-delay: -1s; }
.bb-weather-design-dashboard .bb-snow-flake:nth-child(3) { animation-delay: -2s; }
.bb-weather-design-dashboard .bb-bolt {
  transform-origin: center;
  transform-box: fill-box;
  animation: bb-bolt-flash 2.6s ease-in-out infinite;
}
.bb-weather-design-dashboard .bb-fog-line {
  animation: bb-fog-drift 5s ease-in-out infinite;
}
.bb-weather-design-dashboard .bb-fog-line:nth-child(2) { animation-delay: -1.6s; }
.bb-weather-design-dashboard .bb-fog-line:nth-child(3) { animation-delay: -3.2s; }

@keyframes bb-rain-fall {
  0%   { transform: translateY(-6px); opacity: 0; }
  20%  { opacity: 1; }
  80%  { opacity: 1; }
  100% { transform: translateY(8px);  opacity: 0; }
}
@keyframes bb-snow-fall {
  0%   { transform: translateY(-4px) rotate(0deg);   opacity: 0; }
  15%  { opacity: 1; }
  85%  { opacity: 1; }
  100% { transform: translateY(10px) rotate(360deg); opacity: 0; }
}
@keyframes bb-bolt-flash {
  0%, 92%, 100% { opacity: 1; filter: drop-shadow(0 0 0 transparent); }
  94%           { opacity: .25; }
  96%           { opacity: 1; filter: drop-shadow(0 0 6px rgba(255, 226, 122, .9)); }
}
@keyframes bb-fog-drift {
  0%, 100% { transform: translateX(-2px); }
  50%      { transform: translateX(2px); }
}

@media (prefers-reduced-motion: reduce) {
  .bb-weather-design-dashboard .bb-sun-rays,
  .bb-weather-design-dashboard .bb-sun-core,
  .bb-weather-design-dashboard .bb-rain-drop,
  .bb-weather-design-dashboard .bb-snow-flake,
  .bb-weather-design-dashboard .bb-bolt,
  .bb-weather-design-dashboard .bb-fog-line {
    animation: none;
  }
}

/* Responsive collapse — portrait or narrow widgets stack the hero+stats
   columns into a single column. Section heads stay full-width either way. */
@container slide (max-aspect-ratio: 1/1) {
  .bb-weather-design-dashboard { grid-template-columns: 1fr; }
  .bb-weather-design-dashboard > .bb-weather-current,
  .bb-weather-design-dashboard > .bb-weather-stats { grid-column: 1; }
}
@container slide (max-width: 600px) {
  .bb-weather-design-dashboard { grid-template-columns: 1fr; gap: var(--w-gap-md); padding: var(--w-gap-md); }
  .bb-weather-design-dashboard > .bb-weather-current,
  .bb-weather-design-dashboard > .bb-weather-stats { grid-column: 1; }
  /* On really narrow widgets the section meta becomes noise — drop it. */
  .bb-weather-design-dashboard .bb-weather-section-meta { display: none; }
}

/* ── New sub-elements (shared) ───────────────────────────────────────────── */

/* Hi/Lo — inline pair under the temperature */
.bb-weather-hilo {
  display: flex; gap: var(--w-gap-md);
  font-size: clamp(16px, 2.8cqmin, 32px);
  font-variant-numeric: tabular-nums;
  opacity: .9;
  margin-top: var(--w-gap-xs);
}
.bb-weather-hi, .bb-weather-lo {
  display: inline-flex; align-items: center; gap: 4px;
  font-weight: 600;
}
.bb-weather-hilo b { font-weight: 700; }

/* Sunrise / Sunset row — appears above the forecast as a quiet caption.
   The full-width row layout looked awkward in classic/hero/split/forecast/
   hourly (floated mid-page or overlapped stats). For non-dashboard designs
   we anchor it inline next to the stats with a subtle separator instead of
   reserving its own row. Dashboard uses its own .bb-stat-card-sun stat tile. */
.bb-weather-sun {
  display: inline-flex; gap: clamp(8px, 1.4cqmin, 16px); justify-content: flex-start;
  align-items: center;
  font-size: clamp(14px, 2cqmin, 22px);
  opacity: .75;
  margin-top: clamp(6px, 1cqmin, 12px);
  padding-left: clamp(8px, 1.4cqmin, 16px);
  margin-left: clamp(8px, 1.4cqmin, 16px);
  border-left: 1px solid var(--w-divider);
  border-top: 0;
  padding-top: 0;
}
.bb-weather-sun > span { display: inline-flex; align-items: center; gap: 6px; font-variant-numeric: tabular-nums; }
.bb-weather-sun b { font-weight: 600; }

/* Hourly strip — horizontal cells; auto-fit so it reflows on narrow widgets.
   `flex-shrink: 0` so flex parents (classic/hero) can't squeeze the panel
   below its natural content height and clip the temps off the bottom. */
.bb-weather-hourly {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(min(84px, 8cqw), 1fr);
  gap: var(--w-gap-xs);
  margin-top: var(--w-gap-sm);
  overflow-x: auto;
  scrollbar-width: none;
  flex-shrink: 0;
}
.bb-weather-hourly::-webkit-scrollbar { display: none; }
.bb-weather-hour {
  display: flex; flex-direction: column; align-items: center;
  gap: 6px;
  padding: var(--w-gap-md) var(--w-gap-sm);
  background: var(--w-surface);
  border: 1px solid var(--w-divider);
  border-radius: var(--w-radius-sm);
  font-size: clamp(13px, 2cqmin, 20px);
  font-variant-numeric: tabular-nums;
  text-align: center;
}
.bb-hour-time { opacity: .75; font-weight: 500; font-size: clamp(13px, 2cqmin, 18px); }
.bb-hour-icon { font-size: clamp(28px, 3.8cqmin, 40px); }
.bb-hour-temp { font-weight: 700; font-size: clamp(18px, 3cqmin, 30px); }
.bb-hour-precip { opacity: .8; font-size: .9em; }

/* Wind direction arrow — small SVG inline before the wind value */
.bb-weather-stats .bb-stat-wind { display: inline-flex; align-items: center; gap: 6px; }
.bb-weather-wind-arrow { width: 1em; height: 1em; flex: 0 0 auto; opacity: .8; transition: transform .2s ease-out; }

/* Premium forecast tile — replaces the old simple "weekday + icon + temps" tile.
   Each tile now shows a small temperature-range bar between the lo and hi,
   coloured along the cold→hot gradient. Optional precip line above. */
.bb-slide-weather .bb-forecast-temps {
  display: flex; align-items: center; gap: var(--w-gap-xs);
  justify-content: center;
  width: 100%;
}
.bb-forecast-bar {
  flex: 1;
  height: clamp(5px, .9cqmin, 8px);
  background: var(--w-surface-strong);
  border-radius: 99px;
  position: relative;
  overflow: hidden;
  min-width: 24px;
}
.bb-forecast-bar::before {
  content: '';
  position: absolute;
  left: var(--bar-left, 0%);
  width: var(--bar-width, 100%);
  top: 0; bottom: 0;
  background: var(--bar-gradient, linear-gradient(90deg, #fff, #fff));
  border-radius: 99px;
}
.bb-forecast-lo, .bb-forecast-hi { font-variant-numeric: tabular-nums; font-weight: 700; }
.bb-forecast-lo { opacity: .75; font-size: .95em; }
.bb-forecast-precip {
  font-size: .85em; opacity: .75; font-weight: 500;
  font-variant-numeric: tabular-nums;
}

/* Universal narrow-widget tightening — stack the current-row and trim the
   stats gutters so a 240-ish-wide widget still reads cleanly. Font-size of
   stats is intentionally NOT overridden here; the base rule's 15px floor
   keeps the labels readable on tablets even at narrow widths. */
@container slide (max-width: 380px) {
  .bb-slide-weather .bb-weather-current { gap: var(--w-gap-sm); flex-direction: column; align-items: flex-start; }
  .bb-slide-weather .bb-weather-stats { gap: 0; }
  .bb-slide-weather .bb-weather-stats > div { padding: 0 var(--w-gap-sm); border-left-width: 0; }
  .bb-slide-weather .bb-weather-stats > div + div { border-left-width: 1px; }
}

/* Markdown — the article font-size is a cqmin clamp (tracks the widget box,
   like RSS/News) multiplied by --bb-md-text-scale (the inspector's Text size,
   1 = 100%). Headings/lists below are sized in em, so they ride this base. */
.bb-md {
  font-size: calc(clamp(15px, 3.6cqmin, 44px) * var(--bb-md-text-scale, 1));
  line-height: 1.5;
}
.bb-md h1 { font-size: 1.9em; line-height: 1.15; font-weight: 800; }
.bb-md h2 { font-size: 1.45em; line-height: 1.2; }
.bb-md h3 { font-size: 1.2em; line-height: 1.25; }
.bb-md h1, .bb-md h2, .bb-md h3 { margin: .3em 0 .2em; }
/* list-style-position:inside puts the bullet INSIDE the line box, so it joins
   the text flow and follows the article's text-align — under centred text the
   marker centres with its item instead of being pinned to the widget's left
   edge. Lists stay block-level, so they still stack vertically above/below the
   quote (an inline-block list would flow side-by-side with the next block). */
.bb-md ul, .bb-md ol { list-style-position: inside; padding-left: 0; margin: .3em 0; }
.bb-md blockquote {
  margin: .4em 0; padding-left: .5em;
  border-left: .16em solid var(--bb-st-accent, currentColor);
  opacity: .85; font-style: italic;
}

/* RSS — JS-driven auto-fit / paginate. The plugin renders all items, then
   either hides overflow (fit) or splits into pages (paginate). CSS keeps the
   per-item box generous so even on a small widget the text stays readable —
   if fewer items fit, fewer items show. Min font sizes are sized for legibility
   on a tablet held at arm's length; the cqmin scaling takes over on TVs. */
.bb-slide-rss {
  justify-content: flex-start;
  gap: clamp(6px, 1.6cqmin, 16px);
}
.bb-slide-rss .bb-h1 { font-size: clamp(16px, 5cqmin, 48px); margin: 0 0 .2em; }
.bb-slide-rss .bb-rss-list {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column;
  gap: clamp(6px, 1.8cqmin, 18px);
  flex: 1 1 auto; min-height: 0; overflow: hidden;
}
.bb-slide-rss .bb-rss-item {
  /* flex: 0 0 auto so the list doesn't shrink items to "fit" everything in;
     the JS-level auto-fit / paginate logic depends on items overflowing the
     list at their natural size so it can hide or paginate the overflow. */
  flex: 0 0 auto;
  padding: clamp(10px, 2.2cqmin, 20px) clamp(12px, 2.4cqmin, 22px);
  background: rgba(255,255,255,.06);
  border-radius: clamp(6px, 1.4cqmin, 14px);
  min-width: 0; overflow: hidden;
}
/* Font sizes multiply the auto-scaled clamp() by --bb-rss-text-scale (set by
   the plugin from the user's Text Size slider, 0.8–4.0). Default 1 keeps the
   auto-scaling untouched. */
.bb-slide-rss .bb-rss-title {
  font-weight: 700;
  font-size: calc(clamp(16px, 3.4cqmin, 48px) * var(--bb-rss-text-scale, 1));
  line-height: 1.25;
  margin-bottom: clamp(3px, .8cqmin, 8px);
  overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.bb-slide-rss .bb-rss-desc {
  font-size: calc(clamp(13px, 2.4cqmin, 32px) * var(--bb-rss-text-scale, 1));
  line-height: 1.4; opacity: .8;
  overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
/* User toggle override: explicitly hide descriptions regardless of widget
   size. Goes AFTER the container queries below so it wins on small widgets
   too (whether they would have been hidden automatically or not). */
.bb-slide-rss.bb-rss-no-desc .bb-rss-desc { display: none; }

/* Ticker mode — horizontal scrolling headlines. The viewport clips, the
   track holds two duplicated sequences and is animated by the @keyframes
   bb-ticker-scroll defined in JS (same keyframe as the Ticker plugin).
   Vertical padding uses cqh (height-based) instead of the slide's default
   3% (width-based) — a wide-but-short ticker bar otherwise loses all its
   inner height to the wide-percentage padding and clips the glyphs. */
.bb-slide-rss.bb-rss-mode-ticker {
  justify-content: center;
  align-items: stretch;
  padding: clamp(4px, 1.5cqh, 18px) 0;
}
.bb-slide-rss .bb-rss-ticker-viewport {
  width: 100%;
  overflow: hidden;
  display: flex;
  align-items: center;
  flex: 1 1 auto; min-height: 0;
}
.bb-slide-rss .bb-rss-ticker-track {
  display: inline-flex;
  white-space: nowrap;
  will-change: transform;
  font-weight: 700;
  font-size: calc(clamp(18px, 6cqh, 56px) * var(--bb-rss-text-scale, 1));
  line-height: 1.25;
}
.bb-slide-rss .bb-rss-ticker-item {
  padding: 0 1.2em;
}
.bb-slide-rss .bb-rss-ticker-sep {
  padding: 0 0.4em;
  color: var(--bb-st-accent, #8b5cf6);
  opacity: .7;
}
.bb-slide-rss .bb-rss-loading,
.bb-slide-rss .bb-rss-error {
  padding: clamp(8px, 2cqmin, 18px);
  font-size: clamp(13px, 2.2cqmin, 20px);
  opacity: .6;
}
.bb-slide-rss .bb-rss-error { color: #fbbf24; opacity: .85; }
.bb-slide-rss .bb-rss-dots {
  display: flex; justify-content: center; align-items: center;
  gap: clamp(4px, 1cqmin, 10px);
  margin-top: clamp(4px, 1cqmin, 10px);
  flex: 0 0 auto;
}
.bb-slide-rss .bb-rss-dot {
  width: clamp(5px, 1cqmin, 10px); height: clamp(5px, 1cqmin, 10px);
  border-radius: 50%;
  background: rgba(255,255,255,.25);
  transition: background .25s ease, transform .25s ease;
}
.bb-slide-rss .bb-rss-dot.bb-on {
  background: var(--bb-st-accent, #fff);
  transform: scale(1.25);
}
@container slide (max-width: 360px) {
  .bb-slide-rss .bb-rss-desc { display: none; }
  .bb-slide-rss .bb-rss-title { -webkit-line-clamp: 1; }
}
@container slide (max-height: 220px) {
  .bb-slide-rss .bb-rss-desc { display: none; }
}

/* Calendar — responsive via container queries.
   .bb-slide-calendar is a size container; cqmin-based typography scales with
   the WIDGET box (not the viewport), so a small widget on a big display gets
   sensibly sized fonts. Grid views fill the remaining height so the month
   never overflows a wide-but-short widget. */
.bb-slide-calendar {
  container-type: size; container-name: cal;
  justify-content: flex-start;
  padding: clamp(8px, 3%, 40px);
  gap: clamp(2px, 1%, 12px);
}
.bb-slide-calendar .bb-h1 { font-size: clamp(16px, 6cqmin, 56px); margin: 0 0 .2em; line-height: 1.15; }
.bb-slide-calendar .bb-h2 { font-size: clamp(13px, 3.6cqmin, 28px); margin: 0 0 .2em; line-height: 1.2; }
.bb-cal-view { flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; overflow: hidden; }
.bb-cal-loading, .bb-cal-empty-big { opacity: .6; padding: clamp(8px, 3cqmin, 24px); font-family: var(--bb-font, Inter, sans-serif); }
.bb-cal-empty-big { font-size: clamp(13px, 4cqmin, 32px); text-align: center; margin: auto; }

/* Agenda + Today — list */
.bb-cal-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: clamp(4px, 1.4cqmin, 12px); min-height: 0; overflow: hidden; }
.bb-cal-item {
  display: grid; grid-template-columns: auto 1fr; align-items: baseline;
  gap: clamp(8px, 2.4cqmin, 24px);
  padding: clamp(6px, 1.6cqmin, 14px) clamp(8px, 2cqmin, 18px);
  background: rgba(255,255,255,.06); border-radius: clamp(6px, 1.4cqmin, 12px);
  border-left: clamp(2px, .6cqmin, 4px) solid var(--bb-st-accent);
}
.bb-cal-date { font-family: var(--bb-mono); color: var(--bb-st-accent); font-size: clamp(11px, 2.4cqmin, 22px); font-variant-numeric: tabular-nums; white-space: nowrap; }
.bb-cal-desc { font-size: clamp(12px, 2.8cqmin, 26px); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@container cal (max-width: 360px) {
  .bb-cal-item { grid-template-columns: 1fr; gap: 2px; }
  .bb-cal-desc { white-space: normal; }
}

/* Now & Next (room) */
.bb-cal-nownext { display: flex; flex-direction: column; gap: clamp(6px, 2cqmin, 18px); justify-content: center; height: 100%; }
.bb-cal-status { align-self: flex-start; font-size: clamp(10px, 2.2cqmin, 20px); font-weight: 700; letter-spacing: .08em; text-transform: uppercase; padding: clamp(2px, .6cqmin, 6px) clamp(8px, 1.6cqmin, 16px); border-radius: 999px; }
.bb-cal-status.bb-busy { color: #fca5a5; background: rgba(239,68,68,.16); }
.bb-cal-status.bb-free { color: #86efac; background: rgba(16,185,129,.16); }
.bb-cal-now-label { font-size: clamp(10px, 1.8cqmin, 18px); text-transform: uppercase; letter-spacing: .14em; opacity: .6; }
.bb-cal-now-title { font: 800 clamp(22px, 12cqmin, 96px)/1.05 var(--bb-display); margin-top: 2px; overflow-wrap: anywhere; }
.bb-cal-free-big { color: var(--bb-st-accent); }
.bb-cal-now-time { font-size: clamp(12px, 3cqmin, 30px); opacity: .8; margin-top: 4px; font-variant-numeric: tabular-nums; }
.bb-cal-next { font-size: clamp(12px, 3cqmin, 28px); opacity: .85; margin-top: 4px; }
.bb-cal-next-label { color: var(--bb-st-accent); font-weight: 700; text-transform: uppercase; font-size: .68em; letter-spacing: .1em; margin-right: 8px; }
.bb-cal-next-none { opacity: .5; }

/* Week grid — fills available height; columns clip excess events */
.bb-cal-week { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: clamp(3px, 1cqmin, 10px); height: 100%; }
.bb-cal-col { display: flex; flex-direction: column; min-width: 0; min-height: 0; background: rgba(255,255,255,.05); border-radius: clamp(4px, 1cqmin, 12px); padding: clamp(4px, 1cqmin, 10px); overflow: hidden; }
.bb-cal-col.bb-today { background: color-mix(in srgb, var(--bb-st-accent) 14%, transparent); outline: 1px solid var(--bb-st-accent); }
.bb-cal-colhead { display: flex; flex-direction: column; align-items: center; margin-bottom: clamp(2px, 1cqmin, 8px); flex: 0 0 auto; }
.bb-cal-colhead span { font-size: clamp(9px, 1.4cqmin, 12px); text-transform: uppercase; letter-spacing: .08em; opacity: .6; }
.bb-cal-colhead b { font: 700 clamp(12px, 2.6cqmin, 24px) var(--bb-display); }
.bb-cal-colbody { display: flex; flex-direction: column; gap: clamp(2px, .6cqmin, 6px); min-height: 0; overflow: hidden; }
.bb-cal-ev { font-size: clamp(9px, 1.4cqmin, 14px); background: rgba(255,255,255,.07); border-left: 3px solid var(--bb-st-accent); border-radius: 4px; padding: clamp(1px, .4cqmin, 4px) clamp(3px, 1cqmin, 6px); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bb-cal-ev-time { font-variant-numeric: tabular-nums; opacity: .7; margin-right: 4px; }
.bb-cal-ev-none { opacity: .3; text-align: center; font-size: clamp(9px, 1.4cqmin, 12px); }
@container cal (max-width: 480px) { .bb-cal-ev-time { display: none; } }

/* Month grid — fills the box (no aspect-ratio overflow) */
.bb-cal-monthtitle { font: 700 clamp(13px, 3cqmin, 28px) var(--bb-display); margin: 0 0 clamp(4px, 1cqmin, 10px); color: var(--bb-st-accent); flex: 0 0 auto; }
.bb-cal-month { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); grid-template-rows: auto repeat(6, minmax(0, 1fr)); gap: clamp(3px, .8cqmin, 6px); flex: 1 1 auto; min-height: 0; }
.bb-cal-mhead { text-align: center; font-size: clamp(9px, 1.4cqmin, 12px); text-transform: uppercase; opacity: .5; letter-spacing: .06em; align-self: center; }
.bb-cal-daycell { background: rgba(255,255,255,.05); border-radius: clamp(4px, 1cqmin, 10px); padding: clamp(2px, .8cqmin, 6px) clamp(3px, 1cqmin, 8px); display: flex; flex-direction: column; gap: 2px; overflow: hidden; min-width: 0; min-height: 0; }
.bb-cal-daycell.bb-cal-out { opacity: .35; }
.bb-cal-daycell.bb-today { outline: 2px solid var(--bb-st-accent); background: color-mix(in srgb, var(--bb-st-accent) 14%, transparent); }
.bb-cal-daynum { font: 700 clamp(10px, 1.8cqmin, 16px) var(--bb-display); }
.bb-cal-mev { font-size: clamp(8px, 1.2cqmin, 12px); opacity: .85; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bb-cal-dots { display: flex; gap: 3px; margin-top: auto; }
.bb-cal-dot { width: clamp(3px, .7cqmin, 6px); height: clamp(3px, .7cqmin, 6px); border-radius: 50%; background: var(--bb-st-accent); }
@container cal (max-width: 360px) { .bb-cal-mev { display: none; } }
@container cal (max-height: 220px) { .bb-cal-monthtitle { display: none; } }

/* Clock — digital */
.bb-slide-clock { align-items: center; justify-content: center; text-align: center; }
.bb-slide-clock .bb-clock-label { font-size: clamp(14px, 2.6cqmin, 22px); letter-spacing: .2em; text-transform: uppercase; opacity: .6; }
.bb-slide-clock .bb-digital { font: 800 clamp(80px, 30cqmin, 280px)/1 var(--bb-mono); letter-spacing: .04em; font-variant-numeric: tabular-nums; margin: 16px 0; }
.bb-slide-clock .bb-clock-date { font-size: clamp(14px, 2.2cqmin, 22px); opacity: .7; }
/* Clock — analog */
.bb-clock-analog .bb-analog { width: clamp(180px, 38cqmin, 380px); height: clamp(180px, 38cqmin, 380px); margin: 24px auto; position: relative; }
.bb-clock-analog .bb-analog-face { position: absolute; inset: 0; border-radius: 50%; background: rgba(255,255,255,.04); border: 2px solid rgba(255,255,255,.15); }
.bb-clock-analog .bb-tick { position: absolute; left: 50%; top: 6px; width: 2px; height: 10px; background: rgba(255,255,255,.4); transform-origin: 50% calc(50% - 6px); transform: translateX(-50%) rotate(calc(var(--i) * 30deg)); }
.bb-clock-analog .bb-hand { position: absolute; left: 50%; bottom: 50%; transform-origin: bottom center; background: var(--bb-st-fg); border-radius: 2px; }
.bb-clock-analog .bb-hand-hour { width: 6px; height: 30%; margin-left: -3px; }
.bb-clock-analog .bb-hand-min  { width: 4px; height: 42%; margin-left: -2px; }
.bb-clock-analog .bb-hand-sec  { width: 2px; height: 46%; margin-left: -1px; background: var(--bb-st-accent); }
.bb-clock-analog .bb-analog-center { position: absolute; left: 50%; top: 50%; width: 14px; height: 14px; border-radius: 50%; background: var(--bb-st-accent); transform: translate(-50%, -50%); }

/* World clock */
.bb-slide-worldclock .bb-wc-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
.bb-wc-card { background: rgba(255,255,255,.06); border-radius: 16px; padding: 22px; text-align: center; }
.bb-wc-city { font-size: clamp(14px, 2.2cqmin, 22px); letter-spacing: .12em; text-transform: uppercase; opacity: .7; }
.bb-wc-time { font: 800 clamp(48px, 10cqmin, 96px)/1 var(--bb-mono); margin-top: 8px; font-variant-numeric: tabular-nums; }
.bb-wc-date { font-size: clamp(12px, 1.6cqmin, 22px); opacity: .6; margin-top: 4px; }

/* Countdown */
.bb-slide-countdown { align-items: center; text-align: center; }
.bb-cd-heading { font-size: clamp(20px, 3.2cqmin, 32px); opacity: .85; margin-bottom: 24px; }
.bb-cd-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
.bb-cd-grid > div { background: rgba(255,255,255,.06); border-radius: 16px; padding: 20px; min-width: 140px; }
.bb-cd-grid b { display: block; font: 800 clamp(48px, 13cqmin, 120px)/1 var(--bb-display); font-variant-numeric: tabular-nums; }
.bb-cd-grid span { display: block; font-size: clamp(12px, 1.8cqmin, 26px); text-transform: uppercase; letter-spacing: .12em; opacity: .6; margin-top: 4px; }

/* Quote */
.bb-slide-quote { align-items: center; justify-content: center; }
.bb-quote-card { display: flex; align-items: center; gap: 40px; max-width: 1100px; }
.bb-quote-portrait { width: 220px; height: 220px; border-radius: 50%; object-fit: cover; flex-shrink: 0; }
.bb-quote-mark { font: 900 clamp(48px, 10cqmin, 160px)/0.8 var(--bb-serif); color: var(--bb-st-accent); display: block; margin-bottom: 4px; }
.bb-quote-text blockquote { margin: 0; font: 600 clamp(24px, 5.6cqmin, 88px)/1.2 var(--bb-serif); }
.bb-quote-text cite { display: block; margin-top: 12px; opacity: .7; font-style: normal; font-size: clamp(14px, 2.4cqmin, 32px); }

/* Code */
/* Code is always a dark "terminal" box with its own light text, independent of
   the slide theme's --bb-st-fg. Without this, a light theme (editorial-mono sets
   --bb-st-fg:#1c1c1c) renders near-black code on a dark box = unreadable. */
.bb-slide-code .bb-code { background: #0c0c14; color: #e4e4e7; padding: 24px; border-radius: 12px; overflow: auto; font-size: clamp(14px, 2.4cqmin, 32px); }

/* KPI cards */
.bb-slide-kpi .bb-kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 18px; }
.bb-kpi-card { background: rgba(255,255,255,.06); border-radius: 16px; padding: 22px; }
.bb-kpi-label { font-size: clamp(12px, 1.6cqmin, 24px); text-transform: uppercase; letter-spacing: .14em; opacity: .6; }
.bb-kpi-value { font: 800 clamp(36px, 6.5cqmin, 120px)/1 var(--bb-display); margin-top: 6px; }
.bb-kpi-delta { font-size: clamp(14px, 1.8cqmin, 28px); opacity: .9; margin-top: 4px; }
.bb-kpi-up { color: #10b981; }
.bb-kpi-down { color: #ef4444; }
.bb-kpi-spark { margin-top: 8px; opacity: .8; }
.bb-kpi-target { margin-top: 10px; }
.bb-kpi-targetbar { height: 6px; border-radius: 3px; background: rgba(255,255,255,.1); overflow: hidden; }
.bb-kpi-targetfill { height: 100%; border-radius: inherit; transition: width .6s ease; }
.bb-kpi-targettext { margin-top: 4px; font-size: clamp(11px, 1.5cqmin, 22px); opacity: .65; }
.bb-quote-text cite .bb-quote-source { opacity: .65; font-style: italic; }

/* Chart */
.bb-slide-chart { align-items: center; }
.bb-chart-canvas { background: rgba(255,255,255,.02); border-radius: 12px; }

/* News with photos — JS-driven auto-fit / paginate (same pattern as RSS).
   Grid columns auto-fit based on widget width. Card layout switches from
   side-by-side (image left, text right) to stacked (image on top) on narrow
   cards via container queries on the slide. */
.bb-slide-news {
  justify-content: flex-start;
  gap: clamp(6px, 1.6cqmin, 16px);
}
.bb-slide-news .bb-h1 { font-size: clamp(16px, 5cqmin, 48px); margin: 0 0 .2em; }
.bb-slide-news .bb-news-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr));
  /* grid-auto-rows: max-content — without this, the grid silently splits its
     fixed height across all generated rows, squashing each card's row track
     to a fraction of the card's natural height and causing rows to overlap.
     With max-content, each row stays at its tallest card's height; excess
     rows overflow downward (hidden by overflow:hidden) so countFit can see
     them and either hide-or-paginate. */
  grid-auto-rows: max-content;
  gap: clamp(8px, 2cqmin, 18px);
  flex: 1 1 auto; min-height: 0;
  overflow: hidden;
  align-content: start;
}
.bb-news-card {
  display: grid;
  grid-template-columns: clamp(80px, 22%, 200px) 1fr;
  gap: clamp(8px, 2cqmin, 16px);
  background: rgba(255,255,255,.06);
  border-radius: clamp(8px, 1.4cqmin, 14px);
  overflow: hidden;
  padding: clamp(10px, 2cqmin, 18px);
  align-items: start;
  min-width: 0;
  /* overflow:hidden makes a card's children invisible to its min-content
     calculation. Without this floor, the grid is happy to squash 6 cards
     into a 300px box, which (a) clips every image and (b) defeats the
     auto-fit / paginate overflow detection. */
  min-height: max-content;
}
.bb-news-img {
  width: 100%;
  aspect-ratio: 4 / 3;
  background-size: cover;
  background-position: center;
  border-radius: clamp(4px, 1cqmin, 8px);
}
.bb-news-img-empty { background: rgba(255,255,255,.08); }
.bb-news-text { min-width: 0; }
.bb-news-card h3 {
  font-size: calc(clamp(15px, 2.8cqmin, 40px) * var(--bb-news-text-scale, 1));
  line-height: 1.25;
  margin: 0 0 clamp(3px, .8cqmin, 8px);
  font-weight: 700;
  overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}
.bb-news-card p {
  font-size: calc(clamp(12px, 2cqmin, 26px) * var(--bb-news-text-scale, 1));
  line-height: 1.4; opacity: .8;
  margin: 0;
  overflow: hidden;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
}
.bb-slide-news.bb-news-no-desc .bb-news-card p { display: none; }
.bb-slide-news .bb-news-loading,
.bb-slide-news .bb-news-error {
  padding: clamp(8px, 2cqmin, 18px);
  font-size: clamp(13px, 2.2cqmin, 20px);
  opacity: .6;
}
.bb-slide-news .bb-news-error { color: #fbbf24; opacity: .85; }
.bb-slide-news .bb-news-dots {
  display: flex; justify-content: center; align-items: center;
  gap: clamp(4px, 1cqmin, 10px);
  margin-top: clamp(4px, 1cqmin, 10px);
  flex: 0 0 auto;
}
.bb-slide-news .bb-news-dot {
  width: clamp(5px, 1cqmin, 10px); height: clamp(5px, 1cqmin, 10px);
  border-radius: 50%;
  background: rgba(255,255,255,.25);
  transition: background .25s ease, transform .25s ease;
}
.bb-slide-news .bb-news-dot.bb-on {
  background: var(--bb-st-accent, #fff);
  transform: scale(1.25);
}
/* Narrow slides (vertical 9:16 displays, tiny widgets): stack image on top
   so headlines aren't squeezed into a 40px sliver next to a fixed image. */
@container slide (max-width: 480px) {
  .bb-news-card { grid-template-columns: 1fr; }
  .bb-news-img { aspect-ratio: 16 / 9; }
}

/* Menu / pricelist */
.bb-slide-menu .bb-menu-cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)); gap: 32px; }
.bb-slide-menu .bb-menu-section h2 { color: var(--bb-st-accent); font-size: clamp(20px, 3.6cqmin, 52px); }
.bb-slide-menu ul { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; gap: 14px; }
.bb-slide-menu .bb-menu-row { display: flex; align-items: baseline; gap: 8px; font-size: clamp(16px, 2.8cqmin, 40px); }
.bb-slide-menu .bb-menu-name { font-weight: 600; }
.bb-slide-menu .bb-menu-dots { flex: 1; border-bottom: 1px dotted rgba(255,255,255,.3); height: .8em; }
.bb-slide-menu .bb-menu-price { font-variant-numeric: tabular-nums; }
.bb-slide-menu .bb-menu-desc { font-size: clamp(12px, 1.9cqmin, 24px); opacity: .7; margin-top: 2px; }
/* Per-item layout: optional thumbnail on the left, name+price row + desc on the right */
.bb-slide-menu li { display: flex; gap: 12px; align-items: flex-start; }
.bb-slide-menu .bb-menu-body { flex: 1; min-width: 0; }
.bb-slide-menu .bb-menu-thumb {
  width: clamp(40px, 6cqmin, 72px); height: clamp(40px, 6cqmin, 72px);
  flex-shrink: 0;
  border-radius: 8px;
  background-color: rgba(255,255,255,0.05);
  overflow: hidden;
}
.bb-slide-menu .bb-menu-thumb > img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}
/* Broken thumb: hide so the menu row doesn't show a forever-blank square */
.bb-slide-menu .bb-menu-thumb.bb-menu-thumb-broken { display: none; }
/* Allergen / dietary chips — small badge after the dish name */
.bb-slide-menu .bb-menu-tags { display: inline-flex; gap: 4px; margin-left: 8px; vertical-align: middle; }
.bb-slide-menu .bb-menu-tag {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 1.4em; padding: .15em .5em;
  font: 700 .55em/1.4 var(--bb-font, Inter, sans-serif);
  border-radius: 4px;
  letter-spacing: .04em;
  text-transform: uppercase;
}
/* Sold-out: dim + strikethrough the name; add a small label */
.bb-slide-menu .bb-menu-sold .bb-menu-name,
.bb-slide-menu .bb-menu-sold .bb-menu-price,
.bb-slide-menu .bb-menu-sold .bb-menu-desc { opacity: .45; text-decoration: line-through; text-decoration-thickness: 1px; }
.bb-slide-menu .bb-menu-sold .bb-menu-tag { opacity: .45; }
.bb-slide-menu .bb-menu-soldlbl {
  display: inline-block; margin-left: 8px;
  padding: .15em .5em;
  font: 700 .55em/1.4 var(--bb-font, Inter, sans-serif);
  background: #b91c1c; color: #fff; border-radius: 4px;
  letter-spacing: .04em; text-transform: uppercase;
  text-decoration: none; opacity: 1;
}
.bb-slide-menu .bb-menu-sold .bb-menu-soldlbl { opacity: 1; } /* override the row dim */
/* Featured / today's special — accent-coloured star + soft background */
.bb-slide-menu .bb-menu-featured {
  background: color-mix(in srgb, var(--bb-st-accent) 8%, transparent);
  border-radius: 8px; padding: 6px 8px; margin: -6px -8px;
}
.bb-slide-menu .bb-menu-star { color: var(--bb-st-accent); margin-right: 4px; }

/* QR */
.bb-slide-qr { align-items: center; text-align: center; }
/* `background-color` is set inline from the widget's `bgColor` so the card
   visually matches the QR's own quiet zone (qrserver `bgcolor`). Same for
   `.bb-qr-caption color` — driven by `fgColor` so it stays readable. */
.bb-qr-card { padding: 24px; border-radius: 16px; box-shadow: 0 20px 60px rgba(0,0,0,.4); display: inline-flex; flex-direction: column; align-items: center; gap: 12px; }
.bb-qr-card.bb-qr-frameless { padding: 0; border-radius: 0; box-shadow: none; background: transparent !important; }
.bb-qr-img { display: block; max-width: 90cqw; max-height: 90cqh; }
.bb-qr-frameless .bb-qr-img { max-width: 100cqw; max-height: 100cqh; }
.bb-qr-caption { font-weight: 600; font-size: clamp(14px, 2.2cqmin, 20px); }

/* Currency */
.bb-slide-fx .bb-fx-base { font-size: clamp(16px, 2.6cqmin, 22px); opacity: .6; margin-bottom: 12px; }
.bb-slide-fx .bb-fx-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.bb-fx-cell { background: rgba(255,255,255,.06); border-radius: 12px; padding: 16px; text-align: center; }
.bb-fx-sym { display: block; font-size: clamp(12px, 1.6cqmin, 22px); opacity: .6; letter-spacing: .1em; }
.bb-fx-rate { display: block; font: 800 clamp(28px, 5cqmin, 48px)/1 var(--bb-mono); margin-top: 4px; font-variant-numeric: tabular-nums; }

/* Live JSON */
.bb-slide-livejson .bb-json { font-family: var(--bb-mono); font-size: clamp(12px, 2.2cqmin, 26px); white-space: pre-wrap; padding: 24px; background: rgba(0,0,0,.4); border-radius: 12px; overflow: auto; max-height: 90cqh; }
.bb-j-k { color: #c4b5fd; }
.bb-j-s { color: #86efac; }
.bb-j-num { color: #fbbf24; }
.bb-j-b { color: #f472b6; }
.bb-j-n { color: #94a3b8; }
.bb-json-error { padding: 24px; display: flex; flex-direction: column; gap: 8px; color: #fbbf24; font-family: var(--bb-font, Inter, sans-serif); }
.bb-json-error-title { font-size: clamp(16px, 2.8cqmin, 22px); font-weight: 700; }
.bb-json-error-msg   { font-family: var(--bb-mono); font-size: 13px; opacity: .8; word-break: break-all; }
.bb-json-error-hint  { font-size: clamp(13px, 1.8cqmin, 16px); opacity: .85; max-width: 60ch; }

/* Data table */
.bb-slide-datatable .bb-dt-wrap { width: 100%; overflow: auto; }
.bb-slide-datatable .bb-dt { width: 100%; border-collapse: collapse; font-size: clamp(14px, 2.8cqmin, 44px); }
.bb-slide-datatable .bb-dt th { text-align: left; padding: 10px 16px; color: var(--bb-st-accent); text-transform: uppercase; letter-spacing: .06em; font-size: .8em; border-bottom: 2px solid var(--bb-st-accent); }
.bb-slide-datatable .bb-dt td { padding: 10px 16px; border-bottom: 1px solid rgba(255,255,255,.1); }
.bb-slide-datatable .bb-dt tbody tr:nth-child(even) { background: rgba(255,255,255,.04); }
.bb-slide-datatable .bb-dt-empty { opacity: .55; padding: 24px; font-family: var(--bb-font, Inter, sans-serif); }

/* Ticker — already a size container via .bb-slide above; redundant declaration kept for clarity */
.bb-slide-ticker { container-type: size; }

/* Progress / goal — note: .bb-prog-bar and .bb-prog-ring are scoped to descendants
   because the plugin also puts these classes on the slide root as state markers.
   Without scoping, the rules below would override .bb-slide's sizing and turn
   the entire slide into a pill bar (the bar variant) or display:block container. */
.bb-prog-label { font-size: clamp(16px, 3.6cqmin, 52px); opacity: .85; text-align: center; }
.bb-slide-progress .bb-prog-bar { width: 86%; height: min(48px, 12cqh); min-height: 16px; background: rgba(255,255,255,.1); border-radius: 999px; overflow: hidden; }
.bb-prog-fill { height: 100%; border-radius: 999px; min-width: 2%; transition: width .6s cubic-bezier(.22,1,.36,1); }
.bb-prog-value { font: 700 clamp(16px, 4cqmin, 40px) var(--bb-display); font-variant-numeric: tabular-nums; }
.bb-prog-ringwrap { position: relative; width: min(60cqw, 60cqh); height: min(60cqw, 60cqh); }
.bb-slide-progress .bb-prog-ring { width: 100%; height: 100%; display: block; }
.bb-prog-ringtext { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; }
.bb-prog-pct { font: 800 min(20cqw, 20cqh) var(--bb-display); line-height: 1; font-variant-numeric: tabular-nums; }
.bb-prog-sub { font-size: min(7cqw, 7cqh); opacity: .7; font-variant-numeric: tabular-nums; }

/* Days since */
.bb-slide-dayssince { text-align: center; }
.bb-ds-heading { font-size: min(8cqw, 12cqh); text-transform: uppercase; letter-spacing: .1em; opacity: .75; }
.bb-ds-count { font: 800 min(42cqw, 58cqh) var(--bb-display); line-height: 1; color: var(--bb-st-accent); font-variant-numeric: tabular-nums; }
.bb-ds-unit { font: 700 min(8cqw, 12cqh) var(--bb-display); text-transform: uppercase; letter-spacing: .12em; opacity: .8; }
.bb-ds-date { font-size: min(5cqw, 7cqh); opacity: .6; margin-top: .4em; }
.bb-ds-empty { opacity: .5; padding: 24px; font-family: var(--bb-font, Inter, sans-serif); }

/* Image title overlay */
.bb-image-title { position: absolute; left: 0; right: 0; bottom: 0; padding: 32px 48px; background: linear-gradient(180deg, transparent, rgba(0,0,0,.6)); color: #fff; font: 800 clamp(20px, 5cqmin, 48px)/1.2 var(--bb-display); }

/* ============================================================
   Widget animations — entrance "builds" + ambient loops.
   Loaded by BOTH display.html (player) and index.html (editor); the publish
   bundler inlines this file, so published players ship these too. Toggled via
   the helpers in shared/animations.js (`.bb-wa-*` = build, `.bb-loop-*` = loop).
   ============================================================ */

/* --- Entrance builds (one-shot, transition-driven) ---
   primeBuild() puts the element in the hidden state below; revealBuild() adds
   `.bb-wa-in` to transition it to visible. Duration comes from --bb-wa-dur
   (set per widget); delay is scheduled in JS so it survives cleanup cleanly. */
.bb-wa {
  transition:
    opacity   var(--bb-wa-dur, .6s) cubic-bezier(.22, 1, .36, 1),
    transform var(--bb-wa-dur, .6s) cubic-bezier(.22, 1, .36, 1),
    filter    var(--bb-wa-dur, .6s) ease,
    clip-path var(--bb-wa-dur, .6s) cubic-bezier(.65, 0, .35, 1);
  will-change: opacity, transform, filter, clip-path;
}
/* hidden (initial) states */
.bb-wa-fade       { opacity: 0; }
.bb-wa-fade-up    { opacity: 0; transform: translateY(9%); }
.bb-wa-fade-down  { opacity: 0; transform: translateY(-9%); }
.bb-wa-fade-left  { opacity: 0; transform: translateX(9%); }   /* enters from the right */
.bb-wa-fade-right { opacity: 0; transform: translateX(-9%); }  /* enters from the left */
.bb-wa-scale      { opacity: 0; transform: scale(.82); }       /* pop in */
.bb-wa-zoom       { opacity: 0; transform: scale(1.14); }      /* settle from large */
.bb-wa-reveal     { clip-path: inset(0 100% 0 0); }            /* wipe left → right */
.bb-wa-blur       { opacity: 0; filter: blur(14px); }
.bb-wa-rise       { opacity: 0; transform: translateY(13%) scale(.97); }
/* revealed (visible) state — one rule covers every transform/opacity/filter build */
.bb-wa-fade.bb-wa-in,
.bb-wa-fade-up.bb-wa-in,   .bb-wa-fade-down.bb-wa-in,
.bb-wa-fade-left.bb-wa-in, .bb-wa-fade-right.bb-wa-in,
.bb-wa-scale.bb-wa-in,     .bb-wa-zoom.bb-wa-in,
.bb-wa-blur.bb-wa-in,      .bb-wa-rise.bb-wa-in {
  opacity: 1; transform: none; filter: none;
}
.bb-wa-reveal.bb-wa-in { clip-path: inset(0 0 0 0); }

/* --- Ambient loops (continuous, infinite) --- */
@keyframes bb-loop-float    { 0%, 100% { transform: translateY(0); }        50% { transform: translateY(-2.2%); } }
@keyframes bb-loop-pulse    { 0%, 100% { transform: scale(1); }             50% { transform: scale(1.035); } }
@keyframes bb-loop-sway     { 0%, 100% { transform: rotate(-1.1deg); }      50% { transform: rotate(1.1deg); } }
@keyframes bb-loop-kenburns { 0% { transform: scale(1) translate(0, 0); }   100% { transform: scale(1.12) translate(1.6%, -1.6%); } }
@keyframes bb-loop-glow     { 0%, 100% { filter: brightness(1); }           50% { filter: brightness(1.18); } }
@keyframes bb-loop-spin     { to { transform: rotate(360deg); } }
.bb-loop { will-change: transform, filter; }
.bb-loop-float    { animation: bb-loop-float 4.5s ease-in-out infinite; }
.bb-loop-pulse    { animation: bb-loop-pulse 3s ease-in-out infinite; transform-origin: center; }
.bb-loop-sway     { animation: bb-loop-sway 5s ease-in-out infinite; transform-origin: center; }
.bb-loop-kenburns { animation: bb-loop-kenburns 18s ease-in-out infinite alternate; transform-origin: center; }
.bb-loop-glow     { animation: bb-loop-glow 3.2s ease-in-out infinite; }
.bb-loop-spin     { animation: bb-loop-spin 14s linear infinite; transform-origin: center; }

/* Respect the OS "reduce motion" setting on the player too (the editor is also
   covered by the global guard in components.css). Builds snap straight to
   visible; loops hold still; reveal must never leave content clipped away. */
@media (prefers-reduced-motion: reduce) {
  .bb-wa { transition-duration: .001ms !important; }
  .bb-wa-reveal, .bb-wa-reveal.bb-wa-in { clip-path: none !important; }
  .bb-loop-float, .bb-loop-pulse, .bb-loop-sway,
  .bb-loop-kenburns, .bb-loop-glow, .bb-loop-spin { animation: none !important; }
}
