/* Analytics viewer — dataviz method: single series needs no legend, so
   the chart carries one validated hue (blue, categorical slot 1) scoped
   locally to .viz-root, independent of whatever theme tokens the host
   app's own style.css defines (2026-09-18, validated via the house
   palette's validate_palette.js against both a light #ffffff and dark
   #1f1f1f card surface — PASS on lightness/chroma/CVD/normal-vision,
   contrast clears 3:1 on both). Page chrome (bg/card/ink/muted/rule)
   still reads the HOST app's own tokens, same as every other shared
   block's CSS (list.css, chat.css, ...). */
.viz-root {
  --series-1: #2a78d6;
  --series-2: #eb6834;
  --series-3: #1baf7a;
  --series-4: #eda100;
  --series-5: #e87ba4;
}
@media (prefers-color-scheme: dark) {
  .viz-root {
    --series-1: #3987e5;
    --series-2: #d95926;
    --series-3: #199e70;
    --series-4: #c98500;
    --series-5: #d55181;
  }
}

.analytics-status-page { max-width: 1200px; margin: 0 auto; padding: 16px 16px 16px; }
.analytics-status-page h1 { margin: 0 0 4px; font-size: 20px; }
/* "on the permanent link from every application Analytics do its
   production link with an arrow back" (2026-09-23) -- analytics.html is
   the one stable/production URL every app gets (unlike a work item's own
   ephemeral staging channel), so a plain same-origin "/" always lands
   back on that app's real production home from here. */
