/* ═══════════════════════════════════════════════════════════════════
   HeaderUsageMeters.jsx — persistent top-bar usage-meter widget (OL-054)
   Session: 2026-07-02 PM-2
   ═══════════════════════════════════════════════════════════════════
   Compact top-bar pill (worst-meter % + color state) → click opens a
   popover with all four per-cost-type meters (AI Actions · Image Ops ·
   Doc Ops · SEO/GEO Data), each rendering:
     · floor progress bar (use-or-lose monthly floor)
     · pack-credits-remaining line (non-expiring — distinct visual, so
       the Consumption-Order Rule floor→pack→overage is VISIBLE)
     · days-left-in-cycle
     · pack_cta button (routes to Billing)
     · auto-refill toggle (opt-in framing — never pre-checked; reflects
       the server's real state, which defaults OFF)

   ── LOCKED contract (work-order 2026-07-02 PM-2; api lane builds in
      parallel) ────────────────────────────────────────────────────────
     GET  {apiBase}/api/usage/meters   →
       {
         cycle_days_left, resets_at,
         meters: [{
           key, label,
           floor_used,
           included_floor,               // LIVE api#121 name (floor_limit tolerated);
                                         //   null ⇒ no allowance (inert Nerve / not on plan)
           pack_remaining,               // non-expiring pack/add-on credits
           byok_exempt,                  // true → AI-cost portion is "your key"
           hard_stopped,                 // true → meter is hard-stopped
           auto_refill,                  // server's current opt-in state
           days_left,                    // optional per-meter override
           pack_cta: { label, tab },     // upsell target
           sub_caps: [{ label, used, limit }]  // infra caps (always shown)
         }, ...]
       }
     Field names are read defensively (snake/camel tolerated) so a rename
     can't silently blank the surface.

   ── Honest states (Radical Transparency §3 #27 — no false zeros) ──────
     · endpoint 404 / absent (api round not merged yet) → render NOTHING.
     · fetch error                                       → render NOTHING.
     · loading                                           → skeleton pill,
       never a fabricated 0%.
     · included_floor: null (inert Nerve / not on plan)  → "No allowance on
       your plan yet" — never 0/0, never a bar, never NaN%.
     · byok_exempt                                       → "Unlimited — your
       key"; any pack credits shown as PAUSED (non-spendable), not decrementing.

   ── Role completeness (work-order top-bar rule) ──────────────────────
     Rendered by Topbar (Shell.jsx) ONLY when the effective VIEW role
     (s.role) is 'customer' or 'internal_partner'. A real customer /
     internal_partner sees it directly; every staff persona sees it ONLY
     inside an SA "View as customer/partner" account-context view (View-As
     sets s.role to the account role — so the same s.role gate covers both
     the customer path AND the SA account-context path). Staff own-views
     (super_admin, admin, dev, support, marketing staff) → hidden. CSS-only
     colors (var(--ok)/var(--warn)/var(--danger) with token fallbacks) —
     never hardcoded semantic hex (§3 #5).

   ── Analytics (§6 #32) ───────────────────────────────────────────────
     usage.meters.viewed (dotted KNOWN_EVENT_TYPES key, api main post-#122)
     fires once per
     popover-open to the customer-scoped ingest POST /client/events/track
     (account_id/user_id from the JWT, fire-and-forget, never blocks UI).

   Load order: after Toggle.jsx (reused) + Icon.jsx; before Shell.jsx.
   Registers: window.HeaderUsageMeters (Topbar consumer).
   ═══════════════════════════════════════════════════════════════════ */
