/* ====================================================================
   AIR WIDGETS — WIDGET UI
   ====================================================================

   Every rule that draws a widget mockup. Split out of components.css on
   2026-09-02 because THIS THEME IS SHARED — Air Fill runs it today and the other
   Air Pack apps will — and none of them render widget mockups. They should not
   pay for these bytes or inherit these selectors.

   Enqueued as `kkb-widgets` only where widget markup actually appears; the
   predicate is `kkb_has_widget_ui()` in functions.php and it is filterable, so
   another site can turn it on for a page we didn't anticipate, or off entirely.

   TWO SYSTEMS, sized not serial:
     .wgmock / .wgal   SMALL, square. The widget gallery (template-parts/widget-gallery.php)
                       and the per-feature mockup on feature singles.
     .wmock  / .wphone MEDIUM, 2:1. The showcase phones (kkb_widget_tile / kkb_widget_phone).
   Medium is a different composition, not a bigger small: it has a header glyph, a
   footnote (rule 8 bans one at small only), and room for a second element beside
   the hero. See `xcode/AirWidgets/Air Widgets -- Widget Visual Vocabulary.md`.

   LOAD ORDER: this file loads AFTER controls.css, so the gallery's compound
   selectors (.control-card__demo.wgmock) no longer need to out-specify anything —
   they are kept because they are harmless and self-documenting, but a plain
   .wgmock would now win on its own.
   ==================================================================== */

/* ============================================================
   Air Widgets — stylized widget mockups for the showcase stages.
   Illustrative sample data (like the old flow-panel), not screenshots.
   Uses theme tokens; adapts to showcase--dark bands.
   ============================================================ */
/* Widget mockups render in Apple's system fonts so they read as real Home Screen widgets:
   SF Pro (system-ui / -apple-system) for text, and SF Pro Rounded (ui-rounded) for numeric data —
   the rounded numerals Apple uses across Fitness / Activity. Both fall back to the platform sans. */
:root{
  --w-font: -apple-system, BlinkMacSystemFont, system-ui, "Segoe UI", sans-serif;
  --w-font-rounded: ui-rounded, "SF Pro Rounded", -apple-system, system-ui, sans-serif;
}
.wmock{display:flex;align-items:center;justify-content:center;padding:var(--wp--preset--spacing--40,1.5rem);}

/* The tile. 2:1 because that IS a medium widget — the app renders medium at 1086x546,
   and these sit at Home-Screen width inside the showcase phones.

   ONE SCALE KNOB. Every inner metric is calc(x * var(--wm-s)). The featured tile renders
   at real (enlarged) width rather than transform:scale, so its fixed metrics have to be
   multiplied to keep the proportions the other two have — that used to be ~20 duplicated
   `.wphone__w--feature` rules, one per element, which is how new elements silently ended
   up unscaled. Set --wm-s in one place instead.

   These tiles are ALWAYS LIGHT (they sit on a Home Screen wallpaper), so there are no
   .showcase--dark variants. Adding one back would repeat the white-on-white bug where a
   dark-band text rule applied to a paper tile. */
.wmock__tile{
  --wm-s:1;
  display:flex;flex-direction:column;
  width:min(360px,100%);aspect-ratio:2/1;box-sizing:border-box;
  background:var(--wp--preset--color--paper);
  border:1px solid var(--wp--preset--color--line);
  color:var(--wp--preset--color--ink);
  border-radius:calc(var(--ios-radius-md)*var(--wm-s));
  box-shadow:var(--ios-elevation-2);
  padding:calc(.62rem*var(--wm-s)) calc(.8rem*var(--wm-s));
  overflow:hidden;
  font-family:var(--w-font);font-variant-numeric:tabular-nums;-webkit-font-smoothing:antialiased;
}

/* Header — a tinted glyph beside the title. Medium has this; small does not. */
.wmock__hd{display:flex;align-items:center;gap:calc(.3rem*var(--wm-s));margin:0 0 calc(.4rem*var(--wm-s));}
.wmock__icon{flex:none;inline-size:calc(.72rem*var(--wm-s));block-size:calc(.72rem*var(--wm-s));color:var(--wp--preset--color--gold);}
.wmock__title{font-weight:600;font-size:calc(.68rem*var(--wm-s));letter-spacing:-.01em;}
/* Footnote, bottom-trailing. Rule 8 bans this at SMALL only; medium shows it. */
.wmock__foot{margin:calc(.3rem*var(--wm-s)) 0 0;text-align:end;font-size:calc(.54rem*var(--wm-s));color:var(--wp--preset--color--muted);}
/* Numerals in SF Rounded; everything else is not — all-rounded is the loudest tell. */
.wmring__val,.wmstats__n,.wprog__v,.wmledger__day,.wmledger__peak,.wmledger__ax,.wmlist__time{font-family:var(--w-font-rounded);}

/* --- Progress: ring on the leading edge, readout beside it. Large is not a Medium with
       more air — the extra width buys a second thing, so the surplus goes to the readout
       rather than to a bigger gap. --- */