.analytics-back-link { display: inline-block; margin-bottom: 4px; font-size: 13px; color: var(--muted, #6b6b66); text-decoration: none; }
.analytics-back-link:hover { color: var(--series-1); text-decoration: underline; }

/* "move filters aside title... right aligned filters, left aligned
   title" (2026-09-20) — same row, title hugs the left edge, filters the
   right; wraps to its own row under the title on a narrow viewport
   (mobile-first — never crowds the range buttons against long titles).
   Updated 2026-09-27: resolution field moves below period buttons,
   both right-aligned. */
.analytics-header { display: flex; justify-content: space-between; align-items: flex-start; flex-wrap: wrap; gap: 8px 16px; margin-bottom: 4px; }
/* Two-row controls (from matchable-vnb9, 2026-09-27; the hub's own
   analytics.html uses .analytics-controls too but never had this rule)
   ("make sure same fixes for weekly resolution + showing 90d period +
   resolution selection all are picked from AB analytics template to
   this app"): period buttons stay their own right-aligned row;
   resolution moves to a second right-aligned row below them, next to
   the env selector -- same two-row shape as apps/app-blocks/public/
   analytics.html. */
.analytics-controls { display: flex; flex-direction: column; gap: 6px; align-items: flex-end; }
.analytics-filters { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.analytics-bottom-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.analytics-row-2 { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.analytics-range-btn { padding: 4px 12px; border-radius: 8px; border: 1px solid var(--rule, #e2e2dd); background: var(--card, #fff); color: var(--ink, #1c1c1a); cursor: pointer; font: inherit; }
.analytics-range-btn.active { border-color: var(--series-1); color: var(--series-1); font-weight: 600; }
/* Dev/Production/All selector (2026-09-23, a real <select> dropdown --
   amended from an earlier two-button version once "all" joined the ask)
   -- a thin divider (margin-right) separates it from the time-range
   buttons beside it, same as the buttons it replaced. */
.analytics-env-select { padding: 4px 10px; padding-right: 14px; border-radius: 8px; border: 1px solid var(--rule, #e2e2dd); background: var(--card, #fff); color: var(--ink, #1c1c1a); cursor: pointer; font: inherit; }
.analytics-resolution-selector { display: flex; gap: 6px; align-items: center; }

.analytics-card { background: var(--card, #fff); border: 1px solid var(--rule, #e2e2dd); border-radius: 12px; padding: 10px 14px; margin-bottom: 10px; }
.analytics-card h2 { margin: 0 0 8px; font-size: 13px; }

.viz-chart-wrap { position: relative; }
.viz-chart-wrap svg { width: 100%; height: auto; display: block; }

.viz-grid { stroke: var(--rule, #e2e2dd); stroke-width: 1; }
.viz-axis-label { fill: var(--muted, #6b6b66); font-size: 10px; }
.viz-direct-label { fill: var(--ink, #1c1c1a); font-size: 11px; font-weight: 600; }
.viz-bar { fill: var(--series-1); }
.viz-hit { fill: transparent; cursor: pointer; }
.viz-hit:hover + .viz-bar, .viz-hit:focus + .viz-bar { opacity: 0.85; }

.viz-tooltip { position: absolute; background: var(--ink, #1c1c1a); color: var(--bg, #f6f6f4); border-radius: 8px; padding: 6px 10px; font-size: 12px; pointer-events: none; white-space: nowrap; z-index: 5; }
.viz-tooltip .viz-tooltip-value { font-weight: 700; }
.viz-tooltip .viz-tooltip-label { opacity: 0.75; margin-inline-start: 6px; }

.analytics-status-page table { width: 100%; border-collapse: collapse; }
.analytics-status-page th, .analytics-status-page td { text-align: left; padding: 6px 4px; border-bottom: 1px solid var(--rule, #e2e2dd); font-size: 13px; }
.analytics-status-page .viz-num { text-align: right; font-variant-numeric: tabular-nums; }
.analytics-status-page .viz-uid { font-family: monospace; font-size: 12px; color: var(--muted, #6b6b66); }

.viz-stat { margin: 0; font-size: 32px; font-weight: 700; color: var(--ink, #1c1c1a); font-variant-numeric: tabular-nums; }

.viz-line { fill: none; stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.viz-line-dot { stroke: var(--card, #fff); stroke-width: 2; }

.viz-legend { display: flex; flex-wrap: wrap; gap: 12px; margin: 8px 0 0; font-size: 12px; color: var(--muted, #6b6b66); }
.viz-legend-item { display: inline-flex; align-items: center; gap: 5px; }
.viz-legend-swatch { width: 10px; height: 10px; border-radius: 50%; display: inline-block; }
/* "click on any legend item toggles it on/off, when toggled off legend
   is faded, line is hidden" (2026-09-19) -- <button>-based legend items
   reset to look like the plain <span> ones already did, then fade on
   toggle-off. */
button.viz-legend-item { background: none; border: none; padding: 0; font: inherit; color: inherit; cursor: pointer; }
.viz-legend-item-off { opacity: 0.4; }

/* "left plots 3 right tables (first is place holder), all same height
   1/3 screen" (2026-09-19) — 3 rows x 2 columns (plots left, tables
   right); a grid ROW's height is shared by both its cells natively (no
   JS measurement needed, unlike the flex-pair approach this replaced —
   see analytics-viewer.js's own removed syncPairHeights comment).
   Single column, natural height, on narrow viewports (mobile-first
   default) — a fixed row height would be unreadably short stacked on a
   phone. DOM order IS the visual order: plots and tables interleave
   two-at-a-time (events+placeholder, distinct-users+top-users, screen-
   mean+screen-stats), auto-flowing into the grid row by row.
   "what's needed for them to be seen in 1 page?" (2026-09-20) — plain
   33vh rows overflowed past 100vh once the real header/filters/page
   padding were added on top of them (3 rows already summed to ~99vh
   alone). CHROME_H below is a real, measured estimate of everything
   ABOVE and AROUND the grid on this exact page (h1 + subtitle + filter
   row + page's own top/bottom padding) — 100dvh (real visible viewport,
   not 100vh's desktop-Safari-toolbar overcount on mobile) minus that
   fits the whole dashboard in one screen, no scroll, on any real
   viewport height instead of a guessed constant. */
.viz-dashboard-grid { display: flex; flex-direction: column; gap: 10px; }
@media (min-width: 900px) {
  .viz-dashboard-grid {
    display: grid;
    grid-template-columns: 3fr 2fr;
    --chrome-h: 168px;
    grid-template-rows: repeat(3, calc((100dvh - var(--chrome-h)) / 3));
    gap: 10px;
  }
  .viz-dashboard-grid > .analytics-card { margin-bottom: 0; min-width: 0; min-height: 0; display: flex; flex-direction: column; }
  .viz-dashboard-grid .viz-chart-wrap { flex: 1 1 auto; min-height: 0; }
  .viz-dashboard-grid .viz-chart-wrap svg { height: 100%; }
}
/* Events has no paired table yet — an intentionally blank card, not a
   layout bug (explicit ask: "put empty place holder for a table in the
   first row"). Dashed + faint so it doesn't read as broken chrome. Its
   only purpose is row-alignment with the grid's right column, which
   doesn't exist once the layout stacks to one column on a narrow
   viewport -- hidden there instead of leaving a pointless empty box. */
.viz-placeholder-card { border-style: dashed; opacity: 0.5; }
@media (max-width: 899px) {
  .viz-placeholder-card { display: none; }
}

/* Geo world map (2026-09-23, revised the same day after real feedback:
   "I see black and empty component... low detail map[:] just country
   borders[,] country name, and when zoom in major city names") -- real
   country-border shapes (shared/analytics/world-map-paths.js), a
   choropleth fill for countries with visitors, name/city labels, zoom
   controls top-right, "map on top, sorted country/city list at the
   bottom" per the original verbatim ask. Every element the map draws
   lives in ITS OWN (lng, -lat) coordinate space (the SVG's own viewBox
   is set directly in that space, see analytics-charts.js's
   GeoMap.svgViewBox) -- vector-effect keeps stroke width a constant,
   sane number of screen pixels regardless of how far zoomed in/out the
   current view is (a plain stroke-width would otherwise be a fixed
   fraction of a DEGREE, comically thick zoomed out or invisible zoomed
   in on one country). */
.analytics-geo-card { display: flex; flex-direction: column; gap: 8px; }
/* Panel height, matching a line-plot panel's own height (2026-09-24, real
   regression: this card lives OUTSIDE .viz-dashboard-grid, so its
   .viz-chart-wrap fell back to the generic width:100%/height:auto rule
   below -- a pure function of the CURRENT zoom's own viewBox aspect
   ratio. World view is wide/landscape (~2.5:1); zooming into a
   north-south country like Israel flips that to a narrow/portrait box,
   so the panel ballooned from a normal chart-sized height to several
   times that just by zooming in -- same fixed-height + svg height:100%
   pattern .viz-dashboard-grid's own chart panels already use above,
   applied here too; preserveAspectRatio="xMidYMid meet" (already set in
   renderGeoMap) letterboxes/pillarboxes the map to fit instead of the
   panel growing/shrinking with whatever is currently zoomed. */
#analytics-geo-map-wrap { height: 300px; }
#analytics-geo-map-wrap svg { width: 100%; height: 100%; }
@media (min-width: 900px) {
  #analytics-geo-map-wrap { height: calc((100dvh - 168px) / 3); }
}
.analytics-geo-header { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; }
.analytics-geo-controls { display: flex; gap: 6px; }
.analytics-geo-controls button { width: 28px; height: 28px; border-radius: 6px; border: 1px solid var(--rule, #e2e2dd); background: var(--card, #fff); color: var(--ink, #1c1c1a); cursor: pointer; font: inherit; line-height: 1; }
.analytics-geo-controls .analytics-geo-back { width: auto; height: auto; padding: 0 8px; display: inline-flex; gap: 6px; align-items: center; }
.analytics-geo-back { border-color: var(--muted, #999); color: var(--muted, #999); opacity: 0.7; }
.geo-back-icon { width: 18px; height: 18px; filter: grayscale(1); opacity: 0.8; flex-shrink: 0; }
/* A fixed mid-tone ocean fill (not the --bg/--card host tokens) so the
   map reads the same, always-visible way in both a light and a dark
   host page -- the ORIGINAL bug report was exactly a host dark-mode
   --bg resolving to near-black behind an otherwise-empty component. */
.viz-geo-bg { fill: #cfe3f2; }
@media (prefers-color-scheme: dark) { .viz-geo-bg { fill: #253345; } }
/* "mouse scroll to zoom while on map + drag to pan" (2026-09-23) --
   grab/grabbing cursor over the whole map area (not just a country
   shape's own :hover pointer, which stays as-is for click-to-zoom). */
#analytics-geo-map { cursor: grab; touch-action: none; }
#analytics-geo-map.viz-geo-dragging { cursor: grabbing; }
.viz-geo-country { stroke: var(--muted, #6b6b66); stroke-width: 0.5px; vector-effect: non-scaling-stroke; cursor: pointer; }
.viz-geo-country:hover, .viz-geo-country:focus { stroke-width: 1.5px; outline: none; }
.viz-geo-label { fill: var(--ink, #1c1c1a); text-anchor: middle; pointer-events: none; paint-order: stroke; stroke: var(--card, #fff); stroke-width: 0.15px; }
/* font-size deliberately NOT set here -- analytics-charts.js's drawCity
   sets it per-render as a fraction of the current zoom box's own width
   (mapUnit()), same box-relative-sizing reasoning as .viz-geo-label's
   own font-size; a fixed CSS font-size would win over that (CSS beats
   an SVG presentation attribute) and reintroduce the exact
   "huge zoomed in, invisible zoomed out" bug this whole approach exists
   to avoid. */
.viz-geo-city-label { fill: var(--ink, #1c1c1a); text-anchor: middle; pointer-events: none; paint-order: stroke; stroke: var(--card, #fff); stroke-width: 0.1px; }
.viz-geo-dot { fill: var(--series-2); fill-opacity: 0.85; stroke: var(--card, #fff); stroke-width: 0.3px; vector-effect: non-scaling-stroke; pointer-events: none; }
.viz-hit:hover + .viz-geo-dot, .viz-hit:focus + .viz-geo-dot { fill-opacity: 1; }

.viz-table-scroll { overflow-y: auto; }
.viz-table-scroll thead th { position: sticky; top: 0; background: var(--card, #fff); }
.viz-dashboard-grid .viz-table-scroll { flex: 1 1 auto; min-height: 0; }
@media (max-width: 899px) {
  .viz-table-scroll { max-height: 260px; }
}