(function () {
  'use strict';
  if (typeof window === 'undefined' || !window.React) return;

  const { useState, useEffect, useRef, useCallback } = React;

  const tx = (k, v, f) => (window.WPSB && window.WPSB.t) ? window.WPSB.t(k, v, f) : (f != null ? f : k);

  /* LOCKED path per work-order. apiBase is set at boot (index.html); the
     hardcoded origin is the belt-and-suspenders fallback (never resolve to
     the app's own origin — that Vercel-404s). */
  const METERS_PATH = '/api/usage/meters';
  function apiBase() {
    return (window.WPSBD && window.WPSBD.apiBase)
      || (window.WPSB_CONFIG && window.WPSB_CONFIG.RAILWAY_URL)
      || window.RAILWAY_URL
      || 'https://api.wpsitebeam.io';
  }
  function authToken() {
    return (window.WPSB && window.WPSB.getToken && window.WPSB.getToken())
      || (window.WPSBD && window.WPSBD.getToken && window.WPSBD.getToken())
      || null;
  }
  function switchTab(tab) {
    if (window.WPSBD && window.WPSBD.switchTab) window.WPSBD.switchTab(tab || 'billing');
  }

  /* Customer-scoped client analytics ingest — account_id/user_id come from
     the JWT server-side; NEVER sent in the body. Fire-and-forget: any
     failure is swallowed so analytics can never block or break the surface.
     Targets /client/events/track (NOT the SA-only /events/track). */
  function emitClientEvent(eventType, props) {
    try {
      const token = authToken(); if (!token) return;
      fetch(apiBase() + '/client/events/track', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
        body: JSON.stringify({ event_type: eventType, props: props || {} }),
      }).catch(function () {});
    } catch (_) { /* swallow */ }
  }

  /* ── defensive readers (snake/camel tolerant) ────────────────────── */
  function pick(o, keys, dflt) {
    for (let i = 0; i < keys.length; i++) {
      if (o && o[keys[i]] != null) return o[keys[i]];
    }
    return dflt === undefined ? null : dflt;
  }
  function num(v) { const n = Number(v); return Number.isFinite(n) ? n : null; }

  function readMeter(m) {
    return {
      key:          pick(m, ['key', 'id', 'meter'], ''),
      label:        pick(m, ['label', 'name', 'title'], tx('page.usagemeters.meter_fallback', null, 'Meter')),
      floorUsed:    num(pick(m, ['floor_used', 'floorUsed', 'used'])),
      /* included_floor is the LIVE api#121 field (returns null while a Nerve —
         e.g. seo_geo — is inert / not on the plan). Null floor ⇒ no-allowance
         state, never 0/0 or a NaN% bar. */
      floorLimit:   num(pick(m, ['included_floor', 'includedFloor', 'floor_limit', 'floorLimit', 'limit'])),
      packRemaining:num(pick(m, ['pack_remaining', 'packRemaining', 'pack_credits_remaining', 'packCreditsRemaining'])),
      byokExempt:   !!pick(m, ['byok_exempt', 'byokExempt'], false),
      hardStopped:  !!pick(m, ['hard_stopped', 'hardStopped'], false),
      autoRefill:   !!pick(m, ['auto_refill', 'autoRefill'], false),
      daysLeft:     num(pick(m, ['days_left', 'daysLeft'])),
      packCta:      pick(m, ['pack_cta', 'packCta'], null),
      subCaps:      (pick(m, ['sub_caps', 'subCaps'], []) || []).map(function (s) {
        return {
          label: pick(s, ['label', 'name'], tx('page.usagemeters.cap_fallback', null, 'Cap')),
          used:  num(pick(s, ['used', 'value'])),
          limit: num(pick(s, ['limit', 'max'])),
        };
      }),
    };
  }

  /* Percentage a meter fills its floor (0..100). Null floor → null. */
  function floorPct(mm) {
    if (mm.floorLimit == null || mm.floorLimit <= 0 || mm.floorUsed == null) return null;
    return Math.max(0, Math.min(100, Math.round((mm.floorUsed / mm.floorLimit) * 100)));
  }
  /* Worst sub-cap pressure for a meter (infra caps still bind under BYOK). */
  function subCapPct(mm) {
    let worst = null;
    (mm.subCaps || []).forEach(function (s) {
      if (s.limit == null || s.limit <= 0 || s.used == null) return;
      const p = Math.max(0, Math.min(100, Math.round((s.used / s.limit) * 100)));
      worst = worst == null ? p : Math.max(worst, p);
    });
    return worst;
  }
  /* A meter's overall pressure for the pill: hard-stop pins 100; a
     BYOK-exempt meter contributes only its infra sub-caps (AI portion is
     unlimited); otherwise the floor %. */
  function meterPressure(mm) {
    if (mm.hardStopped) return 100;
    if (mm.byokExempt) { const s = subCapPct(mm); return s == null ? 0 : s; }
    const f = floorPct(mm);
    if (f == null) { const s = subCapPct(mm); return s == null ? 0 : s; }
    const s = subCapPct(mm);
    return s == null ? f : Math.max(f, s);
  }

  /* No plan allowance: floor is null/absent (e.g. included_floor:null for an
     inert Nerve like seo_geo) AND no pack credits. Render an honest
     "no allowance" line — never 0/0, never a bar, never NaN%. A BYOK-exempt
     meter is "unlimited", not "no allowance". */
  function hasNoAllowance(mm) {
    return !mm.byokExempt && mm.floorLimit == null
      && !(mm.packRemaining != null && mm.packRemaining > 0);
  }

  /* State → color token (CSS vars only; fallbacks because --ok/--danger are
     used-with-fallback elsewhere in the app and not base-defined). */
  function toneFor(pct, hardStopped) {
    if (hardStopped || pct >= 90) return 'var(--danger, var(--red))';
    if (pct >= 75) return 'var(--warn)';
    return 'var(--ok, var(--green))';
  }
  function toneTintFor(pct, hardStopped) {
    if (hardStopped || pct >= 90) return 'rgba(255,112,112,.12)';
    if (pct >= 75) return 'rgba(245,184,0,.12)';
    return 'rgba(0,217,122,.10)';
  }

  /* ── small bits ──────────────────────────────────────────────────── */
  function Bar({ pct, tone, height }) {
    const h = height || 6;
    return (
      <div style={{ height: h, borderRadius: 999, background: 'rgba(0,0,0,.15)', overflow: 'hidden' }}>
        <div style={{ height: '100%', width: (pct == null ? 0 : pct) + '%', background: tone,
                      borderRadius: 999, transition: 'width .35s ease' }} />
      </div>
    );
  }

  function MeterRow({ mm, onCta, onAutoRefill }) {
    const pct = floorPct(mm);
    const press = meterPressure(mm);
    const tone = toneFor(press, mm.hardStopped);
    const cta = mm.packCta;
    const ctaLabel = cta && (cta.label || cta.cta) || tx('page.usagemeters.buy_a_pack', null, 'Buy a pack');
    const ctaTab = cta && (cta.tab || cta.target) || 'billing';

    return (
      <div style={{ padding: '12px 14px', borderTop: '1px solid var(--border)' }}>
        {/* header line: label + state chip */}
        <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 8 }}>
          <span style={{ fontWeight: 600, fontSize: '.82rem', color: 'var(--text)', flex: 1, minWidth: 0 }}>
            {mm.label}
          </span>
          {mm.hardStopped ? (
            <span style={{ fontSize: '.64rem', fontWeight: 700, letterSpacing: '.03em',
                           padding: '2px 7px', borderRadius: 999,
                           background: 'rgba(255,112,112,.14)', color: 'var(--danger, var(--red))' }}>
              {tx('page.usagemeters.limit_reached', null, 'LIMIT REACHED')}
            </span>
          ) : mm.byokExempt ? (
            <span style={{ fontSize: '.64rem', fontWeight: 700, letterSpacing: '.03em',
                           padding: '2px 7px', borderRadius: 999,
                           background: 'var(--beam-dim, rgba(0,207,239,.12))', color: 'var(--beam)' }}
                  title={tx('page.usagemeters.your_key_title', null, 'AI cost billed to your own API key')}>
              {tx('page.usagemeters.your_key', null, 'YOUR KEY')}
            </span>
          ) : pct != null ? (
            <span style={{ fontSize: '.72rem', fontWeight: 600, color: tone, fontFamily: 'var(--font-mono)' }}>
              {pct}%
            </span>
          ) : null}
        </div>

        {/* floor line: use-or-lose monthly allowance. Cycle days-left lives once
            in the popover header, not repeated per meter. */}
        {mm.byokExempt ? (
          <div style={{ fontSize: '.76rem', color: 'var(--beam)', fontWeight: 500, marginBottom: 6 }}>
            {tx('page.usagemeters.unlimited_your_key', null, 'Unlimited — your key')}
          </div>
        ) : pct != null ? (
          <>
            <Bar pct={pct} tone={tone} />
            <div style={{ marginTop: 5, fontSize: '.7rem', color: 'var(--dim)' }}>
              <strong style={{ color: 'var(--text-2)' }}>{(mm.floorUsed || 0).toLocaleString()}</strong>
              {tx('page.usagemeters.floor_suffix', { limit: (mm.floorLimit || 0).toLocaleString() }, ' / {limit} monthly')}
            </div>
          </>
        ) : (
          /* included_floor:null (inert Nerve / not on plan) — honest no-allowance
             state, never 0/0 or a bar (Radical Transparency §3 #27). */
          <div style={{ fontSize: '.74rem', color: 'var(--dim)' }}
               title={tx('page.usagemeters.no_allowance_title', null, "This meter isn't included on your current plan yet.")}>
            {tx('page.usagemeters.no_allowance', null, 'No allowance on your plan yet')}
          </div>
        )}

        {/* pack credits — NON-EXPIRING. Rendered as a spendable dashed pill only
            when they can actually decrement. Under BYOK the AI-cost meter is
            billed to the customer's key, so its pack credits are PAUSED — render
            a muted, non-spendable annotation instead (never show frozen credits
            as spendable). */}
        {mm.packRemaining != null && mm.packRemaining > 0 && (
          mm.byokExempt ? (
            <div style={{ marginTop: 8, fontSize: '.68rem', color: 'var(--dim)', fontStyle: 'italic' }}
                 title={tx('page.usagemeters.pack_paused_title', null, "Pack credits don't decrement while your own API key is active.")}>
              {tx('page.usagemeters.pack_paused', { count: mm.packRemaining.toLocaleString() }, '{count} pack credits · not consumed while your key is active')}
            </div>
          ) : (
            <div style={{ display: 'inline-flex', alignItems: 'center', gap: 6, marginTop: 8,
                          padding: '3px 9px', borderRadius: 999,
                          border: '1px dashed var(--green)', color: 'var(--green)',
                          fontSize: '.7rem', fontWeight: 600 }}
                 title={tx('page.usagemeters.pack_spendable_title', null, 'Pack / add-on credits — never expire; consumed after the monthly floor')}>
              <Icon name="sparkles" size={12} />
              {tx('page.usagemeters.pack_spendable', { count: mm.packRemaining.toLocaleString() }, '+{count} pack credits · never expire')}
            </div>
          )
        )}

        {/* infra sub-caps — always shown (bind even under BYOK) */}
        {(mm.subCaps || []).map(function (s, i) {
          const sp = (s.limit != null && s.limit > 0 && s.used != null)
            ? Math.max(0, Math.min(100, Math.round((s.used / s.limit) * 100))) : null;
          return (
            <div key={i} style={{ marginTop: 8 }}>
              <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '.68rem',
                            color: 'var(--dim)', marginBottom: 3 }}>
                <span>{s.label}</span>
                {sp != null
                  ? <span>{(s.used || 0).toLocaleString()} / {(s.limit || 0).toLocaleString()}</span>
                  : <span>—</span>}
              </div>
              {sp != null && <Bar pct={sp} tone={toneFor(sp, false)} height={4} />}
            </div>
          );
        })}

        {/* controls: pack CTA + auto-refill (opt-in framing) */}
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                      gap: 10, marginTop: 12, flexWrap: 'wrap' }}>
          <button className="btn btn-ghost btn-sm"
                  style={{ padding: '3px 10px', fontSize: '.72rem', lineHeight: 1.3 }}
                  onClick={function () { onCta(ctaTab); }}>
            {ctaLabel}
          </button>
          <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7 }}>
            {/* <label htmlFor> names the role="switch" button (a button is a
                labelable element) so screen readers announce "Auto-refill,
                switch, on/off". */}
            <label htmlFor={'wpsb-arf-' + (mm.key || 'meter')}
                   style={{ fontSize: '.68rem', color: 'var(--dim)', cursor: 'pointer' }}
                   title={tx('page.usagemeters.auto_refill_title', null, 'Off by default — turn on to auto-buy a pack when the floor runs out, instead of stopping.')}>
              {tx('page.usagemeters.auto_refill', null, 'Auto-refill')}
            </label>
            <window.Toggle size="sm" id={'wpsb-arf-' + (mm.key || 'meter')} checked={!!mm.autoRefill}
                           onChange={function (v) { onAutoRefill(mm, v); }} />
          </span>
        </div>
      </div>
    );
  }

  /* ── main widget ─────────────────────────────────────────────────── */
  function HeaderUsageMeters() {
    const [state, setState] = useState('loading'); /* loading | ready | hidden */
    const [data, setData] = useState(null);
    const [open, setOpen] = useState(false);
    const rootRef = useRef(null);
    const viewedRef = useRef(false);

    /* fetch once on mount. 404 / absent / error → hidden (render nothing). */
    useEffect(function () {
      let live = true;
      const token = authToken();
      if (!token) { setState('hidden'); return; }
      fetch(apiBase() + METERS_PATH, { headers: { Authorization: 'Bearer ' + token } })
        .then(function (r) {
          if (!r.ok) return null;            /* 404/5xx → hidden */
          return r.json().catch(function () { return null; });
        })
        .then(function (j) {
          if (!live) return;
          const meters = j && (j.meters || j.data || j.usage);
          if (!Array.isArray(meters) || !meters.length) { setState('hidden'); return; }
          setData({
            cycleDaysLeft: num(pick(j, ['cycle_days_left', 'cycleDaysLeft', 'days_left'])),
            resetsAt: pick(j, ['resets_at', 'resetsAt'], null),
            meters: meters.map(readMeter),
          });
          setState('ready');
        })
        .catch(function () { if (live) setState('hidden'); });
      return function () { live = false; };
    }, []);

    /* close popover on outside click + Escape */
    useEffect(function () {
      if (!open) return;
      const onDoc = function (e) { if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false); };
      const onKey = function (e) { if (e.key === 'Escape') setOpen(false); };
      document.addEventListener('mousedown', onDoc);
      document.addEventListener('keydown', onKey);
      return function () { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); };
    }, [open]);

    const togglePopover = useCallback(function () {
      setOpen(function (v) {
        const next = !v;
        if (next && !viewedRef.current) {
          viewedRef.current = true;   /* once per mount is enough for the view event */
          emitClientEvent('usage.meters.viewed', { meters: data ? data.meters.length : 0 });
        }
        return next;
      });
    }, [data]);

    const onCta = useCallback(function (tab) { setOpen(false); switchTab(tab); }, []);

    /* Auto-refill write — optimistic; reverts + toasts honestly on failure
       (Radical Transparency: never claim a write that didn't land). The write
       contract mirrors the read path; a 404 (api round not merged) reverts. */
    const onAutoRefill = useCallback(function (mm, enabled) {
      setData(function (d) {
        if (!d) return d;
        return { ...d, meters: d.meters.map(function (x) { return x.key === mm.key ? { ...x, autoRefill: enabled } : x; }) };
      });
      const token = authToken();
      fetch(apiBase() + METERS_PATH + '/auto-refill', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', Authorization: 'Bearer ' + token },
        body: JSON.stringify({ meter: mm.key, enabled: enabled }),
      }).then(function (r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        if (window.wpsbToast) window.wpsbToast(tx('page.usagemeters.auto_refill_toast', { state: enabled ? tx('page.usagemeters.on', null, 'on') : tx('page.usagemeters.off', null, 'off'), label: mm.label }, 'Auto-refill {state} — {label}'), 'ok');
      }).catch(function () {
        /* revert */
        setData(function (d) {
          if (!d) return d;
          return { ...d, meters: d.meters.map(function (x) { return x.key === mm.key ? { ...x, autoRefill: !enabled } : x; }) };
        });
        if (window.wpsbToast) window.wpsbToast(tx('page.usagemeters.auto_refill_error', null, "Couldn't update auto-refill — try again"), 'err');
      });
    }, []);

    if (state === 'hidden') return null;

    if (state === 'loading') {
      /* skeleton pill — never a fabricated 0% */
      return (
        <div className="usage-pill usage-pill-skeleton" aria-hidden="true"
             style={{ display: 'inline-flex', alignItems: 'center', gap: 7,
                      height: 30, padding: '0 12px', borderRadius: 999,
                      border: '1px solid var(--border)', background: 'var(--surface-2)' }}>
          <span style={{ width: 10, height: 10, borderRadius: '50%', background: 'var(--border)' }} />
          <span style={{ width: 46, height: 8, borderRadius: 999, background: 'var(--border)' }} />
        </div>
      );
    }

    /* ready */
    const meters = data.meters;
    const stopped = meters.filter(function (m) { return m.hardStopped; });
    const anyStopped = stopped.length > 0;
    /* Banner names the meter(s) so the customer knows WHICH limit hit. CTA routes
       to the stopped meter's own pack CTA when there's exactly one, else Billing. */
    const stoppedNames = stopped.map(function (m) { return m.label; });
    const stopBannerText = stopped.length === 1
      ? tx('page.usagemeters.stop_banner_one', { name: stoppedNames[0] }, '{name} has reached its limit. Buy a pack or turn on auto-refill to keep going.')
      : tx('page.usagemeters.stop_banner_many', { count: stoppedNames.length }, '{count} meters have reached their limit. Buy a pack or turn on auto-refill to keep going.');
    const stopCtaTab = (stopped.length === 1 && stopped[0].packCta
      && (stopped[0].packCta.tab || stopped[0].packCta.target)) || 'billing';
    const worst = meters.reduce(function (a, m) { return Math.max(a, meterPressure(m)); }, 0);
    const tone = toneFor(worst, anyStopped);
    const tint = toneTintFor(worst, anyStopped);

    return (
      <div className="usage-pill-root" ref={rootRef} style={{ position: 'relative' }}>
        <button type="button" className="usage-pill"
                aria-haspopup="dialog" aria-expanded={open}
                aria-label={tx('page.usagemeters.pill_aria', { detail: anyStopped
                  ? (stopped.length === 1
                     ? tx('page.usagemeters.aria_meter_limit_one', { name: stoppedNames[0] }, '{name} has reached its limit')
                     : tx('page.usagemeters.aria_meter_limit_many', { count: stoppedNames.length }, '{count} meters have reached their limit'))
                  : tx('page.usagemeters.aria_highest_meter', { pct: worst }, 'highest meter at {pct} percent') }, 'Usage — {detail}')}
                onClick={togglePopover}
                style={{
                  display: 'inline-flex', alignItems: 'center', gap: 7,
                  height: 30, padding: '0 11px', borderRadius: 999, cursor: 'pointer',
                  border: '1px solid ' + (worst >= 75 || anyStopped ? tone : 'var(--border)'),
                  background: worst >= 75 || anyStopped ? tint : 'var(--surface-2)',
                  color: 'var(--text)', font: 'inherit',
                }}>
          <span style={{ position: 'relative', display: 'inline-flex' }}>
            <span aria-hidden="true" style={{ width: 9, height: 9, borderRadius: '50%', background: tone }} />
          </span>
          <span style={{ fontSize: '.74rem', fontWeight: 600, fontFamily: 'var(--font-mono)', color: tone }}>
            {anyStopped ? tx('page.usagemeters.stop', null, 'STOP') : worst + '%'}
          </span>
          <Icon name="chevron-down" size={12} style={{ color: 'var(--dim)' }} />
        </button>

        {open && (
          <div role="dialog" aria-label={tx('page.usagemeters.dialog_aria', null, 'Usage meters')}
               style={{
                 /* NOT converted to the §4e Modal primitive, deliberately.
                    This carries role="dialog" but it is an ANCHORED
                    DROPDOWN — position:absolute under its trigger button,
                    no scrim, no fixed overlay. Routing it through the
                    modal primitive would turn a header popover into a
                    centred full-screen dialog, which is a UX regression,
                    not a consolidation. It is in the z-scale rather than
                    the modal band: at its old literal 1200 it outranked
                    every modal in the app, so a usage popover could paint
                    over a dialog. --z-popover puts a menu under a
                    decision surface, where it belongs. */
                 position: 'absolute', top: 'calc(100% + 8px)', right: 0, zIndex: 'var(--z-popover)',
                 width: 340, maxWidth: '92vw',
                 background: 'var(--surface)', border: '1px solid var(--border)',
                 borderRadius: 12, boxShadow: '0 18px 50px rgba(0,0,0,.4)',
                 overflow: 'hidden',
               }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '12px 14px' }}>
              <span style={{ fontWeight: 700, fontSize: '.9rem', color: 'var(--text)', flex: 1 }}>{tx('page.usagemeters.usage_this_cycle', null, 'Usage this cycle')}</span>
              {data.cycleDaysLeft != null && (
                <span style={{ fontSize: '.7rem', color: 'var(--dim)' }}>{tx('page.usagemeters.days_left', { days: data.cycleDaysLeft }, '{days}d left')}</span>
              )}
              <button className="icon-btn" aria-label={tx('page.usagemeters.close_usage', null, 'Close usage')} onClick={function () { setOpen(false); }}
                      style={{ width: 24, height: 24 }}>
                <Icon name="x" size={13} />
              </button>
            </div>
            {anyStopped && (
              <div style={{ margin: '0 14px 4px', padding: '8px 10px', borderRadius: 8,
                            background: 'rgba(255,112,112,.10)', border: '1px solid var(--danger, var(--red))',
                            display: 'flex', alignItems: 'center', gap: 8 }}>
                <Icon name="info" size={14} style={{ color: 'var(--danger, var(--red))', flex: 'none' }} />
                <span style={{ fontSize: '.72rem', color: 'var(--text-2)', flex: 1 }}>
                  {stopBannerText}
                </span>
                <button className="btn btn-sm"
                        style={{ padding: '3px 10px', fontSize: '.7rem', background: 'var(--beam)', color: '#001018', border: 'none' }}
                        onClick={function () { onCta(stopCtaTab); }}>
                  {tx('page.usagemeters.view_options', null, 'View options')}
                </button>
              </div>
            )}
            <div style={{ maxHeight: '60vh', overflowY: 'auto' }}>
              {meters.map(function (mm, i) {
                return <MeterRow key={mm.key || i} mm={mm} onCta={onCta} onAutoRefill={onAutoRefill} />;
              })}
            </div>
            <div style={{ padding: '10px 14px', borderTop: '1px solid var(--border)',
                          fontSize: '.66rem', color: 'var(--dim)', lineHeight: 1.5 }}>
              {tx('page.usagemeters.footer_note', null, 'Monthly floor is use-or-lose; pack credits never expire and are used after the floor.')}
            </div>
          </div>
        )}
      </div>
    );
  }

  window.HeaderUsageMeters = HeaderUsageMeters;
  console.log('[WPSB] HeaderUsageMeters (OL-054) loaded');
})();