.wprog{flex:1 1 auto;display:flex;align-items:center;gap:calc(.7rem*var(--wm-s));min-height:0;}
.wmring{position:relative;flex:none;width:calc(66px*var(--wm-s));height:calc(66px*var(--wm-s));}
.wmring__svg{display:block;width:100%;height:100%;transform:rotate(-90deg);}
/* Track is a step of the tint off the SURFACE, not a transparent wash — a 20% tint over a
   dark tile resolves to mud. Mirrors WidgetPalette.track. */
.wmring__track{fill:none;stroke:color-mix(in srgb,var(--wp--preset--color--gold) 26%,var(--wp--preset--color--paper));stroke-width:4.2;}
.wmring__fill{fill:none;stroke:var(--wp--preset--color--gold);stroke-width:4.2;stroke-linecap:round;stroke-dasharray:100;stroke-dashoffset:calc(100 - var(--pct));}
.wmring__val{position:absolute;inset:0;display:grid;place-items:center;font-size:calc(.92rem*var(--wm-s));font-weight:700;letter-spacing:-.02em;}
.wprog__rows{flex:1;min-width:0;display:grid;gap:calc(.1rem*var(--wm-s));}
.wprog__row{display:flex;align-items:baseline;justify-content:space-between;gap:calc(.5rem*var(--wm-s));}
.wprog__k{font-size:calc(.6rem*var(--wm-s));color:var(--wp--preset--color--muted);}
.wprog__v{font-size:calc(.66rem*var(--wm-s));font-weight:700;}

/* --- Chart, Ledger direction: a value axis, columns standing ON the floor over a
       recessive SOLID grid, strided day labels, one direct label on the peak. ONE hue for
       every column — length already encodes magnitude, so a per-column palette would be a
       third channel saying nothing, and it would fight the widget's own tint. --- */
.wmledger{flex:1 1 auto;display:flex;gap:calc(.38rem*var(--wm-s));min-height:0;}
/* MARGIN, not padding, on both the axis and the plot. A bar's block-size:var(--h) is a
   percentage of the CONTENT box while a gridline's inset-block-start:var(--t) resolves
   against the PADDING box — so bottom padding silently pushed every gridline below the
   value its own axis label named. With margin the two boxes coincide, and the day labels
   (absolutely positioned under each bar) hang into the reserved strip. */
.wmledger__ax{display:flex;flex-direction:column;justify-content:space-between;font-size:calc(.5rem*var(--wm-s));line-height:1;color:var(--wp--preset--color--muted);text-align:end;margin-block-end:calc(.7rem*var(--wm-s));}
.wmledger__plot{position:relative;flex:1;min-width:0;display:flex;align-items:flex-end;justify-content:space-between;gap:calc(.26rem*var(--wm-s));margin-block-end:calc(.7rem*var(--wm-s));}
.wmledger__grid{position:absolute;inset-inline:0;inset-block-start:var(--t);block-size:1px;background:var(--wp--preset--color--line);z-index:0;}
.wmledger__col{position:relative;z-index:1;flex:1 1 0;max-inline-size:calc(18px*var(--wm-s));block-size:var(--h);border-radius:calc(2.6px*var(--wm-s)) calc(2.6px*var(--wm-s)) 0 0;background:var(--wp--preset--color--gold);}
.wmledger__day{position:absolute;inset-block-start:100%;inset-inline:0;padding-block-start:calc(.13rem*var(--wm-s));font-style:normal;font-size:calc(.48rem*var(--wm-s));text-align:center;color:var(--wp--preset--color--muted);}
.wmledger__peak{position:absolute;inset-block-end:100%;inset-inline:50%;transform:translateX(-50%);padding-block-end:calc(.12rem*var(--wm-s));font-size:calc(.5rem*var(--wm-s));font-weight:700;line-height:1;color:var(--wp--preset--color--muted);}

/* --- Multi-Number: ONE row of hairline-divided cells, left-aligned, NOT boxed cells.
       Value:label ratio 2.2x — four figures at 1.3x read as four paragraphs of small
       print. (This block used to give each cell a sand fill and a radius, which is the
       boxed treatment the vocabulary rules out by name.) --- */
.wmstats{flex:1 1 auto;display:grid;grid-template-columns:repeat(4,minmax(0,1fr));align-content:center;}
.wmstats__cell{display:grid;gap:1px;padding:0 calc(.4rem*var(--wm-s));min-width:0;}
.wmstats__cell:first-child{padding-inline-start:0;}
.wmstats__cell+.cell{border-inline-start:1px solid var(--wp--preset--color--line);}
.wmstats__n{font-size:calc(1rem*var(--wm-s));font-weight:700;line-height:1;letter-spacing:-.02em;}
.wmstats__k{font-size:calc(.46rem*var(--wm-s));line-height:1.15;color:var(--wp--preset--color--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}

/* --- Compact List: rows DIVIDE the height, plain muted trailing values. --- */
.wmlist{flex:1 1 auto;list-style:none;margin:0;padding:0;display:grid;grid-auto-rows:1fr;}
.wmlist__row{display:flex;align-items:center;gap:calc(.5rem*var(--wm-s));min-width:0;border-block-start:1px solid var(--wp--preset--color--line);}
.wmlist__row:first-child{border-block-start:0;}
.wmlist__t{flex:1;min-width:0;font-weight:600;font-size:calc(.62rem*var(--wm-s));white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.wmlist__time{flex:none;font-weight:600;font-size:calc(.58rem*var(--wm-s));color:var(--wp--preset--color--muted);}


/* ====================================================================
   Air Widgets — showcase "widget phone". A life-size iPhone Home Screen
   (reuses .device-video sizing + the frame PNG) showing three widgets +
   the suite app dock. The section's own widget is FEATURED: it renders
   above the frame so, once revealed, it grows and overflows past the
   device edges while the two others (behind the frame) blur + dim.

   States (one-time reveal, driven by .is-revealed from motion.js):
     load / .wphone:not(.is-revealed)  → all three EQUAL, sharp, no blur
     revealed / base                   → feature enlarged + lifted out,
                                          others blurred + dimmed (regular size)
   Base CSS is the END state so no-JS / reduced-motion still shows the design.
   The reveal is a native CSS transition (parallel across the elements).
   Wallpaper is a PLACEHOLDER image (.wphone__wall) — swap the files in
   assets/img/wallpapers/ later.
   ==================================================================== */
.wphone{ --feature-scale:1.3; }   /* min-width flip below — base is the small value */
/* On real phones the device goes ~full-width, so ease the overflow scale a touch. */
@media (min-width:600px){ .wphone{ --feature-scale:1.5; } }
/* the screen cutout — clips the wallpaper, the two side widgets, and the dock */
.wphone__screen{
  position:absolute; z-index:0;
  inset-block:var(--dv-inset-y); inset-inline:var(--dv-inset-x);
  width:90%;
  border-radius:var(--dv-radius);
  overflow:hidden;
  background:var(--wp--preset--color--ink);
  container-type:inline-size; /* so the dock can size in cqw = % of the screen width */
  /* Half the clear space between the featured widget and each neighbour, as a share of
     screen height. Raise to push the two dimmed widgets apart, lower to close them in. */
  --wphone-gap:12.5%;
}
.wphone__wall{ position:absolute; inset:0; width:100%; height:100%; object-fit:cover; z-index:0; }

/* The two non-featured widgets sit on the Home Screen, behind the frame bezel.
   Spacing follows real iOS: ~16px clear of the dynamic island (top) and the dock. */
.wphone__w--above,
.wphone__w--below{
  position:absolute; z-index:1;
  /* Width comes from the DOCK's own expression, not a separate percentage, so the two can
     never drift apart — a Home Screen widget and the dock share the same side margins. */
  inset-inline:2.5cqw;
  width:auto;
}
/* Placed SYMMETRICALLY about the screen's vertical centre, which is also where the featured
   widget centres. Positioning one from the top and the other from the bottom (13.5% / 17%)
   made the gap above the feature 68px and the gap below it 44px; anchoring both to the same
   centre makes the two gaps equal by construction, at any --feature-scale. */
.wphone__w--above{ inset-block:auto calc(50% + var(--wphone-gap)); }
/* Anchored from the BOTTOM so a tall widget can never slide under the dock; it grows
   upward (behind the featured) instead. ~16px+ above the dock (see .wphone__dock). */
.wphone__w--below{ inset-block:calc(50% + var(--wphone-gap)) auto; }

/* The featured widget lives OUTSIDE the screen (above the frame) so it can overflow. The
   anchor fills the device and flex-centres the widget at its ZOOMED size. */
.wphone__feat{
  position:absolute; inset:0; z-index:5;
  display:flex; align-items:center; justify-content:center;
  pointer-events:none;
}
.wphone__w--feature{
  /* flex-shrink:0 so the flex line doesn't shrink this (108%-of-device) widget back to fit — it
     must overflow the frame width, centred, as before. */
  flex:0 0 auto;
  /* Enlarge by REAL width, so the browser lays out and rasterises the widget at its final size and
     it stays crisp — card edge, text, ring, bars. NOT transform:scale() (upscales a composited
     bitmap → the whole widget blurs; will-change/translateZ only make that worse) and NOT zoom
     (a flex line negates the percentage-width magnification). The fixed inner metrics are scaled
     to match below. The reveal animates ONLY the transform, from a downscale up to 1 —
     downscaling a crisp raster stays sharp. */
  width: calc(72% * var(--feature-scale));
  transform-origin:center;
  transform:scale(1);
}

/* Reveal transitions — ONE smooth motion, only *subtly* sequenced top→bottom (the tiny
   delays overlap almost entirely, so it doesn't read as three steps). No reverse.
   Downplay = blur + lower lightness (no opacity change). */
.wphone__w--above{  transition:filter var(--ios-duration-slow) var(--ios-ease-out) 0s; }
.wphone__w--feature{ transition:transform var(--ios-duration-slow) var(--ios-ease-out) .05s; }
.wphone__w--below{  transition:filter var(--ios-duration-slow) var(--ios-ease-out) .1s; }
.wphone__w--feature .wmock__tile{ transition:box-shadow var(--ios-duration-slow) var(--ios-ease-out) .05s; }
/* END state (base) — the two others blur + darken at regular size (no shrink, no opacity). */
.wphone__w--dim{ filter:blur(3px) brightness(.6); }
/* Featured lift shadow grows in the END state. */
.wphone__w--feature .wmock__tile{ box-shadow:0 34px 60px -28px rgba(20,19,26,.62); }

/* INITIAL state (on load, before reveal) — all three equal, sharp, no blur. Counter the zoom
   with a matching downscale so the featured widget starts at the same size as the others. */
.wphone:not(.is-revealed) .wphone__w--feature{ transform:scale(calc(1 / var(--feature-scale))); }
.wphone:not(.is-revealed) .wphone__w--feature .wmock__tile{ box-shadow:var(--ios-elevation-2); }
.wphone:not(.is-revealed) .wphone__w--dim{ filter:none; }

/* iOS-style dock — spans the inner screen edge-to-edge minus ~8px, sits ~8px off the
   bottom, with the three suite apps distributed (space-around). Offsets/sizes are cqw
   (% of the screen width) so ~8px holds across breakpoints. */
.wphone__dock{
  position: absolute;
  inset-inline: 2.5cqw;
  bottom: 2.5cqw;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--wp--preset--spacing--30);
  padding:3.5cqw 2cqw;
  border-radius:11cqw;   /* ~22px */
  /* The blur/saturate come from the iOS material tokens, but the tint does NOT use
     --ios-material-light (72% paper): an iOS dock over a dark wallpaper is a *faint* light
     glass, and 72% renders a white bar. Mixed from the palette token at the strength this
     was designed at, so the value is tokenized without changing the design. */
  background:color-mix(in srgb, var(--wp--preset--color--paper) 18%, transparent);
  -webkit-backdrop-filter:blur(var(--ios-material-blur)) saturate(var(--ios-material-saturate)); backdrop-filter:blur(var(--ios-material-blur)) saturate(var(--ios-material-saturate));
  border:1px solid color-mix(in srgb, var(--wp--preset--color--paper) 14%, transparent);
}
.wphone__app{ flex:0 0 auto; width:18cqw; aspect-ratio:1; }
.wphone__app img{ width:100%; height:100%; border-radius:var(--ios-radius-icon); display:block; box-shadow:0 6px 14px -8px color-mix(in srgb, var(--wp--preset--color--ink) 62%, transparent); }

/* Home-Screen-sized tiles inside the phone. Everything about a tile's internals is
   expressed as calc(x * var(--wm-s)) in the .wmock__tile block above, so this layer only
   sets width and the scale knob — there is no per-element duplication to keep in step.

   The featured tile renders at real (enlarged) width instead of transform:scale, so it is
   painted at true resolution and stays crisp; --feature-scale multiplies its inner metrics
   so the proportions match the two behind it. */
.wphone .wmock__tile{ width:100%; --wm-s:1; }
.wphone .wphone__w--feature .wmock__tile{ --wm-s:var(--feature-scale); }

@media (prefers-reduced-motion: reduce){
  .wphone__w--feature, .wphone__w--above, .wphone__w--below,
  .wphone__w--feature .wmock__tile{ transition:none; }
}

/* ==================================================================== */
/*  Air Widgets — widget gallery                                        */
/*  A grid of mini iOS-widget mockups (template-parts/widget-gallery).  */
/*  Each .wgmock is a themed surface: it carries .control-card__demo, so */
/*  the .scheme-toggle recolours it via the shared --demo-* token set.   */
/*  Everything inside paints ONLY from --demo-* (+ --demo-success, added */
/*  below for the green→gold gauge / status dot / delta — a scheme-      */
/*  stable positive green, mirroring controls.css's use of --success).   */
/*  Compound selectors (.control-card__demo.wgmock) intentionally beat   */
/*  controls.css's single-class base, which loads after this file.       */
/* ==================================================================== */

/* Responsive grid — 1 col on the smallest phones, 2 from 480, 3 tablet, 4 desktop. */
.wgal {
  list-style: none;
  margin: 0 auto;
  padding: 0;
  max-width: var(--wp--style--global--wide-size, 1200px);
  display: grid;
  /* Row gap well clear of .wgal__tile's internal gap (spacing--20), so a caption visibly
     belongs to the widget above it rather than floating between two. Most acute at one
     column, where every gap becomes the page's vertical rhythm. */
  column-gap: var(--wp--preset--spacing--40);
  row-gap: var(--wp--preset--spacing--60);
  align-items: start;
  /* minmax(0,1fr), never 1fr: a bare 1fr floors at min-content, and a tile holding
     a nowrap figure or a hairline stat grid then refuses to shrink, pushing the whole
     page into a horizontal scroll on a phone. */
  grid-template-columns: 1fr;
}
/* Two-up only once a tile can hold its type: below ~480px a half-width tile lands near
   128px, and the smallest labels (6.6cqi) fall to ~8px. One column instead. */
@media (min-width: 480px)  { .wgal { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 900px)  { .wgal { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (min-width: 1200px) { .wgal { grid-template-columns: repeat(4, minmax(0, 1fr)); } }

/* The feature-single hero variant: one widget (the type the page is about) or a
   representative trio for the cross-cutting features. Capped so a single tile reads as a
   widget rather than stretching to the column width. */
.wgal--feature { max-width: 22rem; }
.wgal--feature:has(> :nth-child(2)) { max-width: 34rem; }
@media (min-width: 900px) { .wgal--feature { grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); } }

.wgal__tile {
  display: flex;
  flex-direction: column;
  gap: var(--wp--preset--spacing--20);
  /* THE query container. It must sit on the WRAPPER, not on .wgmock: an element
     cannot query its own size, so cqi used on .wgmock itself would silently resolve
     against the viewport instead — which is exactly the bug this replaced (7cqi
     padding computing to 53px on a 340px tile, swallowing every composition's
     layout and making all thirteen look centred). */
  container-type: inline-size;
}
.wgal__meta { display: grid; gap: var(--wp--preset--spacing--10, 0.25rem); }
.wgal__name {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--wp--preset--spacing--20);
  font-weight: 800;
  font-size: var(--wp--preset--font-size--lg);
  color: var(--wp--preset--color--ink);
}
.wgal__cap { color: var(--wp--preset--color--muted); font-size: var(--wp--preset--font-size--base); line-height: 1.4; }

/* Extra demo tokens the gallery needs, scheme-aware like controls.css's set.
   --demo-track  a lighter step of the tint (a gauge/ring track). Derived from the
                 SURFACE, not a transparent wash: a 20% tint over a dark tile resolves
                 to mud. This mirrors WidgetPalette.track, which is computed per scheme
                 for exactly that reason.
   --demo-wash   the gradient wash beneath a plot. LIGHT TAKES 14%, DARK 22% — a wash
                 darkens a white tile where it lightens a dark one, so the same opacity
                 does not do the same work (Visual Vocabulary, element 1).
   --demo-success a scheme-stable positive green. Dark #43C88A is too light on white,
                 so light uses the deeper step, matching WidgetPalette. */
.wgal .control-card__demo {
  /* The deeper step, because #6cc58f on paper is 2.09:1 — a WCAG AA fail for the delta,
     which is TEXT. The dark override below restores the lighter one, which is correct there. */
  --demo-success: var(--wp--preset--color--success-deep);
  --demo-track: color-mix(in srgb, var(--demo-accent) 30%, var(--demo-surface));
  --demo-wash: 14%;
  --demo-bar: 11%;
  --demo-grid: var(--demo-line);
}
[data-demo-scope]:has(.scheme-toggle__radio[value="dark"]:checked) .wgal .control-card__demo {
  --demo-track: color-mix(in srgb, var(--demo-accent) 42%, var(--demo-surface));
  --demo-wash: 22%;
  --demo-success: var(--wp--preset--color--success);
  /* Both of these are their own decision, not a flip. A wash that reads as ground on
     white composites into a visible block on a dark tile, and --demo-line sits ABOVE
     the dark surface, so an empty heatmap day would pop instead of recede. */
  --demo-bar: 7%;
  --demo-grid: color-mix(in srgb, var(--demo-line) 45%, var(--demo-surface));
}
@media (prefers-color-scheme: dark) {
  [data-demo-scope]:has(.scheme-toggle__radio[value="auto"]:checked) .wgal .control-card__demo {
    --demo-track: color-mix(in srgb, var(--demo-accent) 42%, var(--demo-surface));
    --demo-wash: 22%;
    --demo-success: var(--wp--preset--color--success);
    --demo-bar: 7%;
    --demo-grid: color-mix(in srgb, var(--demo-line) 45%, var(--demo-surface));
  }
}

/* ---------------------------------------------------------------------
   The shell. It owns the frame and NOTHING about the composition.

   Two rules from the Visual Vocabulary outrank everything below:
   1. No two widget types share a layout. Apple's widgets share tokens,
      never compositions — one shell stamped thirteen ways IS the generic
      look. So the shell sets no gap and no alignment; each type declares
      its own, and every .wg-* block below is deliberately different.
   2. The void is CAPPED and the surplus belongs to an element. A bare
      auto-margin takes ALL remaining height; .wgmock__void caps it and
      lets the element that should grow do the growing.

   Square by design: an iOS small widget is square (the app renders it at
   546x546), and container units mean every composition holds its
   proportions whether the tile lands at 160px on a phone or 280px on a
   desktop — the same job WidgetMetrics does in points.
   --------------------------------------------------------------------- */
.control-card__demo.wgmock {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  align-content: start;
  gap: 0;
  /* Explicit, not inherited from the global reset: aspect-ratio resolves against
     whichever box box-sizing names, so under content-box the --floor and --bleed
     tiles stop being square (they lose or gain exactly the padding they withhold). */
  box-sizing: border-box;
  aspect-ratio: 1;
  min-height: 0;
  padding: 7cqi;
  border: 1px solid var(--demo-line);
  border-radius: var(--ios-radius-lg);
  background: var(--demo-surface);
  box-shadow: var(--ios-elevation-1);
  overflow: hidden;
  font-family: var(--w-font);
  font-variant-numeric: tabular-nums;
  -webkit-font-smoothing: antialiased;
}
/* Numerals in SF Rounded; everything else is not. All-rounded is the loudest
   "not drawn by Apple" tell (Visual Vocabulary, rule 4). */
.wgmock__hero, .wnums__n, .wring__val, .wgauge__val, .wcd__n, .wdelta, .wrank__v, .wledger__pk, .wheat__n, .wlist__time, .wcmp__v {
  font-family: var(--w-font-rounded);
  letter-spacing: -0.02em;
}
.control-card__demo.wgmock--center { align-items: center; justify-content: center; }

/* floorsContent — the shell WITHHOLDS its bottom inset so a trend line, columns
   or a rail sits ON the floor. Never a child clawing it back with a negative
   margin: that shrinks the child's layout slot while it still draws full height,
   so the overflow lands outside the frame. */
.control-card__demo.wgmock--floor { padding-block-end: 0; }
/* ...and its leading inset too, for a plot that bleeds off both edges. Children
   that are not the plot re-state their own inline padding. */
.control-card__demo.wgmock--bleed { padding-inline: 0; padding-block-end: 0; }
.wgmock--bleed > :not(.wbleed) { padding-inline: 7cqi; }

/* Header: label leading, accessory trailing. The shell owns this corner — a delta
   belongs in the header, not in a row of its own restating a corner that exists. */
.wgmock__hd { display: flex; align-items: flex-start; justify-content: space-between; gap: 4cqi; }
.wgmock__lbl { font-weight: 600; font-size: 7cqi; line-height: 1.2; color: var(--demo-muted); }
.wgmock__hero { font-weight: 700; font-size: 21cqi; line-height: 0.92; letter-spacing: -0.03em; color: var(--demo-text); }
/* Rule 2b — the void is capped and the surplus belongs to an element — is satisfied here by
   giving the growing element `flex: 1 1 auto` (see .wnums, .wledger, .wlist, .wrank), not by a
   spacer. A .wgmock__void helper existed and was used by nothing; a documented mechanism that
   isn't wired is worse than neither, so it is gone. Reinstate it only if a tile needs it. */

/* Delta pill. Arrow and figure both in the STATE colour, which is independent of
   the widget tint — a decline reads red on a green widget. At this size the fill
   comes off: the arrow and the colour already carry it. */
.wdelta { display: inline-flex; align-items: center; gap: 0.15em; flex: none; font-weight: 700; font-size: 7cqi; color: var(--demo-success); }
.wdelta__a { font-size: 0.85em; }

/* ---- 1 · Record Count — label top, figure on the floor, nothing else.
        A count has no trend, so it gets the plainest composition in the set. */
.wg-count { justify-content: space-between; }
.wg-count .wgmock__hero { font-size: 25cqi; }

/* ---- 2 · Number Summary — label + delta in the header, hero, and the series
        bleeding off the leading and bottom edges. The plot IS the tile. */
.wg-num { justify-content: space-between; }
.wspark { position: relative; display: block; width: 100%; height: 34cqi; }
.wspark__svg { display: block; width: 100%; height: 100%; }
/* No `fill` here: the gradient id is generated per render (wp_unique_id) and set inline on
   the element, so any hardcoded url(#…) would be a dangling reference that only looks correct
   because the inline style wins. */
.wspark__area { stroke: none; }
.wspark__line { fill: none; stroke: var(--demo-accent); stroke-width: 2.4; stroke-linecap: round; stroke-linejoin: round; vector-effect: non-scaling-stroke; }
.wspark__g0 { stop-color: var(--demo-accent); stop-opacity: var(--demo-wash); }
.wspark__g1 { stop-color: var(--demo-accent); stop-opacity: 0; }
/* End marker, drawn at MARKER size not stroke width — at stroke width it reads as a
   kink in the line rather than as "you are here". Inset from the trailing edge by
   more than half its own width, or the tile's corner radius eats half of it. */
.wspark__end {
  position: absolute; inset-inline-start: var(--x); inset-block-start: var(--y);
  inline-size: 4.4cqi; block-size: 4.4cqi; border-radius: 50%;
  background: var(--demo-accent); transform: translate(-50%, -50%);
}

/* Composition hooks with no rules of their own — the type's layout is fully carried by its
   inner block (.wring, .wgauge, .wnums, .wledger, .wlist, .wrank). Declared so every entry in
   $kkb_widgets has a hook that exists, rather than half the set being live and half inert. */
.wg-ring, .wg-gauge, .wg-multi, .wg-chart, .wg-list, .wg-rank { }

/* ---- 3 · Progress Ring — the ring, and nothing under it. Reads 0-100%. */
.wring { position: relative; inline-size: 62cqi; block-size: 62cqi; }
.wring__svg { display: block; width: 100%; height: 100%; transform: rotate(-90deg); }
.wring__track { fill: none; stroke: var(--demo-track); stroke-width: 4.2; }
.wring__fill { fill: none; stroke: var(--demo-accent); stroke-width: 4.2; stroke-linecap: round; stroke-dasharray: 100; stroke-dashoffset: calc(100 - var(--pct)); }
.wring__val { position: absolute; inset: 0; display: grid; place-items: center; font-weight: 700; font-size: 15cqi; color: var(--demo-text); }

/* ---- 4 · Gauge — an open dial, and the one place a goal is a POSITION rather
        than a length. The scale RESCALES past the target so the goal lands inside
        the sweep with visible distance beyond it; without that the tick sits on
        the arc's end cap, where it is redundant and then invisible under the fill. */
.wgauge { position: relative; inline-size: 72cqi; block-size: 72cqi; }
.wgauge__svg { display: block; width: 100%; height: 100%; }
.wgauge__track { fill: none; stroke: var(--demo-track); stroke-width: 4.2; stroke-linecap: round; }
.wgauge__fill { fill: none; stroke: var(--demo-accent); stroke-width: 4.2; stroke-linecap: round; stroke-dasharray: 100; stroke-dashoffset: calc(100 - var(--pct)); }
/* The only dashed line a widget may draw, so nothing else can be mistaken for the
   goal. Drawn proud on both sides so a 4/4 dash stays legible across the band. */
/* SOLID, not dashed. The "only dashed line a widget may draw" is the target RULE on a
   chart, where it spans a length; on a gauge the goal is a POSITION and the mark is a tick
   across the band. At this size a 2/2 dash across a ~7px crossing renders as two grey
   blocks that read as a rendering fault. The surface colour cuts a clean notch and shows
   against the accent arc in both schemes. */
.wgauge__goal { stroke: var(--demo-surface); stroke-width: 2; stroke-linecap: butt; }
.wgauge__val { position: absolute; inset: 0; display: grid; place-items: center; font-weight: 700; font-size: 13cqi; color: var(--demo-text); }

/* ---- 5 · Multi-Number — a hairline-divided stat grid, left-aligned, NOT boxed
        cells. Value:label ratio is 2.2x: four figures at 1.3x read as four
        paragraphs of small print. */
.wnums { flex: 1 1 auto; display: grid; grid-template-columns: 1fr 1fr; align-content: stretch; }
.wnums__cell { display: grid; align-content: center; gap: 0.5cqi; padding: 3cqi 4cqi; min-width: 0; }
.wnums__cell:nth-child(2n+1) { padding-inline-start: 0; }
.wnums__cell:nth-child(2n) { border-inline-start: 1px solid var(--demo-line); }
.wnums__cell:nth-child(n+3) { border-block-start: 1px solid var(--demo-line); }
.wnums__n { font-weight: 700; font-size: 15.4cqi; line-height: 1; color: var(--demo-text); }
.wnums__k { font-weight: 500; font-size: 7cqi; line-height: 1.15; color: var(--demo-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---- 6 · Chart — the Ledger direction: columns standing on the floor with a
        recessive grid. The only direction that answers a quantitative question,
        and deliberately not the line the Number tile already draws. */
.wledger { position: relative; flex: 1 1 auto; display: flex; align-items: flex-end; justify-content: space-between; gap: 2.4cqi; margin-block-start: 4cqi; padding-block-start: 9cqi; }
/* Recessive grid: hairline and SOLID, one step off the surface, so the dashed
   target rule stays the only dashed thing on a tile. */
.wledger__grid { position: absolute; z-index: 0; inset-inline: 0; inset-block-start: var(--t); block-size: 1px; background: var(--demo-line); }
.wledger__col { position: relative; z-index: 1; flex: 1 1 0; max-inline-size: 7cqi; block-size: var(--h); border-radius: 1.6cqi 1.6cqi 0 0; background: var(--demo-accent); }
/* Direct labels are SELECTIVE, never one per mark — eight labels across a small
   tile is an unreadable band. Label the peak only. */
.wledger__pk { position: absolute; z-index: 2; inset-block-end: 100%; inset-inline: 50%; transform: translateX(-50%); padding-block-end: 1.4cqi; font-weight: 700; font-size: 6.6cqi; line-height: 1; color: var(--demo-muted); }

/* ---- 7 · Compact List — rows DIVIDE the height rather than stacking at the top,
        with plain muted trailing values. */
.wlist { flex: 1 1 auto; list-style: none; margin: 0; padding: 0; display: grid; grid-auto-rows: 1fr; }
.wlist__row { display: flex; align-items: center; gap: 3cqi; min-width: 0; border-block-start: 1px solid var(--demo-line); }
.wlist__row:first-child { border-block-start: 0; }
.wlist__t { flex: 1; min-width: 0; font-weight: 600; font-size: 7.4cqi; color: var(--demo-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wlist__time { flex: none; font-weight: 600; font-size: 7.4cqi; color: var(--demo-muted); }

/* ---- 8 · Countdown — always draws its title, a floored figure whose numerals
        take the TINT (the one hero in the set that does), and an edge rail. */
.wg-cd { justify-content: space-between; }
.wcd { display: flex; align-items: baseline; gap: 3cqi; }
/* --demo-accent-strong, not --demo-accent: gold on paper is 1.98:1, and even at 26cqi
   large text needs 3:1. controls.css defines the strong step as "AA on the light stage"
   and re-brightens it to plain gold for dark, so this is right in both. */
.wcd__n { font-weight: 700; font-size: 26cqi; line-height: 0.9; letter-spacing: -0.03em; color: var(--demo-accent-strong); }
.wcd__u { font-weight: 600; font-size: 7cqi; line-height: 1.15; color: var(--demo-muted); white-space: nowrap; }
.wcd__u b { display: block; font-weight: 600; color: var(--demo-text); }
.wcd__rail { display: block; margin-block-start: 5cqi; block-size: 2.6cqi; border-radius: 1.3cqi 1.3cqi 0 0; background: var(--demo-track); overflow: hidden; }
.wcd__rail > i { display: block; block-size: 100%; inline-size: var(--p); border-radius: inherit; background: var(--demo-accent); }

/* ---- 9 · Status — the record on top, the state on the floor. The GLYPH carries
        the state colour; the text never wears the data colour (rule 7). */
.wg-status { justify-content: space-between; }
.wstatus { display: flex; align-items: center; gap: 3.4cqi; }
.wstatus__dot { display: block; flex: none; inline-size: 9cqi; block-size: 9cqi; color: var(--demo-success); }
.wstatus__t { font-weight: 700; font-size: 11.5cqi; letter-spacing: -0.02em; color: var(--demo-text); }

/* ---- 10 · Comparison — "now" owns the floor, "then" sits beside it in the
        trailing corner, and the delta rides under the value it describes. */
.wg-cmp { justify-content: space-between; }
.wcmp { display: flex; align-items: flex-end; justify-content: space-between; gap: 4cqi; }
.wcmp__now, .wcmp__then { display: grid; gap: 0.8cqi; min-width: 0; justify-items: start; }
.wcmp__then { justify-items: end; }
.wcmp__then { text-align: end; }
.wcmp__v { font-weight: 700; font-size: 14cqi; line-height: 1; color: var(--demo-text); white-space: nowrap; }
.wcmp__now, .wcmp__then { overflow: hidden; }
.wcmp__then .wcmp__v { font-size: 9.5cqi; color: var(--demo-muted); }
.wcmp__k { font-weight: 600; font-size: 6.6cqi; color: var(--demo-muted); }

/* ---- 11 · Leaderboard — the magnitude bar sits BEHIND the row, not beside it:
        the rank, the name and the figure are the content, and a bar competing for
        width costs the name its characters. It is a wash, so it reads as ground. */
.wrank { flex: 1 1 auto; list-style: none; margin: 0; padding: 0; display: grid; grid-auto-rows: 1fr; align-content: stretch; }
.wrank__row { position: relative; display: flex; align-items: center; gap: 3cqi; min-width: 0; padding-inline: 2cqi; }
.wrank__row::before {
  content: ""; position: absolute; inset-block: 1.8cqi; inset-inline-start: 0;
  inline-size: var(--m); border-radius: 1.4cqi;
  background: color-mix(in srgb, var(--demo-accent) var(--demo-bar), transparent);
}
/* The "Others" roll-up gets no bar — a remainder is not a competitor, and giving it
   one would place it in the ranking it is explicitly outside of. */
.wrank__row--rest::before { content: none; }
.wrank__n { position: relative; flex: none; inline-size: 5cqi; font-weight: 700; font-size: 7cqi; color: var(--demo-muted); }
.wrank__t { position: relative; flex: 1; min-width: 0; font-weight: 600; font-size: 7.4cqi; color: var(--demo-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wrank__v { position: relative; flex: none; font-weight: 700; font-size: 7.4cqi; color: var(--demo-text); }

/* ---- 12 · Heatmap — the grid on the floor, with the readout in the corner. */
.wg-heat { justify-content: space-between; }
/* flex + align-content, not a bare grid: the surplus belongs to an element (rule 2b),
   and a top-anchored grid over a floor of dead space is the exact defect the app's own
   vocabulary doc still lists as owed on the Heatmap's Large tile. Centre it instead. */
.wheat { flex: 1 1 auto; display: grid; align-content: center; grid-template-columns: repeat(13, 1fr); gap: 1.1cqi; }
.wheat > span { --l: 0%; aspect-ratio: 1; border-radius: 0.9cqi; background: color-mix(in srgb, var(--demo-accent) var(--l), var(--demo-grid)); }
.wheat__n { align-self: stretch; text-align: end; font-weight: 600; font-size: 6.8cqi; color: var(--demo-muted); }

/* ---- 13 · Text Snippet — the app's "note" display style (plain / quote / note are real
        options on this mode): the text is the object, set on a washed card with a leading
        tint rail, and the record it came from takes the floor. Deliberately NOT
        label-top/value-floor — that shell already carries Record Count and Status, and a
        third copy of it is the generic look rule 1 rules out. */
.wg-snip { justify-content: space-between; }
.wsnip {
  position: relative;
  flex: 1 1 auto;
  display: flex;
  gap: 2.6cqi;
  padding: 4cqi 4cqi 4cqi 5.5cqi;
  border-radius: 3.5cqi;
  background: color-mix(in srgb, var(--demo-accent) 9%, transparent);
}
/* The rail is element 5 read vertically — it grows from the leading edge and marks the
   quote as a quoted thing rather than decorating it. */
.wsnip::before {
  content: "";
  position: absolute;
  inset-block: 4cqi;
  inset-inline-start: 0;
  inline-size: 1.6cqi;
  border-radius: 999px;
  background: var(--demo-accent);
}
.wsnip__q { flex: none; font-size: 13cqi; line-height: 0.9; color: var(--demo-accent-strong); }
.wsnip__t { font-weight: 600; font-size: 8cqi; line-height: 1.3; color: var(--demo-text); }
.wsnip__src { font-weight: 600; font-size: 6.6cqi; color: var(--demo-muted); }

/* No reduced-motion rule here on purpose: motion.js bails out on prefers-reduced-motion
   before it animates anything, and GSAP does not use CSS transitions, so a `transition: none`
   here would be inert. (The previous one was doubly inert — it targeted a DESCENDANT of
   [data-reveal-group], and that attribute sits on .wgal itself.) */
