.leaflet-pane,.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-tile-container,.leaflet-pane>svg,.leaflet-pane>canvas,.leaflet-zoom-box,.leaflet-image-layer,.leaflet-layer{position:absolute;left:0;top:0}.leaflet-container{overflow:hidden}.leaflet-tile,.leaflet-marker-icon,.leaflet-marker-shadow{-webkit-user-select:none;-moz-user-select:none;user-select:none;-webkit-user-drag:none}.leaflet-tile::selection{background:transparent}.leaflet-safari .leaflet-tile{image-rendering:-webkit-optimize-contrast}.leaflet-safari .leaflet-tile-container{width:1600px;height:1600px;-webkit-transform-origin:0 0}.leaflet-marker-icon,.leaflet-marker-shadow{display:block}.leaflet-container .leaflet-overlay-pane svg,.leaflet-container .leaflet-marker-pane img,.leaflet-container .leaflet-shadow-pane img,.leaflet-container .leaflet-tile-pane img,.leaflet-container img.leaflet-image-layer,.leaflet-container .leaflet-tile{max-width:none !important;max-height:none !important}.leaflet-container.leaflet-touch-zoom{-ms-touch-action:pan-x pan-y;touch-action:pan-x pan-y}.leaflet-container.leaflet-touch-drag{-ms-touch-action:pinch-zoom;touch-action:none;touch-action:pinch-zoom}.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom{-ms-touch-action:none;touch-action:none}.leaflet-container{-webkit-tap-highlight-color:transparent}.leaflet-container a{-webkit-tap-highlight-color:rgba(51,181,229,0.4)}.leaflet-tile{filter:inherit;visibility:hidden}.leaflet-tile-loaded{visibility:inherit}.leaflet-zoom-box{width:0;height:0;-moz-box-sizing:border-box;box-sizing:border-box;z-index:800}.leaflet-overlay-pane svg{-moz-user-select:none}.leaflet-pane{z-index:400}.leaflet-tile-pane{z-index:200}.leaflet-overlay-pane{z-index:400}.leaflet-shadow-pane{z-index:500}.leaflet-marker-pane{z-index:600}.leaflet-tooltip-pane{z-index:650}.leaflet-popup-pane{z-index:700}.leaflet-map-pane canvas{z-index:100}.leaflet-map-pane svg{z-index:200}.leaflet-vml-shape{width:1px;height:1px}.lvml{behavior:url(#default#VML);display:inline-block;position:absolute}.leaflet-control{position:relative;z-index:800;pointer-events:visiblePainted;pointer-events:auto}.leaflet-top,.leaflet-bottom{position:absolute;z-index:1000;pointer-events:none}.leaflet-top{top:0}.leaflet-right{right:0}.leaflet-bottom{bottom:0}.leaflet-left{left:0}.leaflet-control{float:left;clear:both}.leaflet-right .leaflet-control{float:right}.leaflet-top .leaflet-control{margin-top:10px}.leaflet-bottom .leaflet-control{margin-bottom:10px}.leaflet-left .leaflet-control{margin-left:10px}.leaflet-right .leaflet-control{margin-right:10px}.leaflet-fade-anim .leaflet-tile{will-change:opacity}.leaflet-fade-anim .leaflet-popup{opacity:0;-webkit-transition:opacity 0.2s linear;-moz-transition:opacity 0.2s linear;transition:opacity 0.2s linear}.leaflet-fade-anim .leaflet-map-pane .leaflet-popup{opacity:1}.leaflet-zoom-animated{-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}.leaflet-zoom-anim .leaflet-zoom-animated{will-change:transform}.leaflet-zoom-anim .leaflet-zoom-animated{-webkit-transition:-webkit-transform 0.25s cubic-bezier(0, 0, 0.25, 1);-moz-transition:-moz-transform 0.25s cubic-bezier(0, 0, 0.25, 1);transition:transform 0.25s cubic-bezier(0, 0, 0.25, 1)}.leaflet-zoom-anim .leaflet-tile,.leaflet-pan-anim .leaflet-tile{-webkit-transition:none;-moz-transition:none;transition:none}.leaflet-zoom-anim .leaflet-zoom-hide{visibility:hidden}.leaflet-interactive{cursor:pointer}.leaflet-grab{cursor:-webkit-grab;cursor:-moz-grab;cursor:grab}.leaflet-crosshair,.leaflet-crosshair .leaflet-interactive{cursor:crosshair}.leaflet-popup-pane,.leaflet-control{cursor:auto}.leaflet-dragging .leaflet-grab,.leaflet-dragging .leaflet-grab .leaflet-interactive,.leaflet-dragging .leaflet-marker-draggable{cursor:move;cursor:-webkit-grabbing;cursor:-moz-grabbing;cursor:grabbing}.leaflet-marker-icon,.leaflet-marker-shadow,.leaflet-image-layer,.leaflet-pane>svg path,.leaflet-tile-container{pointer-events:none}.leaflet-marker-icon.leaflet-interactive,.leaflet-image-layer.leaflet-interactive,.leaflet-pane>svg path.leaflet-interactive,svg.leaflet-image-layer.leaflet-interactive path{pointer-events:visiblePainted;pointer-events:auto}.leaflet-container{background:#ddd;outline:0}.leaflet-container a{color:#0078A8}.leaflet-container a.leaflet-active{outline:2px solid orange}.leaflet-zoom-box{border:2px dotted #38f;background:rgba(255,255,255,0.5)}.leaflet-container{font:12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif}.leaflet-bar{box-shadow:0 1px 5px rgba(0,0,0,0.65);border-radius:4px}.leaflet-bar a,.leaflet-bar a:hover{background-color:#fff;border-bottom:1px solid #ccc;width:26px;height:26px;line-height:26px;display:block;text-align:center;text-decoration:none;color:black}.leaflet-bar a,.leaflet-control-layers-toggle{background-position:50% 50%;background-repeat:no-repeat;display:block}.leaflet-bar a:hover{background-color:#f4f4f4}.leaflet-bar a:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.leaflet-bar a:last-child{border-bottom-left-radius:4px;border-bottom-right-radius:4px;border-bottom:none}.leaflet-bar a.leaflet-disabled{cursor:default;background-color:#f4f4f4;color:#bbb}.leaflet-touch .leaflet-bar a{width:30px;height:30px;line-height:30px}.leaflet-touch .leaflet-bar a:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.leaflet-touch .leaflet-bar a:last-child{border-bottom-left-radius:2px;border-bottom-right-radius:2px}.leaflet-control-zoom-in,.leaflet-control-zoom-out{font:bold 18px 'Lucida Console', Monaco, monospace;text-indent:1px}.leaflet-touch .leaflet-control-zoom-in,.leaflet-touch .leaflet-control-zoom-out{font-size:22px}.leaflet-control-layers{box-shadow:0 1px 5px rgba(0,0,0,0.4);background:#fff;border-radius:5px}.leaflet-control-layers-toggle{background-image:url(images/layers.png);width:36px;height:36px}.leaflet-retina .leaflet-control-layers-toggle{background-image:url(images/layers-2x.png);background-size:26px 26px}.leaflet-touch .leaflet-control-layers-toggle{width:44px;height:44px}.leaflet-control-layers .leaflet-control-layers-list,.leaflet-control-layers-expanded .leaflet-control-layers-toggle{display:none}.leaflet-control-layers-expanded .leaflet-control-layers-list{display:block;position:relative}.leaflet-control-layers-expanded{padding:6px 10px 6px 6px;color:#333;background:#fff}.leaflet-control-layers-scrollbar{overflow-y:scroll;overflow-x:hidden;padding-right:5px}.leaflet-control-layers-selector{margin-top:2px;position:relative;top:1px}.leaflet-control-layers label{display:block}.leaflet-control-layers-separator{height:0;border-top:1px solid #ddd;margin:5px -10px 5px -6px}.leaflet-default-icon-path{background-image:url(images/marker-icon.png)}.leaflet-container .leaflet-control-attribution{background:#fff;background:rgba(255,255,255,0.7);margin:0}.leaflet-control-attribution,.leaflet-control-scale-line{padding:0 5px;color:#333}.leaflet-control-attribution a{text-decoration:none}.leaflet-control-attribution a:hover{text-decoration:underline}.leaflet-container .leaflet-control-attribution,.leaflet-container .leaflet-control-scale{font-size:11px}.leaflet-left .leaflet-control-scale{margin-left:5px}.leaflet-bottom .leaflet-control-scale{margin-bottom:5px}.leaflet-control-scale-line{border:2px solid #777;border-top:none;line-height:1.1;padding:2px 5px 1px;font-size:11px;white-space:nowrap;overflow:hidden;-moz-box-sizing:border-box;box-sizing:border-box;background:#fff;background:rgba(255,255,255,0.5)}.leaflet-control-scale-line:not(:first-child){border-top:2px solid #777;border-bottom:none;margin-top:-2px}.leaflet-control-scale-line:not(:first-child):not(:last-child){border-bottom:2px solid #777}.leaflet-touch .leaflet-control-attribution,.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{box-shadow:none}.leaflet-touch .leaflet-control-layers,.leaflet-touch .leaflet-bar{border:2px solid rgba(0,0,0,0.2);background-clip:padding-box}.leaflet-popup{position:absolute;text-align:center;margin-bottom:20px}.leaflet-popup-content-wrapper{padding:1px;text-align:left;border-radius:12px}.leaflet-popup-content{margin:13px 19px;line-height:1.4}.leaflet-popup-content p{margin:18px 0}.leaflet-popup-tip-container{width:40px;height:20px;position:absolute;left:50%;margin-left:-20px;overflow:hidden;pointer-events:none}.leaflet-popup-tip{width:17px;height:17px;padding:1px;margin:-10px auto 0;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.leaflet-popup-content-wrapper,.leaflet-popup-tip{background:white;color:#333;box-shadow:0 3px 14px rgba(0,0,0,0.4)}.leaflet-container a.leaflet-popup-close-button{position:absolute;top:0;right:0;padding:4px 4px 0 0;border:none;text-align:center;width:18px;height:14px;font:16px/14px Tahoma, Verdana, sans-serif;color:#c3c3c3;text-decoration:none;font-weight:bold;background:transparent}.leaflet-container a.leaflet-popup-close-button:hover{color:#999}.leaflet-popup-scrolled{overflow:auto;border-bottom:1px solid #ddd;border-top:1px solid #ddd}.leaflet-oldie .leaflet-popup-content-wrapper{-ms-zoom:1}.leaflet-oldie .leaflet-popup-tip{width:24px;margin:0 auto;-ms-filter:"progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";filter:progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)}.leaflet-oldie .leaflet-popup-tip-container{margin-top:-1px}.leaflet-oldie .leaflet-control-zoom,.leaflet-oldie .leaflet-control-layers,.leaflet-oldie .leaflet-popup-content-wrapper,.leaflet-oldie .leaflet-popup-tip{border:1px solid #999}.leaflet-div-icon{background:#fff;border:1px solid #666}.leaflet-tooltip{position:absolute;padding:6px;background-color:#fff;border:1px solid #fff;border-radius:3px;color:#222;white-space:nowrap;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;pointer-events:none;box-shadow:0 1px 3px rgba(0,0,0,0.4)}.leaflet-tooltip.leaflet-clickable{cursor:pointer;pointer-events:auto}.leaflet-tooltip-top:before,.leaflet-tooltip-bottom:before,.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{position:absolute;pointer-events:none;border:6px solid transparent;background:transparent;content:""}.leaflet-tooltip-bottom{margin-top:6px}.leaflet-tooltip-top{margin-top:-6px}.leaflet-tooltip-bottom:before,.leaflet-tooltip-top:before{left:50%;margin-left:-6px}.leaflet-tooltip-top:before{bottom:0;margin-bottom:-12px;border-top-color:#fff}.leaflet-tooltip-bottom:before{top:0;margin-top:-12px;margin-left:-6px;border-bottom-color:#fff}.leaflet-tooltip-left{margin-left:-6px}.leaflet-tooltip-right{margin-left:6px}.leaflet-tooltip-left:before,.leaflet-tooltip-right:before{top:50%;margin-top:-6px}.leaflet-tooltip-left:before{right:0;margin-right:-12px;border-left-color:#fff}.leaflet-tooltip-right:before{left:0;margin-left:-12px;border-right-color:#fff}

@font-face {
  font-family: "Work Sans Atlas";
  src: url("../vendor/worksans/WorkSansVariable.ttf") format("truetype");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Work Sans Atlas";
  src: url("../vendor/worksans/WorkSansVariable-Italic.ttf") format("truetype");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}

@font-face {
  font-family: "Inter Atlas";
  src: url("../vendor/inter/InterVariable.woff2") format("woff2");
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "Inter Atlas";
  src: url("../vendor/inter/InterVariable-Italic.woff2") format("woff2");
  font-weight: 100 900;
  font-style: italic;
  font-display: swap;
}

:root {
  --font-ui: "Work Sans Atlas", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --font-data: "Inter Atlas", "SF Pro Text", "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --bg: #07090e;
  --panel: rgba(8, 11, 18, 0.84);
  --panel-strong: rgba(10, 14, 24, 0.94);
  --panel-soft: rgba(165, 220, 255, 0.08);
  /* Dark-theme text palette. Lightness was raised across the board (muted 57->74%,
     accent-dim 49->66%, accent-hot 57->66%) because the previous values read as too
     dim on the near-black background. Hue and saturation are carried over unchanged,
     so the palette keeps its character. --text is pure white and the --accent
     highlight is kept at its original #70c3e2, both by request. */
  --text: #ffffff;
  --muted: #b1c0c8;
  /* Quieter text than --muted, for notes and sources, at 4.5:1 or better on
     every panel. It replaces text dimmed with opacity, which passed on the dark
     theme and fell to 3.2-3.6:1 on the light one. */
  --muted-soft: #8d99a1;
  --border: rgba(112, 195, 226, 0.14);
  --line: rgba(112, 195, 226, 0.12);
  --accent: #70c3e2;
  --accent-rgb: 112, 195, 226;
  /* The front page's frames and the rules inside them. Built on --accent-rgb,
     so the light theme follows without a second palette. */
  --home-line: rgba(var(--accent-rgb), 0.16);
  --home-line-soft: rgba(var(--accent-rgb), 0.11);
  /* The middle step of the comparison bars, between the accent and
     --accent-hot. It was a hard-coded #ffa857 with no light-theme step. */
  --accent-warm: #ffa857;
  --accent-hot: #e17087;
  --accent-dim: #90b1c1;
  --shadow: 0 4px 16px rgba(0, 0, 0, 0.35);
  /* A card's own ground. On a near-black page a two-per-cent lift is all a
     card needs to separate from it. On a light page the same lift is
     invisible and the card reads as a hole cut in the background -- next to a
     card that happens to be a <button>, which the light theme's form rule
     paints white, the pair looks like a bug. So it is plain white there. */
  --card-bg: rgba(255, 255, 255, 0.02);
  --mosque: #72c9ff;
  --prayer: #4ea66e;
  --other-worship: #d6b162;
  --reception: #d65485;
  --safe-top: max(14px, env(safe-area-inset-top));
  --safe-right: max(14px, env(safe-area-inset-right));
  --safe-bottom: max(14px, env(safe-area-inset-bottom));
  --safe-left: max(14px, env(safe-area-inset-left));
  --top-bar-height: 64px;
  --panel-width: min(324px, calc(100vw - var(--safe-left) - var(--safe-right) - 10px));
  --stats-width: clamp(256px, 25.6vw, 352px);
  --mobile-map-modes-offset: 0px;
  --btn-height: 32px;
  --control-radius: 9999px;
  --control-border: rgba(112, 195, 226, 0.12);
  --control-border-strong: rgba(112, 195, 226, 0.22);
  --control-bg: rgba(8, 12, 18, 0.9);
  --control-bg-hover: rgba(112, 195, 226, 0.1);
  --control-hover-border: rgba(112, 195, 226, 0.22);
  --control-hover-bg: rgba(112, 195, 226, 0.1);
  --control-shadow-ring: 0 0 0 1px rgba(112, 195, 226, 0.06);
  /* The top bar floats over the map, so its pills are a light veil over what
     is behind them rather than a plate on top of it: enough tint and blur to
     read, not enough to cut a dark band across the view. */
  --topbar-surface: rgba(10, 15, 24, 0.34);
  --topbar-surface-border: rgba(255, 255, 255, 0.12);
  --panel-card-bg: rgba(7, 11, 18, 0.88);
  --panel-card-border: rgba(112, 195, 226, 0.10);
  --menu-surface-shadow: 0 4px 16px rgba(0, 0, 0, 0.28);
  --card-radius: 18px;
  --card-radius-sm: 14px;

  /* Elevation and corner scale. Four radii and three shadows, so a surface
     picks a step rather than inventing a number. Rings, hairlines and accent
     glows are separate: they say something about state, not about height. */
  --radius-xs: 4px;
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 18px;
  --radius-pill: 9999px;
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.2);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.35);
  --shadow-lg: 0 18px 40px rgba(0, 0, 0, 0.45);
  --shadow-up: 0 -10px 32px rgba(0, 0, 0, 0.5);
  --card-inset-border: rgba(255, 255, 255, 0.018);
  --surface-raised: rgba(11, 16, 25, 0.86);
  /* Semantic surfaces — both themes redefine these so components never hardcode
     a per-theme background (see body.light-theme). */
  --surface-panel: rgba(14, 20, 31, 0.97);
  --surface-bar: rgba(9, 13, 21, 0.96);
  --surface-overlay: rgba(15, 21, 32, 0.98);
  /* Knowledge-base migration-flow map (Knowledge.elm renderMigrationPath /
     renderMigrationSourceLabel / renderMigrationDot). Elm's SVG fill/stroke
     attributes reference these directly, so the animation follows the theme
     without Elm needing to know which one is active. */
  --kb-migmap-bg-radial: rgba(96, 165, 250, 0.10);
  --kb-migmap-bg-1: rgba(7, 14, 24, 0.98);
  --kb-migmap-bg-2: rgba(6, 10, 18, 0.98);
  --kb-migmap-border: rgba(255, 255, 255, 0.08);
  --kb-migmap-dest-fill: rgba(30, 60, 80, 0.45);
  --kb-migmap-dest-stroke: rgba(60, 120, 180, 0.48);
  --kb-migmap-path-stroke: rgba(80, 110, 140, 0.34);
  --kb-migmap-label: rgba(255, 180, 60, 0.72);
  --kb-migmap-dot-hot-settled: rgba(249, 115, 22, 0.58);
  --kb-migmap-dot-hot-flight: #f97316;
  --kb-migmap-dot-settled: rgba(96, 165, 250, 0.30);
  --kb-migmap-dot-flight: #60a5fa;
  /* History view's flow map. The outward arc is deliberately colourless: it is
     the same people leaving, and giving it a hue of its own would read as a
     seventh category beside the six grounds. */
  --migmap-out: rgba(226, 240, 248, 0.5);
  --migmap-unknown: rgba(150, 178, 196, 0.75);
  --migmap-land: rgba(28, 48, 64, 0.66);
  --migmap-land-stroke: rgba(88, 132, 162, 0.34);
  /* Pew Muslim-population choropleth (Knowledge.elm renderPewMap). Dark ramp:
     dark to bright as the share rises. The light ramp below runs the other way,
     pale to deep, because "more" means more ink on white and more light on
     black. Sharing one ramp across both themes made the palest countries — the
     ones with the SMALLEST Muslim populations — the loudest thing on the map. */
  /* The main map's own choropleth ramp, so the knowledge base's maps read as
     the same instrument as the atlas: deep blue for the low end, gold through
     the middle, red at the top. Two steps are darkened 5 % from the atlas's
     values because a white reading on them measured 4.4 against the fill;
     these clear 4.5. */
  --kb-pew-fill-30: #812738;
  --kb-pew-fill-20: #bc5044;
  --kb-pew-fill-10: #d5ba5a;
  --kb-pew-fill-7: #4690b2;
  --kb-pew-fill-3: #275978;
  --kb-pew-fill-0: #193246;
  --kb-pew-text-30: #ffffff;
  --kb-pew-text-20: #ffffff;
  --kb-pew-text-10: #0b1220;
  --kb-pew-text-7: #0b1220;
  --kb-pew-text-3: #ffffff;
  --kb-pew-text-0: #ffffff;
  --kb-pew-no-data: #1a2535;
  /* A full-white border on a dark map is the loudest thing in the card; this
     separates the countries without drawing the eye to the seams. */
  --kb-pew-stroke: rgba(255, 255, 255, 0.42);
  --kb-map-tip-bg: #0f1826;
  --kb-map-tip-border: rgba(255, 255, 255, 0.22);
  --kb-map-tip-ink: #eef4f8;
  /* Consanguinity choropleth (Knowledge.elm renderConsanguinityMap). As with
     the Pew map the bucket ramp is data-driven and identical in both themes;
     only the "0 % in source" fill flips, so it reads as empty rather than as
     the palest step of the ramp. */
  --kb-consang-zero: #eef2f7;
  --series-child: #c47a20;
  /* The second and third series colours of the area and origin line charts.
     The amber is the charts view's own second series colour (CHART_LINE_COLORS
     in app_charts_core.js), so the permit-grounds chart on a country page and
     the residence-permit chart in the charts view draw "family" in the same
     colour. Contrast on the dark card is 8.75:1, a shade better than the
     #d8a657 it replaces; the light theme keeps its darker override below,
     where #f0a030 would fall to 2.15:1 on white. */
  /* The chart palette. Every series colour on the site comes from here: the
     modules that draw charts used to carry their own copies, which is why the
     light theme could not reach them. Order is the order charts assign them. */
  --series-1: #70c3e2;
  --series-2: #f0a030;
  --series-3: #60c860;
  --series-4: #e05070;
  --series-5: #a080e0;
  --series-6: #40d0c0;
  --series-7: #f0d060;
  --series-8: #e08040;
  --series-9: #80c0e0;
  --series-10: #d080a0;
  /* What a series means, above which slot it sits in. The knowledge base's
     charts had paired blue with the Finnish background and coral with the
     foreign one in 68 series before these existed; the names make the
     pairing the rule for every renderer (REDESIGN_PLAN.md, phase 3). A
     chart that draws one series alone keeps the accent. Aliases of the
     palette, so the light theme's own values reach them unchanged. */
  --c-native: var(--series-1);
  --c-foreign: var(--series-4);
  --c-total: var(--tint-sand);
  --c-compare: var(--tint-cobalt);
  --c-projection: var(--series-child);
  --tint-azure: #5aa9d6;
  --tint-sky: #8ee6ff;
  --tint-coral: #ff8a65;
  --tint-sand: #e0a33e;
  --tint-cobalt: #4aa3ff;
  /* The type scale (REDESIGN_PLAN.md, phase 1). Sizes are rem so the browser's
     own text-size setting reaches them, and no HTML text goes under --fs-xs.
     The file had grown fifty-six different sizes, the commonest 11 px.
     --fs-chart is the one step under the floor, for axis and legend text
     inside a chart, and is still 12 px. */
  --fs-xs: 0.75rem;
  --fs-sm: 0.875rem;
  --fs-md: 1rem;
  --fs-lg: 1.125rem;
  --fs-xl: 1.375rem;
  --fs-2xl: 1.75rem;
  --fs-chart: 0.75rem;
  --lh-prose: 1.6;
  --lh-ui: 1.45;
  --lh-tight: 1.15;
  --area-line-1: #f0a030;
  --area-line-2: #9d8df1;
  /* The charts view's protection colour, so a permit ground looks the same in
     both places. 4.77:1 on the dark card. */
  --area-line-4: #e05070;
  /* Its study colour, for the same reason: the permit chart on a country page
     draws the same four grounds as the charts view. 6.99:1 on the dark card. */
  --area-line-5: #60c860;
  /* Panel edges and fills for the area and origin pages and the map's stats
     panel. These were written against a --panel-border token that was never
     defined anywhere, with a translucent-white fallback, so the fallback is
     what rendered: a hairline on the dark theme and nothing at all on the light one,
     which left every card on those pages with no edge and no fill. */
  --panel-border: rgba(255, 255, 255, 0.14);
  --panel-rule: rgba(255, 255, 255, 0.07);
  --panel-bg: rgba(255, 255, 255, 0.03);
}

/* Prose steps down one notch on a phone: 17 px in a 390 px column runs to
   thirty-odd characters a line. */
@media (max-width: 700px) {
  :root {
    --fs-md: 0.9375rem;
  }
}

* {
  box-sizing: border-box;
  scrollbar-color: rgba(112, 195, 226, 0.18) transparent;
  scrollbar-width: thin;
}

*::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

*::-webkit-scrollbar-track {
  background: transparent;
}

*::-webkit-scrollbar-thumb {
  background: rgba(112, 195, 226, 0.18);
  border-radius: var(--radius-pill);
}

*::-webkit-scrollbar-thumb:hover {
  background: rgba(112, 195, 226, 0.32);
}

*::-webkit-scrollbar-corner {
  background: transparent;
}

html,
body {
  width: 100%;
  height: 100%;
  margin: 0;
  overflow: hidden;
  font-family: var(--font-ui);
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  color: var(--text);
}

html {
  background: #000000;
}

body {
  background: transparent;
}

body {
  min-height: 100dvh;
}

body::before,
body::after {
  content: "";
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

body::before {
  display: none;
}

body::after {
  background: linear-gradient(rgba(0, 0, 0, 0.16), rgba(0, 0, 0, 0.3));
  mix-blend-mode: screen;
  opacity: 0.35;
}

button,
select,
input {
  font: inherit;
}

button {
  cursor: pointer;
}

select,
input,
button {
  appearance: none;
  border: 1px solid var(--border);
  background: rgba(10, 14, 22, 0.9);
  color: var(--text);
  outline: none;
  transition: border-color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease, transform 0.18s ease;
}

input[type="range"] {
  height: 20px;
  padding: 0;
  background: transparent;
  border: none;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
}

input[type="range"]::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: var(--radius-xs);
  background: rgba(112, 195, 226, 0.18);
}

input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 1px rgba(112, 195, 226, 0.2);
  cursor: pointer;
  margin-top: -10px;
}

input[type="range"]::-moz-range-track {
  height: 4px;
  border-radius: var(--radius-xs);
  background: rgba(112, 195, 226, 0.18);
}

input[type="range"]::-moz-range-thumb {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--bg);
  border: 2px solid var(--accent);
  box-shadow: 0 0 0 1px rgba(112, 195, 226, 0.2);
  cursor: pointer;
}

select:hover,
input:hover,
button:hover,
select:focus,
input:focus,
button:focus-visible {
  border-color: rgba(112, 195, 226, 0.36);
  box-shadow: 0 0 0 1px rgba(112, 195, 226, 0.12), inset 0 0 0 1px rgba(112, 195, 226, 0.06);
}

input[type="range"]:hover,
input[type="range"]:focus {
  border-color: transparent;
  box-shadow: none;
}

/* One focus ring for everything reached by keyboard. The rules above take the
   outline off every control and put back a 1 px ring at 12 % opacity, and the
   sliders, the comparison countries and a few summaries showed nothing at all.
   A component that draws its own focus mark at higher specificity keeps it. */
html :focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Value labels drawn in a series colour are written darker on the light
   theme: the colours are chosen for bars, and as 12 px text on white they
   read at 2.2-4.1:1. */
body.light-theme .chart-value {
  filter: brightness(0.62);
}

.chart-muted-note {
  color: var(--muted-soft);
}

/* The history filter's chip is a dark wash, which the light theme never
   lightened: its label read at 3.2:1 on the grey it made. */
body.light-theme .hist-filter {
  background: rgba(10, 18, 28, 0.05);
}

/* Leaflet writes an inline outline:none onto whatever has focus whenever a key
   is pressed inside the map (its preventOutline helper, meant for drags), so
   the zoom buttons and the map itself never showed keyboard focus. Only
   !important beats an inline style, and :focus-visible keeps a mouse click
   free of the ring, which is what Leaflet was after. */
.leaflet-container a:focus-visible {
  outline: 2px solid var(--accent) !important;
  outline-offset: 2px;
}

.leaflet-container:focus-visible {
  outline: 2px solid var(--accent) !important;
  outline-offset: -3px;
}

/* Frameless buttons on the front page. The ring above is drawn for controls
   that have a box, and on a button that has none it invents one: around the
   centred live counter, around a key figure's cell, around a panel heading.
   Each of these shows its hover in its own background or in its label's
   colour, so the ring has nothing to add. Hover only: a keyboard user still
   gets the ring on focus. */
.home-hero-note-link:hover,
.home-live:hover,
.home-stat:hover,
.home-cell-title--nav:hover,
.home-panel:hover {
  border-color: transparent;
  box-shadow: none;
}

button:active {
  transform: translateY(1px);
}

option {
  background: #090d15;
  color: var(--text);
}

[hidden] {
  display: none !important;
}

/* Fix leaflet feature click outline */
path.leaflet-interactive:focus {
  outline: none;
}

.leaflet-container,
.leaflet-grab,
.leaflet-drag-target {
  cursor: default;
}

.leaflet-container.is-boundary-hovering,
.leaflet-container.is-boundary-hovering .leaflet-interactive {
  cursor: pointer !important;
}

.leaflet-dragging .leaflet-grab,
.leaflet-dragging .leaflet-grab .leaflet-interactive,
.leaflet-dragging .leaflet-marker-draggable {
  cursor: grabbing !important;
}

.app-shell {
  position: relative;
  display: grid;
  grid-template-rows: minmax(0, 1fr);
  grid-template-columns: 1fr;
  width: 100vw;
  height: 100dvh;
  overflow: hidden;
  z-index: 1;
}

.app-shell::before {
  display: none;
}

/* Every stage is hidden by default and re-shown by the rule for its own view.
   #infoStage was missing from this list, so nothing ever hid it: it was only
   ever switched ON at body[data-view="info"], and on any other view it fell back
   to a <section>'s default display:block and stayed on screen at full height,
   overlapping whatever you had navigated to. */
body[data-view] #homeStage,
body[data-view] #mapStage,
body[data-view] #chartStage,
body[data-view] #europeStage,
body[data-view] #areaStage,
body[data-view] #originStage,
body[data-view] #cartogramStage,
body[data-view] #infoStage,
body[data-view] #knowledgeStage,
body[data-view] #historyStage,
body[data-view] #toolsStage,
body[data-view] #utilityPanel,
body[data-view] #menuToggleBtn,
body[data-view] #chartTocToggleBtn {
  display: none;
}

body[data-view="map"] #mapStage {
  display: grid;
}

body[data-view="map"] #utilityPanel,
body[data-view="cartogram"] #utilityPanel {
  display: flex;
  flex-direction: column;
}

body[data-view="map"] #menuToggleBtn,
body[data-view="cartogram"] #menuToggleBtn {
  display: flex;
}

/* TOC toggles are available on desktop too (like the map menu), so the contents
   sidebar can be hidden to give the charts / knowledge base more room. */
body[data-view="chart"] #chartTocToggleBtn {
  display: flex;
}

body[data-view="home"] #homeStage {
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow-y: auto;
}

body[data-view="chart"] #chartStage,
body[data-view="europe"] #europeStage,
body[data-view="area"] #areaStage,
body[data-view="origin"] #originStage,
body[data-view="info"] #infoStage,
body[data-view="history"] #historyStage,
body[data-view="tools"] #toolsStage,
body[data-view="knowledge"] #knowledgeStage {
  display: flex;
}

/* The nav now floats over the content (absolute),
so panel views need their
   content to start below it. Map/cartogram stay full-bleed (the map fills the
   whole viewport and the nav floats over it). */
body[data-view="chart"] #chartStage,
body[data-view="europe"] #europeStage,
body[data-view="area"] #areaStage,
body[data-view="origin"] #originStage,
body[data-view="info"] #infoStage,
body[data-view="history"] #historyStage,
body[data-view="tools"] #toolsStage,
body[data-view="knowledge"] #knowledgeStage {
  padding-top: var(--top-bar-height);
}

/* Area search: one field, up to eight results, shared by the front page and
   the area view. Results float over the page rather than pushing it down. */
.area-search {
  position: relative;
  width: min(560px, 100%);
  display: flex;
  flex-direction: column;
  /* The block sat almost against the nav bar. The area page gets no top
     padding of its own -- the Elm island replaces .area-mount, so that rule's
     padding never lands -- so the breathing room has to come from here. */
  gap: 12px;
  margin: 22px 0 26px;
}

.area-search-label {
  font-size: var(--fs-sm);
  color: var(--muted);
  letter-spacing: 0.02em;
  text-align: center;
}

.area-search-input {
  width: 100%;
  padding: 12px 16px;
  text-align: center;
  border-radius: var(--radius-pill);
  border: 1px solid var(--panel-border);
  background: rgba(255, 255, 255, 0.04);
  color: inherit;
  font: inherit;
  font-size: var(--fs-md);
}

.area-search-results {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 60;
  list-style: none;
  margin: 0;
  padding: 0;
  background: var(--surface-overlay);
  backdrop-filter: blur(10px);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  box-shadow: var(--menu-surface-shadow, 0 18px 40px rgba(0, 0, 0, 0.45));
  overflow: hidden;
  text-align: left;
}

.area-search-link {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 14px;
  padding: 10px 16px;
  color: inherit;
  text-decoration: none;
}

.area-search-result + .area-search-result .area-search-link {
  border-top: 1px solid var(--panel-rule);
}

.area-search-link:hover,
.area-search-link:focus-visible {
  background: rgba(255, 255, 255, 0.06);
}

.area-search-meta {
  color: var(--muted);
  font-size: var(--fs-sm);
  white-space: nowrap;
}

.area-search-empty {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  right: 0;
  z-index: 60;
  color: var(--muted);
  font-size: var(--fs-sm);
  margin: 0;
  padding: 10px 16px;
  background: var(--surface-overlay);
  backdrop-filter: blur(10px);
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-md);
  box-shadow: var(--menu-surface-shadow, 0 18px 40px rgba(0, 0, 0, 0.45));
  text-align: center;
}

/* ── Area view ──────────────────────────────────────────────────────────────
   One postal area or municipality on its own page. Reached from the front-page
   search, so it is a reading page rather than a control surface: a single
   column, cards for each group of figures, no sidebar. */
.area-stage {
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  overflow-y: auto;
}

.area-mount {
  width: min(760px, 100%);
  padding: 28px 20px 64px;
}

.area-page {
  display: flex;
  flex-direction: column;
  gap: 18px;
  /* Elm replaces the mount node, so the width has to live on the rendered
     page too, or it sizes itself around the length of an area name. */
  width: min(760px, 100%);
  padding: 28px 20px 64px;
}

/* Charts on the page: a fixed 320x132 viewBox from Elm, scaled to whatever
   width the card has. Everything about them is stroke and text colour -- the
   geometry is the island's. */
.area-charts {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* Title on the left, the chart's own control on the right, wrapping to two
   rows on a phone rather than squeezing the title. */
.area-chart-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px 14px;
  flex-wrap: wrap;
}

.area-chart-svg {
  display: block;
  width: 100%;
  height: auto;
  margin-top: 10px;
  overflow: visible;
}

/* Labels live in viewBox units, so they grow with the box. A labelled chart
   used to be capped at 460 px to hold them down, which left it sitting in the
   left two thirds of a wide card. It fills the card now and the type is stepped
   down instead, in the container queries below. */
.area-chart-svg--labelled {
  margin-inline: auto;
}

.area-chart-point-label {
  fill: var(--text);
  font-size: var(--fs-xs);
  font-weight: 600;
}

.area-chart-line {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
}

.area-chart-line--0 { stroke: var(--accent); }
.area-chart-line--1 { stroke: var(--area-line-1); }
.area-chart-line--2 { stroke: var(--area-line-2); }
/* A fourth, for the charts that put every population concept on one axis. */
.area-chart-line--3 { stroke: #5fb98e; }
/* Slots 4-6 are the charts view's own colours for work, family and protection
   (CHART_LINE_COLORS in app_charts_core.js). A permit ground has to look the
   same wherever it is drawn: the origin page's grounds chart asks for these by
   number so it matches the Migri chart in the charts view rather than picking
   up this palette's first three. */
/* A marker per data point is legible on a wide plot and a caterpillar on a
   narrow one: thirty-five years of annual points on a phone leaves the circles
   touching, and the line they are meant to mark reads worse than it would
   alone. The crosshair and the readout are how a value gets picked out at that
   width anyway. Dropped below the same 700px the charts view uses elsewhere. */
@media (max-width: 700px) {
  .chart-point-marker {
    display: none;
  }
}

/* Slot 4 is the charts view's protection colour (CHART_LINE_COLORS in
   app_charts_core.js). Work and family need no slot of their own: --accent is
   already that view's #70c3e2 and --area-line-1 its #f0a030, so a permit ground
   drawn here matches the Migri chart. Protection had no counterpart in this
   palette, which left it violet on a country page and red in the charts view. */
.area-chart-line--4 { stroke: var(--area-line-4); }
.area-chart-line--5 { stroke: var(--area-line-5); }

.area-chart-line.is-projected {
  stroke-dasharray: 5 4;
  opacity: 0.75;
}

.area-chart-dot {
  stroke: none;
}

.area-chart-dot.area-chart-line--0 { fill: var(--accent); }
.area-chart-dot.area-chart-line--1 { fill: var(--area-line-1); }
.area-chart-dot.area-chart-line--2 { fill: var(--area-line-2); }
.area-chart-dot.area-chart-line--3 { fill: #5fb98e; }
.area-chart-dot.area-chart-line--4 { fill: var(--area-line-4); }
.area-chart-dot.area-chart-line--5 { fill: var(--area-line-5); }

.area-chart-baseline,
.area-chart-zero {
  stroke: var(--border);
  stroke-width: 1;
}

.area-chart-zero {
  stroke-dasharray: 3 3;
}

/* Sizes are in viewBox units, and the 320-unit box is drawn about 760 px wide,
   so everything here is scaled ~2.4x on screen: 9px rendered as ~22px and the
   axis shouted over the data. 5 lands near 12px, which is what the rest of the
   card's small text is. */
.area-chart-axis {
  fill: var(--muted);
  font-size: 5px;
}

/* Sizes are viewBox units, so what the reader sees is this times the SVG's
   scale -- and the card is ~324 px wide on a phone against ~690 on a desktop.
   One fixed size therefore renders at 6 px on mobile and 14 px on desktop; the
   readout was unreadable on exactly the device that has no hover to fall back
   on. The breakpoints below hold the RENDERED size near 13 px instead. Row
   spacing is in em, so it follows automatically. */
.area-chart-readout {
  fill: var(--text);
  font-size: 5.5px;
  font-weight: 600;
}

.area-chart-readout-value {
  font-weight: 600;
}

/* Keyed on the CARD's width, not the viewport's. Viewport breakpoints were the
   obvious try and they are wrong: the area page's column is narrower than the
   origin page's, so at the same 1400 px viewport one card renders the SVG ~680
   wide and the other ~560, and a single rule gave 14 px on one page and 9 px on
   the other. A container query measures the thing that actually sets the scale.
   Targets ~13 px rendered in every bucket. */
.area-chart-card {
  container-type: inline-size;
}

@container (max-width: 760px) {
  .area-chart-axis {
    font-size: 5.6px;
  }

  .area-chart-readout {
    font-size: 6px;
  }
}

/* A labelled chart now scales like any other -- about 2.2x in a 720 px card --
   so its type is stepped down to land near the size it was drawn at. Two steps
   rather than one: between 500 and 700 the scale is roughly 1.7, past 700 about
   2.2, and a single size would be wrong at one end. */
@container (min-width: 500px) {
  .area-chart-svg--labelled .area-chart-axis {
    font-size: 6.4px;
  }

  .area-chart-svg--labelled .area-chart-point-label {
    font-size: 6.8px;
  }
}

/* 640 rather than 700: the query measures the card's content box, which is its
   720 px less its padding. */
@container (min-width: 640px) {
  .area-chart-svg--labelled .area-chart-axis {
    font-size: 5.2px;
  }

  .area-chart-svg--labelled .area-chart-point-label {
    font-size: 5.6px;
  }
}

@container (max-width: 600px) {
  .area-chart-axis {
    font-size: 9.5px;
  }

  .area-chart-readout {
    font-size: 10px;
  }
}

@container (max-width: 420px) {
  .area-chart-axis {
    font-size: 11px;
  }

  .area-chart-readout {
    font-size: 12px;
  }
}

/* Same three colours the lines and the legend swatches use, so the number and
   the line it came from are the same colour. A single-series chart still reads
   fine because --accent is its line colour too. */
.area-chart-readout--0 { fill: var(--accent); }
.area-chart-readout--1 { fill: #d8a657; }
.area-chart-readout--2 { fill: #9d8df1; }
.area-chart-readout--3 { fill: #5fb98e; }

/* A tspan inside .area-chart-readout, so it inherits the size the breakpoints
   set and only needs its colour. Carrying its own font-size meant this later
   rule beat the media queries above and the year stayed 6 px on mobile while
   the values grew. */
.area-chart-readout-dim {
  fill: var(--muted);
}

.area-chart-guide {
  stroke: var(--accent);
  stroke-width: 0.6;
  opacity: 0.5;
}

.area-chart-hit {
  fill: transparent;
  cursor: crosshair;
}

.area-chart-dot {
  stroke: var(--bg);
  stroke-width: 0.8;
}

/* The hit rects sit on top of everything, so the guide and dots underneath
   would otherwise swallow the pointer and flicker between adjacent years. */
.area-chart-guide,
.area-chart-dot,
.area-chart-readout,
.area-chart-readout-dim {
  pointer-events: none;
}

.area-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 8px;
  font-size: var(--fs-sm);
  color: var(--muted);
}

.area-chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.area-chart-swatch {
  width: 12px;
  height: 3px;
  border-radius: var(--radius-xs);
  background: var(--accent);
}

.area-chart-swatch--1 { background: var(--area-line-1); }
.area-chart-swatch--2 { background: var(--area-line-2); }
.area-chart-swatch--3 { background: #5fb98e; }
.area-chart-swatch--4 { background: var(--area-line-4); }
.area-chart-swatch--5 { background: var(--area-line-5); }

/* A footnote, not body copy: the same weight as the hint under a sidebar
   dropdown (.chart-std-hint), so it reads as small print under the chart
   instead of competing with it. */
.area-chart-note {
  margin: 8px 0 0;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
  opacity: 0.85;
}

/* The link is a .chart-xlink now -- the same control the map's info box and the
   charts view use -- so only its placement is defined here. The old plain-text
   .area-chart-link is gone; nothing else used it. */
.area-chart-link-row {
  display: flex;
  margin-top: 12px;
}

.area-chart-link-row .chart-xlink {
  width: 100%;
}

/* The search box stays narrower than the page, centred over it. */
.area-page > .area-search {
  align-self: center;
}

.area-page--empty {
  padding-top: 40px;
}

.area-empty {
  color: var(--muted);
  font-size: var(--fs-md);
  margin: 0;
}

/* The view's own front (Area.elm, indexPage): ranked link lists of every
   page it has, two columns where there is room. The rank comes from a
   counter, so the list stays an <ol> a screen reader can count. */
.area-index-lead {
  color: var(--muted);
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
  margin: 10px 0 0;
  max-width: 62ch;
}

.area-index-lists {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 28px 40px;
  margin-top: 8px;
}

/* The rows are capped (max-width above), so a lone list does not stretch a
   name and its number to the opposite edges of the page; the title above
   keeps the full width. A cap in the track's minmax would count against the
   column count instead and fold two lists into one column. */

.area-index-list-title {
  font-size: var(--fs-lg);
  margin: 0 0 4px;
}

.area-index-list-note {
  color: var(--muted);
  font-size: var(--fs-xs);
  margin: 0 0 10px;
}

.area-index-entries {
  list-style: none;
  margin: 0;
  padding: 0;
  counter-reset: rank;
  max-width: 440px;
}

.area-index-link {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--panel-rule);
  color: inherit;
  text-decoration: none;
  font-size: var(--fs-sm);
}

.area-index-link::before {
  counter-increment: rank;
  content: counter(rank);
  flex: 0 0 2.2em;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.area-index-label {
  flex: 1 1 auto;
}

.area-index-link:hover .area-index-label,
.area-index-link:focus-visible .area-index-label {
  color: var(--accent);
}

.area-index-value {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.area-title {
  font-size: clamp(1.625rem, 4vw, 2.25rem);
  line-height: 1.15;
  margin: 0;
  letter-spacing: -0.01em;
}

.area-context {
  color: var(--muted);
  font-size: var(--fs-md);
  margin: 6px 0 0;
}

/* The municipality, not the postal code, is what a reader recognises and what
   they go to next: it reads at the size of the line above the meta and carries
   the page's text colour rather than the muted one. */
.area-context-name {
  color: var(--text);
  font-size: var(--fs-md);
  font-weight: 600;
}

.area-context-link {
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, currentColor 40%, transparent);
}

.area-context-link:hover,
.area-context-link:focus-visible {
  border-bottom-color: currentColor;
}

.area-headline {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.area-headline-value {
  font-size: clamp(2.375rem, 7vw, 3.5rem);
  font-weight: 650;
  line-height: 1;
  color: var(--accent, #7fd4ff);
}

.area-headline-label {
  color: var(--muted);
  font-size: var(--fs-sm);
}

.area-card {
  background: var(--panel-bg);
  border: 1px solid var(--panel-rule);
  border-radius: var(--radius-md);
  padding: 16px 18px;
}

.area-card-title {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  margin: 0 0 12px;
}

/* The context card carries a heading and a source line, unlike the page's own
   rows card, so its note gets the same quiet treatment the chart notes have. */
.area-context-note {
  margin: 10px 0 0;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.area-rows,
.area-related-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

/* Helsinki has 83 postal areas and Espoo 60, so the full list made this card
   taller than everything else on the page put together. Capped and scrolled,
   the same treatment the ranking window gets; `overscroll-behavior` stops a
   trackpad carrying on into the page once the list ends. */
/* No cap and no scrolling: the ranking is the one box on this page that
   scrolls its own content, so this list runs to its natural length and the
   page carries it. A second scroll box meant a reader dragging over this one
   had to guess which of the two would move. */
.area-related-list {
  overflow: visible;
}

/* The last row's rule would otherwise sit against the scroll edge. */
.area-related-list .area-related:last-child {
  border-bottom: 0;
}

.area-row,
.area-related {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 7px 0;
  border-bottom: 1px solid var(--panel-rule);
  font-size: var(--fs-md);
}

.area-row:last-child,
.area-related:last-child {
  border-bottom: 0;
}

.area-row-label,
.area-related-value {
  color: var(--muted);
}

.area-row-value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* A row whose value is a set of links (the languages of a background country).
   Underlined rather than coloured: the value column is where the reader's eye
   already is, and a row of accent-coloured text there would compete with the
   figures around it. */
.area-row-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, currentColor 45%, transparent);
}

.area-row-link:hover,
.area-row-link:focus-visible {
  border-bottom-color: currentColor;
}

.area-related-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.area-related-link:hover {
  border-bottom-color: currentColor;
}

.area-bar-row {
  display: grid;
  grid-template-columns: minmax(90px, 150px) 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 4px 0;
  font-size: var(--fs-sm);
}

/* Each row was its own grid, so the value column was as wide as that row's own
   text and every track ended at a different place. Subgrid gives all the rows
   one shared set of columns, which lines the track ends up. Behind @supports
   because a browser that does not know the keyword drops the declaration and
   stacks the three cells into one column. */
@supports (grid-template-columns: subgrid) {
  .area-bars {
    display: grid;
    grid-template-columns: minmax(90px, 150px) 1fr auto;
    column-gap: 10px;
  }

  .area-bar-row {
    grid-template-columns: subgrid;
    grid-column: 1 / -1;
  }
}

/* The site's tooltip, drawn from a data-tip attribute rather than left to the
   browser's title bubble: that one waits half a second, arrives in the system's
   colours and puts a long sentence on one line. This appears on hover with no
   delay, in the site's own panel, and wraps. Anything with data-tip gets it,
   which is why the attribute also makes the element a positioning context.
   `data-tip-pos="top"` opens it upwards, for controls near the bottom of what
   they sit in. Keyboard reaches it through :focus-within, so an icon button or
   a row with a link inside shows the same text when tabbed to. */
[data-tip] {
  position: relative;
}

/* An empty data-tip is not a tooltip. The selector matches on the attribute
   being PRESENT, so a caller that sets it to "" (a bar row with no note, say)
   would otherwise pop an empty bubble on hover. */
[data-tip]:not([data-tip=""]):hover::after,
[data-tip]:not([data-tip=""]):focus-within::after {
  content: attr(data-tip);
  position: absolute;
  left: 0;
  top: calc(100% - 2px);
  z-index: 20;
  max-width: min(420px, 92vw);
  width: max-content;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-strong);
  color: var(--text);
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  white-space: normal;
  box-shadow: var(--shadow-md);
  pointer-events: none;
}

/* Opens upwards: the last bar rows in a card, and anything that asks. */
.area-bar-row[data-tip]:nth-last-child(-n + 2):hover::after,
.area-bar-row[data-tip]:nth-last-child(-n + 2):focus-within::after,
[data-tip][data-tip-pos="top"]:hover::after,
[data-tip][data-tip-pos="top"]:focus-within::after {
  top: auto;
  bottom: calc(100% - 2px);
}

/* Anchored to the right edge instead of the left, for controls near the right
   edge of the window: the top bar's language, theme and share buttons. */
[data-tip][data-tip-align="right"]:hover::after,
[data-tip][data-tip-align="right"]:focus-within::after {
  left: auto;
  right: 0;
}

/* The same thing decided at hover time rather than written into the markup.
   initTooltipFlip() measures what the bubble needs against what is left before
   the edge that would cut it, and sets this when the sentence would not fit --
   which is how a control that only sometimes sits at the edge (a card's own
   button on a phone, the to-top button over a scrolled page) keeps its text.
   Authored data-tip-align always wins: the script leaves it alone. */
[data-tip][data-tip-flip="right"]:hover::after,
[data-tip][data-tip-flip="right"]:focus-within::after {
  left: auto;
  right: 0;
}

/* Same thing one level in. A chart's title row ends flush against the right
   edge of .chart-main-area, which clips, so a left-anchored bubble on any of
   the trailing controls lost its tail: 128px of the knowledge-base link's
   sentence, 83px of the share button's. These open leftwards. The filters
   toggle sits at the other end of the row and keeps the default. The attribute
   is in the selector only to outrank the base rule wherever it is read. */
.chart-title-row .chart-xlink[data-tip]:hover::after,
.chart-title-row .chart-xlink[data-tip]:focus-within::after,
.chart-title-row .chart-share-btn[data-tip]:hover::after,
.chart-title-row .chart-share-btn[data-tip]:focus-within::after {
  left: auto;
  right: 0;
}

.area-bar-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Names are truncated with an ellipsis, so a link has to look like one without
   relying on the reader recognising a cut-off place name. Underlined on hover
   and focus rather than always, which would stripe the whole column. */
.area-bar-name--link {
  color: inherit;
  text-decoration: none;
}

.area-bar-name--link:hover,
.area-bar-name--link:focus-visible {
  color: var(--accent);
  text-decoration: underline;
}

.area-bar-track {
  background: rgba(255, 255, 255, 0.07);
  border-radius: var(--radius-pill);
  height: 8px;
  overflow: hidden;
}

.area-bar-fill {
  background: var(--accent, #7fd4ff);
  height: 100%;
  border-radius: var(--radius-pill);
}

.area-bar-value {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* The school table is the one wide thing on the page, so it scrolls inside its
   own box instead of widening the column. */
/* Prose standing in for a table: an explanation of why a card is empty. */
.area-card-note {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
  max-width: 74ch;
}

/* Sideways only. The table is the one wide thing on the page and still scrolls
   horizontally, but it no longer caps its own height: a municipality's hundred
   schools make a long card rather than a second vertical scroll box competing
   with the ranking. */
.area-table-wrap {
  overflow-x: auto;
  /* `clip`, not `visible`: a `visible` axis next to a scrolling one computes
     back to `auto`, which makes the box a vertical scroll container with
     nothing to scroll -- and that swallows the drag instead of passing it to
     the page. `clip` is not a scroll container. Nothing is actually clipped:
     the wrapper has no height of its own any more, so it is as tall as its
     table. */
  overflow-y: clip;
}

/* Not sticky any more: the header stayed put while the body scrolled under it
   inside the wrapper, and the wrapper no longer scrolls vertically. Left
   sticky it would pin itself to the top of the window instead and ride up
   under the floating bar. */
.area-table thead th {
  background: var(--panel);
}

/* The school name links to the area it sits in. Underlined on hover only, so a
   hundred-row table does not read as a wall of blue. */
.area-school-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px solid transparent;
}

.area-school-link:hover,
.area-school-link:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.area-table {
  border-collapse: collapse;
  width: 100%;
  font-size: var(--fs-sm);
}

.area-table th,
.area-table td {
  text-align: left;
  padding: 6px 12px 6px 0;
  border-bottom: 1px solid var(--panel-rule);
  white-space: nowrap;
}

.area-table th {
  color: var(--muted);
  font-weight: 600;
}

.area-table td + td {
  font-variant-numeric: tabular-nums;
}

/* The way back to the list the page belongs to, above the title. Plain text
   with an arrow, not a pill: it is a route marker, not an action. */
.area-back {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin: 2px 0 10px;
  color: var(--muted);
  font-size: var(--fs-sm);
  text-decoration: none;
}

.area-back:hover,
.area-back:focus-visible {
  color: var(--text);
}

.area-back-arrow {
  font-size: 1.1em;
  line-height: 1;
}

.area-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.area-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--panel-border);
  color: inherit;
  text-decoration: none;
  font-size: var(--fs-sm);
}

.area-link:hover {
  border-color: var(--accent, #7fd4ff);
}

/* The promoted action, directly under the page title. A filled button rather
   than another outlined pill, because everything else on the page is an
   outlined pill and the point is that this one is not. */
.area-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin: 14px 0 4px;
}

.area-action {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 20px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--accent, #7fd4ff);
  background: var(--accent, #7fd4ff);
  color: #06131b;
  text-decoration: none;
  font-size: var(--fs-md);
  font-weight: 620;
  letter-spacing: 0.01em;
  transition: filter 120ms ease, transform 120ms ease;
}

.area-action::before {
  /* A map pin, so the button reads as "go to the map" before the label does. */
  content: "";
  width: 13px;
  height: 13px;
  flex: none;
  background: currentColor;
  -webkit-mask: var(--pin-mask) center / contain no-repeat;
  mask: var(--pin-mask) center / contain no-repeat;
  --pin-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 2a7 7 0 0 0-7 7c0 5.25 7 13 7 13s7-7.75 7-13a7 7 0 0 0-7-7zm0 9.5A2.5 2.5 0 1 1 12 6.5a2.5 2.5 0 0 1 0 5z'/%3E%3C/svg%3E");
}

.area-action:hover,
.area-action:focus-visible {
  filter: brightness(1.08);
  transform: translateY(-1px);
}

.area-source {
  color: var(--muted);
  font-size: var(--fs-sm);
  margin: 0;
}

.area-source a {
  color: inherit;
}

@media (max-width: 920px) {
  .area-mount,
  .area-page {
    padding: 18px 14px 56px;
  }

  .area-bar-row,
  .area-bars {
    grid-template-columns: minmax(80px, 120px) 1fr auto;
  }

  @supports (grid-template-columns: subgrid) {
    .area-bar-row {
      grid-template-columns: subgrid;
    }
  }
}

/* Clearance below the floating nav. The hero is the page's opening statement
   and wants air above it, not just separation from the bar. */
body[data-view="home"] #homeStage {
  padding-top: calc(var(--top-bar-height) + 64px);
}

body[data-view="cartogram"] #cartogramStage {
  display: block;
}

body[data-view="cartogram"] #overlayLayersSection {
  display: none;
}

html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #homeStage,
html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #mapStage,
html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #chartStage,
html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #europeStage,
html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #cartogramStage,
html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #knowledgeStage,
html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #utilityPanel,
html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #menuToggleBtn,
html:not(.js) body:has(:is(#homeStage, #mapStage, #chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #chartTocToggleBtn {
  display: none;
}

html:not(.js) body:has(#homeStage:target) #homeStage {
  display: flex;
  flex-direction: column;
  align-items: center;
  overflow-y: auto;
}

html:not(.js) body:has(#mapStage:target) #mapStage,
html:not(.js) body:has(#mapStage:target) #utilityPanel {
  display: grid;
}

html:not(.js) body:has(#mapStage:target) #menuToggleBtn {
  display: flex;
}

html:not(.js) body:has(#chartStage:target) #chartStage,
html:not(.js) body:has(#europeStage:target) #europeStage,
html:not(.js) body:has(#knowledgeStage:target) #knowledgeStage {
  display: flex;
}

html:not(.js) body:has(#cartogramStage:target) #cartogramStage {
  display: block;
}

html:not(.js) body:has(#cartogramStage:target) #utilityPanel {
  display: grid;
}

html:not(.js) body:has(#cartogramStage:target) #menuToggleBtn {
  display: flex;
}

/* ── Top bar ── */
/* The first tab stop on every page. Off screen until it has focus, then it
   sits over the top bar so the ten controls after it can be skipped in one
   press. The target is the stage that is showing: app_charts_core.js moves the
   href as the view changes, because the stages are siblings and only one is
   visible at a time. */
.skip-link {
  position: absolute;
  top: -100px;
  left: 12px;
  z-index: 10000;
  padding: 10px 14px;
  border-radius: var(--radius-sm);
  background: var(--accent);
  color: #06222c;
  font-weight: 700;
  text-decoration: none;
}

.skip-link:focus-visible {
  top: 12px;
  outline: 2px solid var(--text);
  outline-offset: 2px;
}

/* The light theme's accent is a dark blue, so the label flips to white there. */
body.light-theme .skip-link {
  color: #ffffff;
}

.top-bar {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 730;
  display: block;
  height: var(--top-bar-height);
  padding: 0;
  background: transparent;
  border-bottom: none;
  pointer-events: none;
}

/* The bar floats over the content, so its empty/transparent areas must let
   clicks through; only the actual controls stay interactive. Scope to
   .top-bar-layout, not .top-bar — the Elm topbar renders .top-bar-layout--elm
   as a direct app-shell child (its buttons are NOT inside .top-bar). */
.top-bar-layout {
  pointer-events: none;
}
.top-bar-layout a,
.top-bar-layout button,
.top-bar-layout summary,
.top-bar-layout select,
.top-bar-layout input,
.top-bar-layout label,
.top-bar-tabs,
.top-bar-layout .lang-dropdown,
.top-bar-layout .theme-toggle-btn,
.top-bar-layout .mobile-nav-dropdown,
.top-bar-layout [role="button"] {
  pointer-events: auto;
}

.top-bar-layout.top-bar-layout--elm {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: 730;
  height: var(--top-bar-height);
  background: transparent;
  border-bottom: none;
  pointer-events: none;
}

.top-bar-layout {
  position: relative;
  display: grid;
  /* Equal side columns, each at least as wide as its own buttons, so the nav
     pill sits in the middle of the BAR rather than wherever the wider side
     leaves it. The left column used to be a plain `1fr` against a max-content
     right, which meant it swallowed every spare pixel and shoved the pill
     right: 751px off centre on a 2560px screen, 431px on 1920.

     The `max-content` floor is what keeps the old phone bug fixed. With three
     freely flexible columns the centre's `auto` won the space on a narrow
     screen — a long view name ("Tietopankki") widened it until the right
     column was narrower than the search, language and theme buttons inside it,
     and they overflowed leftwards across the pill. Here the sides never shrink
     below their own contents; when the bar runs out of room the centre gives
     way instead, which is the right thing to lose. */
  grid-template-columns: minmax(max-content, 1fr) minmax(0, auto) minmax(max-content, 1fr);
  align-items: center;
  gap: 10px;
  width: 100%;
  height: 100%;
  padding: 0 calc(var(--safe-right) + 12px) 0 calc(var(--safe-left) + 10px);
}

.top-bar-zone {
  display: flex;
  align-items: center;
  min-width: 0;
}

.top-bar-left {
  justify-content: flex-start;
  gap: 8px;
}

.top-bar-center {
  justify-content: center;
}

.top-bar-right {
  justify-content: flex-end;
  gap: 8px;
}

.top-bar-right .lang-dropdown,
.top-bar-right .theme-toggle-btn {
  right: auto;
}

.top-bar-toggle {
  position: static;
  z-index: 3;
  transform: none;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: var(--btn-height) !important;
  padding: 0;
  line-height: 1;
  border-radius: var(--radius-pill);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

.top-bar-toggle:hover {
  background: var(--control-hover-bg);
  border-color: var(--control-hover-border);
  color: var(--text);
  box-shadow: var(--control-shadow-ring);
}

.top-bar-toggle:active {
  transform: none;
  background: var(--control-hover-bg);
  border-color: var(--control-hover-border);
  color: var(--text);
}

/* Only inside the bar: the same classes serve panels and chart cards, where
   an opaque control is right because it sits on a card, not over the map. */
.top-bar-layout .top-bar-toggle,
.top-bar-layout .map-share-btn,
.top-bar-layout .lang-dropdown-trigger,
.top-bar-layout .theme-toggle-btn,
.top-bar-layout .mobile-nav-trigger {
  background: var(--topbar-surface);
  border-color: var(--topbar-surface-border);
  backdrop-filter: blur(10px);
}

.top-bar-icon {
  width: 22px;
  height: 22px;
  pointer-events: none;
}

.top-bar-icon * {
  pointer-events: none;
}

.top-bar-icon.is-hidden {
  display: none;
}

.top-bar-tabs-mount {
  position: static;
  width: auto;
  max-width: 100%;
  transform: none;
  z-index: 2;
  display: flex;
  justify-content: center;
  min-width: 0;
  pointer-events: none;
}

.top-bar-tabs {
  flex: 0 1 auto;
  display: flex;
  max-width: 100%;
  min-width: 0;
  gap: 4px;
  background: var(--topbar-surface);
  backdrop-filter: blur(10px);
  padding: 3px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--topbar-surface-border);
  align-items: center;
  overflow: hidden;
  pointer-events: auto;
}

.top-bar-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 30px;
  min-width: 0;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  border: none;
  background: transparent;
  color: var(--muted);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
  cursor: pointer;
  transition: color 0.2s ease, background 0.2s ease;
  position: relative;
  z-index: 1;
}

.top-bar-tab:hover {
  color: var(--text);
}

.top-bar-tab.is-active {
  background: transparent;
  color: var(--accent);
  font-weight: 600;
  box-shadow: none;
}

/* Migri chart view toggle (by country / sex / age) */
.migri-view-toggle {
  display: inline-flex;
  gap: 2px;
  padding: 3px;
  margin: 0 0 10px;
  background: var(--control-bg);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-md);
}
.migri-view-btn {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 5px 12px;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background 0.15s ease, color 0.15s ease;
}
.migri-view-btn:hover {
  color: var(--text);
  background: rgba(112, 195, 226, 0.05);
}
.migri-view-btn.is-active {
  color: var(--accent);
  background: rgba(112, 195, 226, 0.12);
  box-shadow: var(--shadow-sm);
}
/* In sex/age breakdown mode the country picker does not apply, so hide it. */
.migri-mode-breakdown .chart-sidebar-mun {
  display: none;
}

/* Map/Cartogram render as one pill with a sliding indicator behind the active
   label, rather than two separate tab buttons — app_ui.js's
   syncTopBarGeoIndicator positions #topBarGeoIndicator with a translateX. */
.top-bar-tab-pair {
  position: relative;
  display: inline-flex;
  align-items: center;
  background: rgba(255, 255, 255, 0.04);
  border: 1px solid var(--topbar-surface-border);
  border-radius: var(--radius-pill);
}

.top-bar-tab-pair-indicator {
  position: absolute;
  top: 3px;
  bottom: 3px;
  left: 0;
  width: 0;
  border-radius: var(--radius-pill);
  background: rgba(112, 195, 226, 0.12);
  border: 1.5px solid var(--accent);
  /* Same lit-accent feel as the other tabs' underline glow, kept inside the
     pill's bordered shape: a soft outer halo plus a faint inner light.
     Uses --accent-rgb so the glow tracks the theme's accent (cyan in dark,
     blue in light) instead of staying a faint dark-theme cyan. */
  box-shadow: 0 0 12px rgba(var(--accent-rgb), 0.32),
    inset 0 0 8px rgba(var(--accent-rgb), 0.12);
  transition: transform 0.22s cubic-bezier(0.4, 0, 0.2, 1),
    width 0.22s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.22s ease;
  z-index: 0;
  pointer-events: none;
}

/* First placement (page load, or arriving back from a non-geo view) and resize
   corrections jump straight to the target; only map↔cartogram switches slide. */
.top-bar-tab-pair-indicator.is-instant {
  transition: none;
}

.top-bar-tab-pair.is-off .top-bar-tab-pair-indicator {
  opacity: 0;
}

.top-bar-tab--pair {
  position: relative;
  z-index: 1;
}

body[data-view="map"] #tabMap.top-bar-tab--pair,
body[data-view="cartogram"] #tabCartogram.top-bar-tab--pair,
html:not(.js) body:has(#mapStage:target) #tabMap.top-bar-tab--pair,
html:not(.js) body:has(#cartogramStage:target) #tabCartogram.top-bar-tab--pair {
  background: transparent;
  color: var(--accent);
  font-weight: 600;
  box-shadow: none;
}

body.light-theme[data-view="map"] #tabMap.top-bar-tab--pair,
body.light-theme[data-view="cartogram"] #tabCartogram.top-bar-tab--pair,
body.light-theme body:has(#cartogramStage:target) #tabCartogram.top-bar-tab--pair,
body.light-theme body:has(#mapStage:target) #tabMap.top-bar-tab--pair {
  background: transparent;
  color: var(--accent);
  box-shadow: none;
}

body[data-view]:not([data-view="map"]) #tabMap.is-active {
  background: transparent;
  color: var(--muted);
  box-shadow: none;
}

body[data-view="home"] #tabHome,
body[data-view="map"] #tabMap,
body[data-view="chart"] #tabChart,
body[data-view="europe"] #tabEurope,
body[data-view="knowledge"] #tabKnowledge,
body[data-view="history"] #tabHistory,
body[data-view="tools"] #tabTools,
html:not(.js) body:has(#homeStage:target) #tabHome,
html:not(.js) body:has(#mapStage:target) #tabMap,
html:not(.js) body:has(#chartStage:target) #tabChart,
html:not(.js) body:has(#europeStage:target) #tabEurope,
html:not(.js) body:has(#knowledgeStage:target) #tabKnowledge {
  background: transparent;
  color: var(--accent);
  font-weight: 600;
  box-shadow: none;
}

html:not(.js) body:has(:is(#chartStage, #europeStage, #cartogramStage, #knowledgeStage):target) #tabMap.is-active {
  background: transparent;
  color: var(--muted);
  box-shadow: none;
}

body[data-view="cartogram"] #tabCartogram,
html:not(.js) body:has(#cartogramStage:target) #tabCartogram,
html:not(.js) body:has(#mapStage:target) #tabMap {
  background: rgba(112, 195, 226, 0.12);
  color: var(--accent);
  box-shadow: var(--shadow-sm);
}

.chart-title-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex: 0 0 auto;
  min-width: 0;
}
.chart-title-row .chart-view-title {
  flex: 1 1 auto;
  margin: 0;
  /* A flex item will not shrink below its content width unless told to, and
     every control in this row is flex-shrink: 0 -- so without this the row
     simply ran past the right edge of the chart card. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The row's own controls are the first thing to give up space, well before the
   title gets an ellipsis: below this width the chart stage is narrow enough
   that "LUE TAUSTAT / NÄYTÄ KARTALLA / JAA LINKKI" spelled out no longer fits
   beside the chart's name. The icons keep their tooltip and aria-label. */
@media (max-width: 1280px) {
  .chart-title-row .chart-xlink span,
  .chart-title-row .chart-share-btn span {
    display: none;
  }

  .chart-title-row .chart-xlink,
  .chart-title-row .chart-share-btn {
    padding: 0 8px;
    min-width: 32px;
  }
}

.chart-share-btn {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: auto;
  min-height: 30px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--muted);
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
  opacity: 0.9;
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
}
.chart-share-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
.chart-share-btn:hover { opacity: 1; color: var(--text); border-color: var(--control-hover-border); background: var(--control-hover-bg); box-shadow: var(--control-shadow-ring); }
.chart-share-btn.is-copied { opacity: 1; color: #3dba6e; border-color: #3dba6e; background: rgba(61, 186, 110, 0.1); }

/* Cross-view links (map ↔ charts ↔ knowledge base) — same control style as
   the share button, with the accent colour marking them as navigation. */
.chart-xlink {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: auto;
  min-height: 30px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(112, 195, 226, 0.4);
  background: var(--control-bg);
  color: var(--accent);
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
  opacity: 0.9;
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
  text-decoration: none;
}
.chart-xlink svg { width: 14px; height: 14px; flex-shrink: 0; }
.chart-xlink:hover {
  opacity: 1;
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(112, 195, 226, 0.08);
  box-shadow: var(--control-shadow-ring);
}

/* Map sidebar cross-link row (filled by updateMapCrossLinks) */
.map-xlink-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 12px;
}
.map-xlink-row:empty { display: none; }
/* A text link, not a second button under the controls: it leads away from the
   map and should read as a footnote to the dataset rather than as a control. */
.map-xlink-row .chart-xlink {
  flex: 0 0 auto;
  min-height: 0;
  padding: 5px 0;
  border: 0;
  background: none;
  box-shadow: none;
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  color: var(--muted);
}
.map-xlink-row .chart-xlink:hover {
  color: var(--text);
  background: none;
}
.map-xlink-row .chart-xlink svg {
  width: 13px;
  height: 13px;
}
/* ─────────────────────────────────────────────────────────────────────────
   Share menu
   One control, two ways to hand a view on: the link, and a picture of what is
   on screen. Built like the language dropdown so the top bar keeps one
   disclosure pattern, and sized so the right zone still fits at 390px.
   ───────────────────────────────────────────────────────────────────────── */

.share-dropdown {
  position: relative;
}

.share-dropdown > summary {
  list-style: none;
  cursor: pointer;
}

.share-dropdown > summary::-webkit-details-marker { display: none; }

.share-dropdown-menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 1200;
  display: flex;
  flex-direction: column;
  min-width: 176px;
  padding: 4px;
  border: 1px solid var(--control-border-strong);
  border-radius: var(--radius-sm);
  background: var(--panel-strong);
  box-shadow: var(--shadow);
}

.share-dropdown:not([open]) .share-dropdown-menu { display: none; }

/* While the menu is open the tooltip would sit under it, repeating the label
   the menu already spells out. */
.share-dropdown[open] > summary[data-tip]::before,
.share-dropdown[open] > summary[data-tip]::after { display: none; }

.share-panel {
  width: 268px;
  gap: 8px;
  padding: 10px;
}

.share-panel-modes {
  display: flex;
  gap: 6px;
}

.share-mode-btn {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  height: 40px;
  border: 1px solid var(--control-border-strong);
  border-radius: var(--radius-sm);
  background: var(--control-bg);
  color: var(--muted);
  cursor: pointer;
}

.share-mode-btn svg { width: 16px; height: 16px; }

.share-mode-btn:hover { color: var(--text); }

.share-mode-btn.is-active {
  color: var(--accent);
  border-color: rgba(var(--accent-rgb), 0.45);
  background: var(--control-bg-hover);
}

.share-panel-pane { display: none; flex-direction: column; gap: 6px; }
.share-panel-pane.is-active { display: flex; }

.share-url-field {
  width: 100%;
  height: 32px;
  padding: 0 8px;
  border: 1px solid var(--control-border-strong);
  border-radius: var(--radius-sm);
  background: var(--control-bg);
  color: var(--text);
  font-family: var(--font-data);
  font-size: var(--fs-sm);
  text-overflow: ellipsis;
}

.share-copy-hint {
  margin: 0;
  min-height: 14px;
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  color: var(--accent);
}

.share-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 96px;
  border: 1px solid var(--control-border-strong);
  border-radius: var(--radius-sm);
  background: var(--control-bg);
  overflow: hidden;
}

/* A phone's map export is portrait, and a 150px cap turned it into a 56px
   sliver. Let the preview use the panel's width and cap the height high enough
   that a tall picture is still legible. */
.share-preview img {
  max-width: 100%;
  max-height: 240px;
  display: block;
  object-fit: contain;
}
.share-preview.is-loading::after {
  content: "…";
  color: var(--muted);
  font-size: var(--fs-lg);
}
.share-preview.is-loading img { display: none; }

.share-panel-download {
  height: 32px;
  border: 1px solid var(--control-border-strong);
  border-radius: var(--radius-sm);
  background: var(--control-bg);
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}

.share-panel-download:hover { color: var(--accent); border-color: rgba(var(--accent-rgb), 0.45); }
.share-panel-download.is-busy { opacity: 0.6; pointer-events: none; }

.map-share-btn {
  position: static;
  transform: none;
  z-index: 3;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
  white-space: nowrap;
}
.map-share-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
.map-share-btn:hover { color: var(--text); border-color: var(--control-hover-border); background: var(--control-hover-bg); box-shadow: var(--control-shadow-ring); }
.map-share-btn.is-copied { color: #3dba6e; border-color: #3dba6e; background: rgba(61, 186, 110, 0.1); }

/* The knowledge base has no shareable per-section state (serializeState() has no
   "knowledge" case), and the chart view already has its own per-chart share
   buttons, so the generic top-bar share button is either broken or redundant
   there — hide it on both views, at every breakpoint. */
body[data-view="chart"] .map-share-btn,
body[data-view="knowledge"] .map-share-btn,
body[data-view="chart"] .share-dropdown,
body[data-view="knowledge"] .share-dropdown,
html:not(.js) body:has(#chartStage:target) .map-share-btn,
html:not(.js) body:has(#knowledgeStage:target) .map-share-btn {
  display: none;
}

.share-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 30px;
  padding: 0 14px;
  margin-left: 8px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(112, 195, 226, 0.2);
  background: rgba(112, 195, 226, 0.06);
  color: var(--muted);
  font-size: var(--fs-xs);
  cursor: pointer;
  transition: color 0.18s ease, border-color 0.18s ease, background 0.18s ease;
  white-space: nowrap;
}
.share-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
.share-btn:hover { color: var(--text); border-color: rgba(112, 195, 226, 0.4); background: rgba(112, 195, 226, 0.1); }
.share-btn.is-copied { color: var(--accent); border-color: var(--accent); }

.share-toast {
  position: fixed;
  bottom: 28px;
  left: 50%;
  transform: translateX(-50%) translateY(12px);
  background: var(--surface-overlay);
  border: 1px solid rgba(112, 195, 226, 0.3);
  color: var(--accent);
  font-size: var(--fs-sm);
  padding: 8px 20px;
  border-radius: var(--radius-pill);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 9999;
}
.share-toast.is-visible { opacity: 1; transform: translateX(-50%) translateY(0); }

.top-bar-pill {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  height: var(--btn-height) !important;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  padding: 0 10px;
  border: 1px solid rgba(125, 232, 178, 0.22);
  border-radius: var(--radius-pill);
  background: rgba(11, 22, 18, 0.88);
  color: #8de3ba;
  font-size: var(--fs-xs);
  letter-spacing: 0.03em;
  text-decoration: none;
  cursor: pointer;
}

/* The map, chart and cartogram views fill the screen with their own thing and
   carry no page title, so each shipped with no h1 at all. This gives them one
   for a screen reader and a crawler without taking a pixel from the view. */
.stage-title-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── Map stage ── */
.map-stage {
  position: relative;
  min-width: 0;
  height: 100%;
}

.map-noscript-message {
  position: absolute;
  inset: 50% auto auto 50%;
  z-index: 1150;
  width: min(420px, calc(100% - 32px));
  transform: translate(-50%, -50%);
  padding: 18px 20px;
  border: 1px solid rgba(112, 195, 226, 0.2);
  border-radius: var(--radius-lg);
  background: rgba(8, 12, 18, 0.94);
  box-shadow: var(--shadow-lg);
  color: var(--text);
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
  text-align: center;
}

.clipboard-helper {
  position: fixed;
  opacity: 0;
}

.js-hidden-select {
  display: none;
}

.map-loading-overlay {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 1100;
  cursor: default;
  pointer-events: none;
}
.map-loading-overlay.is-active { display: block; }

.map-basemap-fallback {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.35s ease;
  background:
    radial-gradient(circle at 24% 18%, rgba(88, 143, 164, 0.13), transparent 34%),
    radial-gradient(circle at 72% 58%, rgba(22, 78, 102, 0.15), transparent 42%),
    linear-gradient(180deg, #08131d 0%, #02070d 100%);
}

.map-basemap-fallback.is-visible {
  opacity: 1;
}

.map-basemap-fallback__glow,
.map-basemap-fallback__grid {
  position: absolute;
  inset: 0;
}

.map-basemap-fallback__glow {
  background:
    linear-gradient(180deg, rgba(58, 115, 138, 0.12), transparent 48%),
    radial-gradient(circle at 50% 42%, rgba(74, 131, 158, 0.09), transparent 38%);
  opacity: 0.62;
}

.map-basemap-fallback__grid {
  background:
    linear-gradient(100deg, transparent 0%, rgba(112, 156, 176, 0.025) 46%, transparent 64%),
    radial-gradient(circle at 58% 35%, rgba(130, 176, 196, 0.028), transparent 32%);
  opacity: 0.56;
}

.mobile-map-modes {
  display: none;
  position: absolute;
  top: calc(var(--safe-top) + var(--mobile-map-modes-offset));
  left: var(--safe-left);
  right: var(--safe-right);
  z-index: 720;
  gap: 8px;
  align-items: flex-start;
  flex-wrap: wrap;
  padding-right: 4px;
  padding-bottom: 4px;
  overflow: visible;
  scrollbar-width: none;
}

.mobile-map-modes::-webkit-scrollbar {
  display: none;
}

.map-controls-elm--mobile {
  max-width: 100%;
}

/* A <details> now, so it must be a box of its own: display:contents would let
   a closed disclosure's children render as if it were not there. */
.map-controls-elm--overlays {
  display: block;
}

.mobile-map-mode-button {
  flex: 0 0 auto;
  height: var(--btn-height);
  padding: 0 12px;
  border-radius: var(--radius-pill);
  background: rgba(8, 12, 18, 0.94);
  color: var(--muted);
  font-size: var(--fs-xs);
  letter-spacing: 0.02em;
  box-shadow: var(--shadow);
  white-space: nowrap;
}

.mobile-map-mode-button.is-active {
  border-color: rgba(112, 195, 226, 0.4);
  background:
    linear-gradient(135deg, rgba(112, 195, 226, 0.18), transparent 70%),
    rgba(8, 12, 18, 0.96);
  color: var(--accent);
}

#map {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background:
    radial-gradient(circle at 25% 20%, rgba(84, 138, 160, 0.10), transparent 34%),
    linear-gradient(180deg, #08131d 0%, #02070d 100%);
}

#map {
  z-index: 2;
  background: transparent;
  will-change: transform;
}

.boundary-hover-popup {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 715;
  max-width: min(320px, calc(100vw - 28px));
  padding: 10px 12px;
  border: 1px solid rgba(112, 195, 226, 0.22);
  border-radius: var(--radius-md);
  background: var(--surface-overlay);
  box-shadow: var(--shadow-lg);
  color: var(--text);
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  pointer-events: none;
  opacity: 0;
  transform: translate3d(-9999px, -9999px, 0);
  transition: opacity 0.08s ease;
}

.boundary-hover-popup.is-visible {
  opacity: 1;
}

/* ── Floating info card (right side) ── */
.info-card {
  --panel-pad-x: 14px;
  --panel-pad-top: 14px;
  --panel-pad-bottom: 14px;
  position: fixed;
  top: 64px;
  right: 24px;
  z-index: 720;
  display: grid;
  align-content: start;
  gap: 10px;
  width: clamp(208px, 17.6vw, 272px);
  width: var(--stats-width);
  max-height: calc(100dvh - 100px);
  padding: var(--panel-pad-top) var(--panel-pad-x) var(--panel-pad-bottom);
  overflow-x: hidden;
  overflow-y: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--surface-panel);
  box-shadow: var(--shadow);
  opacity: 0;
  pointer-events: none;
  transform: translateX(20px);
  transition: opacity 0.2s ease, transform 0.2s ease;
}

#statsPanel {
  background: var(--surface-panel);
  isolation: isolate;
  contain: paint;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

.info-card.is-visible {
  opacity: 1;
  pointer-events: auto;
  transform: translateX(0);
}

/* The panel's close control is the same inline SVG as the search-clear
   buttons, just larger; the button's own colour and hover still drive it. */
.stats-panel-close-btn .stats-panel-close-icon {
  width: 14px;
  height: 14px;
}

.stats-panel-close-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  outline: none;
  box-shadow: none;
  color: var(--muted);
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
  flex-shrink: 0;
  margin-left: auto;
  padding: 0 0 0 8px;
  transition: color 0.16s ease;
  -webkit-appearance: none;
  appearance: none;
}

.stats-panel-close-btn:hover,
.stats-panel-close-btn:focus,
.stats-panel-close-btn:focus-visible {
  background: none;
  color: var(--text);
  outline: none;
  border-color: transparent;
  box-shadow: none;
}

/* The stats panel's cards carry small uppercase headings ("HISTORIALLINEN
   KEHITYS") that sat almost against the card edge at 9px/10px, which read as a
   missing margin rather than a tight design. */
.info-card .stats-card {
  padding: 13px 14px;
  border-radius: var(--radius-md);
  gap: 6px;
}

#statsPanel .stats-card {
  background: rgba(10, 14, 22, 0.94);
  box-shadow: none;
}

.info-card .stats-primary-value {
  font-size: clamp(1.375rem, 3vw, 1.875rem);
}

.info-card .stats-primary-meta,
.info-card .stats-panel-empty {
  font-size: var(--fs-xs);
}

.info-card #graphPanelTitle {
  font-size: var(--fs-sm);
}

/* ── Menu section headings ── */
.menu-section {
  display: grid;
  gap: 8px;
}

/* ── School switcher in info panel ── */
.school-switcher-container {
  display: none;
  position: relative;
  margin-bottom: 8px;
}

.school-switcher-container.is-visible {
  display: block;
}

.school-switcher-select {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 36px;
  padding: 0 14px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  border-radius: var(--radius-md);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--text);
  cursor: pointer;
  appearance: none;
  transition: border-color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease;
}

.school-switcher-select:hover {
  border-color: var(--control-hover-border);
  background: var(--control-hover-bg);
  box-shadow: var(--control-shadow-ring);
}

.school-switcher-chevron {
  width: 12px;
  height: 12px;
  color: var(--muted);
  flex-shrink: 0;
}

.school-switcher-menu {
  display: none;
  position: absolute;
  left: 0;
  right: 0;
  z-index: 100;
  margin: 0;
  padding: 4px;
  list-style: none;
  border-radius: var(--radius-md);
  border: 1px solid var(--control-border);
  background: var(--panel-strong);
  box-shadow: var(--menu-surface-shadow);
  backdrop-filter: blur(10px);
}

.school-switcher-menu.is-open {
  display: block;
}

.school-switcher-menu li {
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.school-switcher-menu li:hover {
  background: rgba(112, 195, 226, 0.1);
}

.school-switcher-menu li[aria-selected="true"] {
  color: var(--accent);
  background: rgba(112, 195, 226, 0.12);
}

.school-switcher-metric {
  color: var(--muted);
  font-size: var(--fs-xs);
}

.school-switcher-single {
  min-height: 38px;
  display: flex;
  align-items: center;
  padding: 0 14px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--text);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-pill);
  background: var(--control-bg);
}

/* ── Custom dataset dropdown ── */
.custom-dropdown {
  position: relative;
}

.custom-dropdown-trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 38px;
  padding: 9px 14px 9px 18px;
  gap: 10px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: var(--lh-ui);
  border-radius: var(--radius-sm);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--text);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.18s ease, background 0.18s ease, box-shadow 0.18s ease, color 0.18s ease;
}

.custom-dropdown-label {
  flex: 1;
  min-width: 0;
}

/* Copy that only the "show everything" setting brings back. */
.adv-only {
  display: none;
}

html.is-advanced .adv-only {
  display: block;
}

/* Whole datasets the plain view leaves out: a second classification of a
   population already charted, a register count with its own definition, or a
   layer that is context rather than a migration figure. The eyeglasses button
   brings every one of them back, so nothing is removed, only deferred.

   Hiding rather than restoring a display value keeps this to one rule: in the
   advanced mode the selector simply does not match, and the element is laid
   out by whatever rule owns it. The aria-selected exception is for the map's
   dataset menu -- a shared link can land on a hidden dataset, and the row for
   the one on screen has to stay in the list. */
html:not(.is-advanced) .is-expert-only:not([aria-selected="true"]) {
  display: none;
}

html.is-advanced #metricsSection > .utility-panel-card-heading .menu-section-subtitle {
  margin-top: 6px;
}

.advanced-toggle-btn[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--accent);
}

/* Measure on the first line, area level on a second quieter one. The separator
   exists for textContent readers only. */
.custom-dropdown-main {
  display: block;
}

.custom-dropdown-sep {
  display: none;
}

.custom-dropdown-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--muted);
}

/* A sidebar disclosure: one line closed, the controls under it open. Used for
   the city shortcuts and the overlays. The chevron is the only decoration. */
.map-disclosure {
  border: 0;
  padding: 0;
}

.map-disclosure-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  list-style: none;
  cursor: pointer;
  padding: 6px 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
}

.map-disclosure-summary::-webkit-details-marker {
  display: none;
}

.map-disclosure-summary::after {
  content: "";
  width: 7px;
  height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  transition: transform 0.15s ease;
  flex: 0 0 auto;
  margin-right: 3px;
}

.map-disclosure[open] > .map-disclosure-summary::after {
  transform: rotate(-135deg);
}

.map-disclosure-summary:hover {
  color: var(--text);
}

.map-disclosure-summary .menu-section-title {
  margin: 0;
}

.map-quick-city-panel[open] .map-quick-city-buttons {
  margin-top: 6px;
}

@media (prefers-reduced-motion: reduce) {
  .map-disclosure-summary::after { transition: none; }
}

.custom-dropdown-trigger:hover,
.custom-dropdown.is-open .custom-dropdown-trigger {
  border-color: var(--control-hover-border);
  background: var(--control-hover-bg);
  box-shadow: var(--control-shadow-ring);
}

.custom-dropdown-chevron {
  width: 12px;
  height: 12px;
  color: var(--muted);
  flex-shrink: 0;
  opacity: 0.9;
  transition: transform 0.18s ease, color 0.18s ease, opacity 0.18s ease;
}

.custom-dropdown.is-open .custom-dropdown-chevron {
  transform: rotate(180deg);
  color: var(--accent);
  opacity: 1;
}

#metricDropdown .custom-dropdown-trigger {
  height: auto;
  min-height: 40px;
  padding: 8px 12px 8px 16px;
  font-size: var(--fs-sm);
  align-items: center;
  text-align: left;
}

.custom-dropdown-menu {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  width: 100%;
  z-index: 9999;
  margin: 0;
  padding: 4px;
  list-style: none;
  border-radius: var(--radius-md);
  border: 1px solid var(--control-border);
  background: var(--panel-strong);
  box-shadow: var(--menu-surface-shadow);
  backdrop-filter: blur(10px);
}

.custom-dropdown-menu.is-open {
  display: block;
}

.custom-dropdown-menu li {
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s ease, color 0.12s ease;
}

.custom-dropdown-menu li:hover {
  background: rgba(112, 195, 226, 0.1);
}

/* The keyboard's highlight, the same look as the pointer's, because the two
   share one index. */
.custom-dropdown-menu li.is-active {
  background: rgba(112, 195, 226, 0.1);
}

.custom-dropdown-menu li[aria-selected="true"] {
  color: var(--accent);
  background: rgba(112, 195, 226, 0.12);
}

.custom-dropdown-menu li.option-group-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  padding: 10px 12px 4px;
  cursor: default;
  user-select: none;
  pointer-events: none;
  border-radius: 0;
}

/* Europe sits below a rule: everything above it is Finland. */
.custom-dropdown-menu li.option-after-divider {
  margin-top: 6px;
  padding-top: 12px;
  border-top: 1px solid rgba(112, 195, 226, 0.22);
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.custom-dropdown-menu li.option-indented {
  padding-left: 18px;
}

.custom-dropdown-menu li.metric-option {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.metric-label-text {
  font-weight: 600;
  font-size: var(--fs-sm);
  line-height: 1.3;
}

.metric-subtitle {
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.3;
}

.menu-section-title {
  margin: 0;
  padding-left: 12px;
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--text);
}

/* Every sidebar card names itself. The h2 and its i18n key already existed in
   MapSidebar.elm / MapControls.elm for all four cards, and so did this styling —
   only display:none kept them off, swept in with the 701d87d restructure rather
   than by any design decision. Without them the dataset card appeared titled
   (that is its subtitle hint) while search and overlays looked unlabelled. */
.utility-panel .menu-section-title {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--muted);
  padding: 0 0 8px 0;
  margin: 0 0 4px 0;
  border-bottom: 1px solid var(--panel-card-border);
}

.menu-section-subtitle {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--text);
}

/* The cartogram's subtitle is the one that carries a sentence rather than a
   label. Uppercase with letter-spacing turns two sentences into four lines of
   shouting, so this one reads as prose. */
.utility-panel-card--cartogram .menu-section-subtitle {
  font-size: var(--fs-xs);
  letter-spacing: 0;
  text-transform: none;
  line-height: var(--lh-ui);
  color: var(--muted);
}

#metricsSection > .menu-section-subtitle:first-child {
  margin-bottom: 10px;
}

#yearControlGroup {
  gap: 7px;
  margin-bottom: 8px;
}

#metricSelectGroup,
#yearControlGroup,
#natBgPickerGroup {
  display: none;
}

#metricsSection[data-control-mode="standard"] #metricSelectGroup,
#metricsSection[data-control-mode="natbg"] #natBgPickerGroup {
  display: block;
}

#metricsSection[data-has-years="true"] #yearControlGroup {
  display: grid;
}

/* ── Left-side menu panel ── */
.utility-panel {
  --panel-pad-x: 12px;
  --panel-pad-top: 12px;
  --panel-pad-bottom: 12px;
  --panel-gap: 12px;
  position: fixed;
  top: calc(var(--top-bar-height) + 6px);
  left: 12px;
  bottom: 12px;
  z-index: 710;
  display: flex;
  flex-direction: column;
  width: var(--panel-width);
  min-width: 280px;
  max-width: 80vw;
  height: auto;
  max-height: calc(100dvh - var(--top-bar-height) - 18px);
  overflow: hidden;
  overscroll-behavior: contain;
  resize: none;
  border: 1px solid var(--border);
  /* Concentric corners: outer radius = inner card radius + the gap (panel
     padding), so the panel and its first cards share one rounding centre. */
  border-radius: calc(var(--card-radius) + var(--panel-pad-x));
  background: var(--surface-panel);
  box-shadow: var(--shadow-lg);
  transform: translateX(calc(-100% - 16px));
  opacity: 0;
  pointer-events: none;
  transition: transform 0.22s ease, opacity 0.22s ease;
}

/* Inner scroll wrapper: keeps the scrollbar inside the panel's rounded box
   (the outer panel uses overflow:hidden + border-radius to clip it). */
.utility-panel-scroll {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  align-content: start;
  gap: var(--panel-gap);
  /* Inset the scroll area vertically so its scrollbar stops short of the
     panel's rounded top/bottom corners (a native scrollbar isn't clipped by
     the mask below). The margin does the insetting; the padding is reduced by
     the same amount so the visible content gap is unchanged. */
  --panel-scroll-inset: 12px;
  margin-block: var(--panel-scroll-inset);
  padding: max(0px, calc(var(--panel-pad-top) - var(--panel-scroll-inset)))
    var(--panel-pad-x)
    max(0px, calc(var(--panel-pad-bottom) - var(--panel-scroll-inset)));
  overflow-y: auto;
  overflow-x: hidden;
  overscroll-behavior: contain;
  /* Soft-fade the scroll edges so cards dissolve instead of hard-clipping at
     the top/bottom of the inset scroll area. */
  --panel-fade: 8px;
  -webkit-mask-image: linear-gradient(
    to bottom,
    transparent 0,
    #000 var(--panel-fade),
    #000 calc(100% - var(--panel-fade)),
    transparent 100%
  );
  mask-image: linear-gradient(
    to bottom,
    transparent 0,
    #000 var(--panel-fade),
    #000 calc(100% - var(--panel-fade)),
    transparent 100%
  );
}

.utility-panel--elm {
  display: flex;
  flex-direction: column;
}

.utility-panel-card {
  display: grid;
  gap: 12px;
}

.utility-panel-card-heading {
  display: grid;
  gap: 4px;
  padding: 2px 4px 0;
}

/* The year of a dataset that has no year slider: same row shape as the slider's
   heading, so it reads as the same piece of information. */
/* The metric's own caveat, under the picker it belongs to. Small and quiet:
   it is a qualification, not a heading. */
.metric-caveat {
  margin: 2px 2px 0;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.utility-control-block--vintage {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}

.utility-control-block {
  display: grid;
  gap: 9px;
  min-width: 0;
}
.utility-panel-native-controls {
  display: none;
}

#cartogramSidebarSection {
  display: none;
}

body[data-view="cartogram"] #cartogramSidebarSection {
  display: grid;
}

.cartogram-sidebar-legend {
  min-height: 24px;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.utility-panel.is-open {
  transform: translateX(0);
  opacity: 1;
  pointer-events: auto;
}

.stats-card {
  position: relative;
  border: 1px solid var(--panel-card-border);
  background: var(--panel-card-bg);
  border-radius: var(--radius-lg);
  padding: 11px 12px;
  box-shadow: inset 0 1px 0 var(--card-inset-border);
}

.stats-card h3 {
  margin: 0;
  padding-top: 0;
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: var(--lh-ui);
}

.stats-primary-meta,
.stats-empty,
.stats-panel-empty {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
}

.stats-card {
  display: grid;
  gap: 8px;
}

.stats-card-primary {
  background:
    linear-gradient(135deg, rgba(112, 195, 226, 0.08), transparent 44%),
    rgba(7, 11, 18, 0.82);
}

#statsPanel .stats-card-primary {
  background: rgba(12, 18, 26, 0.96);
}

.dataset-range-cards {
  display: grid;
  gap: 9px;
}

.dataset-range-card {
  gap: 7px;
}

.stats-card-heading {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.stats-card-heading--row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.ranking-scroll-back-btn {
  flex-shrink: 0;
  font-size: var(--fs-xs);
  padding: 2px 8px;
  background: rgba(112, 195, 226, 0.08);
  color: var(--accent);
  border: 1px solid rgba(112, 195, 226, 0.22);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background 0.12s;
}

.ranking-scroll-back-btn:hover {
  background: rgba(112, 195, 226, 0.16);
}

#graphPanelTitle {
  margin: 0;
  line-height: 1;
}

#graphPanelSubtitle {
  margin-top: 1px;
}

.stats-value-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.stats-primary-value {
  font-size: clamp(1.875rem, 4vw, 2.625rem);
  line-height: 1;
  color: #ffffff;
  white-space: nowrap;
}

.stats-secondary-stat {
  display: flex;
  align-items: baseline;
  gap: 8px;
  padding: 4px 0 10px;
  border-bottom: 1px solid rgba(112, 195, 226, 0.08);
  margin-bottom: 4px;
}

.stats-secondary-value {
  font-size: clamp(1.375rem, 3vw, 1.875rem);
  line-height: 1;
  font-weight: 600;
  color: #ffffff;
}

.stats-secondary-label {
  font-size: var(--fs-xs);
  opacity: 0.55;
}

.stats-supplementary {
  display: grid;
  gap: 6px;
}

.stats-supplementary-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 6px;
  border-top: 1px solid rgba(112, 195, 226, 0.08);
  font-size: var(--fs-xs);
}

/* Section heading inside a supplementary block: a stronger rule and more space
   above than the hairline between ordinary rows, so the per-area list below it
   reads as its own group rather than a continuation of the summary figures. */
.stats-supplementary-heading {
  margin-top: 6px;
  padding-top: 10px;
  border-top: 1px solid rgba(112, 195, 226, 0.26);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--muted);
}

/* The row straight after a heading already has the heading's rule above it. */
.stats-supplementary-heading + .stats-supplementary-row {
  border-top: 0;
  padding-top: 2px;
}

.stats-supplementary-label {
  color: var(--muted);
}

.stats-supplementary-value {
  color: var(--text);
  text-align: right;
}

/* Method text under the stats panel, opened on demand. The projection note is
   some 450 characters of regression detail; glued onto the source line it was a
   wall of prose under a small panel. */
.stats-supplementary-note {
  margin-top: 8px;
}

.stats-supplementary-note > p {
  margin: 6px 0 0;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.stats-supplementary-source {
  margin: 10px 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.stats-supplementary-source a {
  color: inherit;
  text-decoration: underline;
  text-decoration-color: rgba(112, 195, 226, 0.35);
}

.stats-supplementary-source a:hover {
  color: var(--accent);
}

.foreign-bg-bars {
  margin-top: 10px;
  display: grid;
  gap: 4px;
}

.foreign-bg-bars-title {
  font-size: var(--fs-xs);
  letter-spacing: 0.1em;
  color: var(--muted);
  margin-bottom: 2px;
}

.foreign-bg-bar-row {
  display: grid;
  grid-template-columns: 1fr 2fr auto;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
}

.foreign-bg-bar-name {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Same convention as .area-bar-name--link: names are ellipsised, so the link
   shows itself on hover/focus instead of striping the whole column. */
.foreign-bg-bar-name--link {
  text-decoration: none;
}

.foreign-bg-bar-name--link:hover,
.foreign-bg-bar-name--link:focus-visible {
  color: var(--accent);
  text-decoration: underline;
}

.foreign-bg-bar-track {
  height: 5px;
  background: rgba(255,255,255,0.08);
  border-radius: var(--radius-xs);
  overflow: hidden;
}

.foreign-bg-bar-fill {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-xs);
  opacity: 0.7;
}

.foreign-bg-bar-pct {
  color: var(--muted);
  text-align: right;
  min-width: 32px;
  font-size: var(--fs-xs);
}

/* Selected-area names are longer than the country names these bars were built
   for, so the comparison card trades bar length for label room. */
#statsCompareSection .foreign-bg-bar-row {
  grid-template-columns: 1.6fr 1fr auto;
}

#statsCompareSection .foreign-bg-bars {
  margin-top: 0;
}

.stats-diagram {
  display: grid;
  gap: 3px;
}

.stats-svg {
  width: 100%;
  height: auto;
  overflow: visible;
}

.stats-diagram-note,
.stats-diagram-labels {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.stats-diagram-note strong {
  color: var(--text);
  font-weight: 500;
}

.stats-ranking-list {
  display: grid;
  gap: 8px;
  max-height: min(34dvh, 320px);
  overflow-y: auto;
  padding-right: 4px;
  overscroll-behavior: contain;
  align-content: start;
}

.stats-ranking-item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  padding: 9px 10px;
  border: 1px solid rgba(112, 195, 226, 0.08);
  border-radius: var(--radius-md);
  background: rgba(8, 12, 18, 0.64);
  cursor: pointer;
  transition: border-color 0.1s, background 0.1s;
}

.stats-ranking-item:hover {
  border-color: rgba(112, 195, 226, 0.2);
  background: rgba(8, 12, 18, 0.84);
}

.stats-ranking-item.is-selected {
  border-color: rgba(112, 195, 226, 0.26);
  background:
    linear-gradient(135deg, rgba(112, 195, 226, 0.12), transparent 72%),
    rgba(8, 12, 18, 0.78);
}

.stats-ranking-item.is-selected .stats-ranking-name,
.stats-ranking-item.is-selected .stats-ranking-value {
  color: #ffffff;
}

.stats-ranking-item.is-selected .stats-ranking-name {
  font-weight: 500;
}

.stats-ranking-rank {
  color: var(--accent);
  font-size: var(--fs-xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.stats-ranking-name {
  min-width: 0;
  font-size: var(--fs-xs);
  color: var(--text);
}

.stats-ranking-name strong {
  font-weight: 500;
}

.stats-ranking-value {
  font-size: var(--fs-xs);
  color: #ffffff;
}

.utility-panel > * {
  position: relative;
  z-index: 1;
}

.control-group {
  display: grid;
  gap: 7px;
  min-width: 0;
  padding: 10px;
  border: 1px solid rgba(112, 195, 226, 0.1);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  box-shadow: inset 0 1px 0 var(--card-inset-border);
}
.control-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.control-caption {
  font-size: var(--fs-xs);
  color: rgba(127, 151, 164, 0.9);
  text-align: right;
}

#yearRangeCurrent {
  font-weight: 600;
  color: var(--accent);
}

.control-label {
  font-size: var(--fs-xs);
  line-height: var(--lh-prose);
  color: var(--muted);
}

.control-native-select {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.info-card h3 {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 500;
  letter-spacing: 0.1em;
}

.overlay-panel,
details {
  position: relative;
  border: 1px solid var(--panel-card-border);
  border-radius: var(--radius-lg);
  background: var(--panel-card-bg);
}

.panel-hint {
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-prose);
}

.transparency-control {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 0;
  min-width: 120px;
}

.transparency-control input {
  width: 100%;
}

.transparency-value {
  font-size: var(--fs-xs);
  color: var(--accent);
  min-width: 32px;
  text-align: right;
}

.layer-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 10px 0;
  border-bottom: 1px solid var(--panel-card-border);
}

.layer-toggle:last-of-type {
  border-bottom: none;
}

.faith-toggle-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  padding: 8px 0 12px;
  border-bottom: 1px solid var(--panel-card-border);
}

.faith-toggle-list:empty {
  display: none;
}

.faith-toggle-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: 5px 8px;
  border: 1px solid rgba(112, 195, 226, 0.16);
  border-radius: var(--radius-md);
  background: rgba(10, 18, 28, 0.54);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-xs);
  line-height: 1.1;
  cursor: pointer;
}

.faith-toggle-chip[aria-pressed="true"] {
  border-color: rgba(214, 177, 98, 0.72);
  background: rgba(214, 177, 98, 0.16);
  color: #ffe2a0;
}

.faith-toggle-count {
  color: var(--muted);
  font-size: var(--fs-xs);
}
/* The button sits beside the track rather than under it: the row is already
   two lines tall (track, then scale labels), and a control below the labels
   would read as belonging to the projections block underneath. */
.year-slider-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.year-slider-row .year-slider-group {
  flex: 1;
  min-width: 0;
}

.year-play-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  /* Centred on the 22px track, not on the whole column. */
  margin-top: -3px;
  padding: 0;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-pill);
  background: var(--panel-bg);
  color: var(--accent);
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease, background 0.15s ease;
}

.year-play-btn[hidden] {
  display: none;
}

.year-play-btn:hover:not(:disabled) {
  border-color: var(--accent);
  color: var(--accent);
}

.year-play-btn:disabled {
  opacity: 0.45;
  cursor: default;
}

.year-play-btn.is-playing {
  border-color: var(--accent-hot);
  color: var(--accent-hot);
}

.year-play-icon {
  display: block;
  width: 13px;
  height: 13px;
}

.year-play-btn .year-play-icon--stop,
.year-play-btn.is-playing .year-play-icon--play {
  display: none;
}

.year-play-btn.is-playing .year-play-icon--stop {
  display: block;
}

.year-slider-group {
  display: grid;
  gap: 8px;
}

.year-range {
  width: 100%;
  /* The slider is positioned by year and its values ascend with time, so the
     old direction:rtl (which paired with a descending option index) would put
     1990 on the right. Left is earliest, right is latest, matching the scale
     labels underneath. */
  -webkit-appearance: none;
  appearance: none;
  background: transparent;
  border: none !important;
  box-shadow: none !important;
  height: 22px;
  padding: 0;
  cursor: pointer;
}

/* Legend under the multi-area comparison chart: the names and current values
   the bar version used to carry, kept out of the small SVG. */
.stats-multiline-legend {
  display: grid;
  gap: 3px;
  margin-top: 6px;
}

.stats-multiline-row {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  line-height: 1.3;
}

.stats-multiline-dot {
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  flex-shrink: 0;
}

.stats-multiline-name {
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.stats-multiline-value {
  margin-left: auto;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

/* Marks under the track for the years the slider can stop on. Only rendered
   for sparse sliders (see YEAR_TICK_MAX_POINTS) — with four points across two
   decades the control otherwise looks continuous. */
.year-slider-ticks {
  position: relative;
  height: 4px;
  /* Pulled up against the track: the input reserves room for the 20px thumb, so
     without this the marks float halfway to the scale labels and read as part
     of them instead of the track. */
  margin-top: -6px;
}

.year-slider-ticks[hidden] {
  display: none;
}

.year-slider-tick {
  position: absolute;
  top: 0;
  width: 1px;
  height: 4px;
  /* Brighter than the track itself: at the track's own 0.18 alpha the marks
     disappear against it. */
  background: rgba(112, 195, 226, 0.45);
  transform: translateX(-0.5px);
}

body.light-theme .year-slider-tick {
  background: rgba(82, 99, 115, 0.5);
}

.year-projection-block {
  display: grid;
  gap: 6px;
  margin-top: 10px;
}

.year-projection-block[hidden] {
  display: none;
}

/* Slide toggle. Generic on purpose -- the projection control is the first user,
   but nothing in here is specific to it, so the next on/off control in the
   sidebar can reuse .ui-switch rather than growing a third toggle shape. */
.ui-switch {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 0;
  min-height: 24px;
  background: none;
  border: none;
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  text-align: left;
  cursor: pointer;
}

/* The global `button:hover, button:focus-visible` rule paints a focus ring with
   box-shadow. On a control with no border of its own that drew a rectangle
   around the whole label, so the ring moves onto the track below. */
.ui-switch:hover,
.ui-switch:focus-visible {
  box-shadow: none;
  border-color: transparent;
}

.ui-switch-track {
  position: relative;
  flex-shrink: 0;
  width: 32px;
  height: 18px;
  border-radius: var(--radius-pill);
  background: rgba(112, 195, 226, 0.14);
  border: 1px solid var(--line);
  transition: background 0.16s ease, border-color 0.16s ease;
}

.ui-switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--muted);
  transition: transform 0.16s ease, background 0.16s ease;
}

.ui-switch:hover .ui-switch-track {
  border-color: rgba(112, 195, 226, 0.36);
}

.ui-switch:focus-visible .ui-switch-track {
  box-shadow: 0 0 0 2px rgba(112, 195, 226, 0.35);
}

.ui-switch[aria-checked="true"] .ui-switch-track {
  background: var(--accent);
  border-color: var(--accent);
}

.ui-switch[aria-checked="true"] .ui-switch-knob {
  transform: translateX(14px);
  background: #000;
}

.ui-switch[aria-checked="true"] .ui-switch-label {
  color: var(--text);
}

/* The switch was built for the dark theme and carried two of its assumptions
   into the light one. The knob is #000 because on the dark theme's bright cyan
   track a black dot reads as a hole punched in it -- on the light theme's deep
   blue track it is just a dark smudge on a dark pill, and the only pure black
   anywhere in the light UI. And the unchecked track is a cyan tint at 14%
   alpha, which over a dark ground is a faint glow and over white is nothing at
   all. Light theme gets a white knob and a neutral track it can actually see. */
/* A third assumption, found when the knowledge base reused this switch. The
   switch clears its own background with `.ui-switch { background: none }`, but
   `body.light-theme button` sets a white fill and wins: two classes beat one on
   an element selector's tie-break. So every .ui-switch on the site sat on a
   white rectangle in the light theme -- visible wherever the ground is not
   white, as in the map's stats card. Matching the light theme's own
   specificity puts the switch back on whatever it is standing on. */
body.light-theme .ui-switch {
  background: none;
}

body.light-theme .ui-switch-track {
  background: rgba(17, 24, 39, 0.08);
}

body.light-theme .ui-switch[aria-checked="true"] .ui-switch-knob {
  background: #ffffff;
}

@media (prefers-reduced-motion: reduce) {
  .ui-switch-track,
  .ui-switch-knob {
    transition: none;
  }
}

.year-projection-switch {
  /* The block is a grid, which would otherwise stretch the control across the
     full sidebar width. */
  justify-self: start;
}

.year-projection-hint {
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.year-range {
  --year-track: rgba(112, 195, 226, 0.18);
}

.year-range::-webkit-slider-runnable-track {
  height: 4px;
  border-radius: var(--radius-xs);
  background: var(--year-track);
}

.year-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--bg);
  border: 2.5px solid var(--accent);
  box-shadow: 0 0 0 1px rgba(112, 195, 226, 0.25), 0 2px 8px rgba(0, 0, 0, 0.35);
  cursor: pointer;
  margin-top: -8px;
  transition: transform 0.15s ease, box-shadow 0.15s ease;
}

.year-range:hover::-webkit-slider-thumb {
  transform: scale(1.12);
  box-shadow: 0 0 0 4px rgba(112, 195, 226, 0.15), 0 2px 8px rgba(0, 0, 0, 0.35);
}

.year-range::-moz-range-track {
  height: 4px;
  border-radius: var(--radius-xs);
  background: rgba(112, 195, 226, 0.18);
}

.year-range::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--bg);
  border: 2.5px solid var(--accent);
  box-shadow: 0 0 0 1px rgba(112, 195, 226, 0.25), 0 2px 8px rgba(0, 0, 0, 0.35);
  cursor: pointer;
}

/* ── Projected years on the year slider ──────────────────────────────────────
   Everything past the last measured year is modelled, so the track changes
   character there: solid up to the boundary, dashed beyond it. --year-proj-start
   is set by syncYearSliderProjectionStyling() as a percentage of the width. */
.year-range.has-projection::-webkit-slider-runnable-track {
  /* Two layers side by side rather than stacked: each gets its own slice of the
     width, so the dashes stay inside the projected span. Both draw in the same
     --year-track colour the solid part uses — the projected span is the SAME
     line with gaps in it, not a brighter one; a brighter dash would read as an
     emphasis on the modelled years rather than a caveat about them. The gaps
     are transparent, so what shows through is the panel behind the track. */
  background-image:
    linear-gradient(var(--year-track), var(--year-track)),
    repeating-linear-gradient(to right, var(--year-track) 0 4px, transparent 4px 9px);
  background-size: var(--year-proj-start, 100%) 100%, calc(100% - var(--year-proj-start, 100%)) 100%;
  background-position: left center, right center;
  background-repeat: no-repeat, no-repeat;
}

.year-range.has-projection::-moz-range-track {
  background-image:
    linear-gradient(var(--year-track), var(--year-track)),
    repeating-linear-gradient(to right, var(--year-track) 0 4px, transparent 4px 9px);
  background-size: var(--year-proj-start, 100%) 100%, calc(100% - var(--year-proj-start, 100%)) 100%;
  background-position: left center, right center;
  background-repeat: no-repeat, no-repeat;
}

/* Thumb while the selected year is a projection: hollow with a dashed ring, so
   the control itself says "this number is modelled" without reading the label. */
.year-range.is-projected::-webkit-slider-thumb {
  background: var(--bg);
  border-style: dashed;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(112, 195, 226, 0.18), 0 2px 8px rgba(0, 0, 0, 0.35);
}

.year-range.is-projected::-moz-range-thumb {
  background: var(--bg);
  border-style: dashed;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(112, 195, 226, 0.18), 0 2px 8px rgba(0, 0, 0, 0.35);
}

.year-range.is-projected + .year-scale,
.year-range.is-projected ~ .year-scale {
  color: var(--accent);
}

.year-slider-group .year-range.is-projected {
  --year-thumb-accent: var(--accent);
}

.year-scale {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.layer-copy {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.layer-name {
  color: var(--text);
  font-size: var(--fs-sm);
}

.layer-meta {
  color: var(--muted);
  font-size: var(--fs-xs);
}

.layer-button {
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--text);
}

.layer-button {
  min-width: 70px;
  height: var(--btn-height) !important;
  padding: 0 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  font-size: var(--fs-xs);
  letter-spacing: 0.14em;
  border-radius: var(--radius-pill);
}

.layer-button.is-active {
  color: var(--accent);
  border-color: var(--control-border-strong);
  box-shadow: inset 0 0 0 1px rgba(112, 195, 226, 0.08);
}

.layer-button:not(.is-active) {
  color: var(--muted);
}

.layer-button:disabled {
  opacity: 0.35;
  cursor: not-allowed;
  pointer-events: none;
}

.leaflet-pane.top-markers-pane {
  z-index: 610;
}

.atlas-marker {
  position: relative;
  display: block;
  width: 18px;
  height: 24px;
  --marker-color: var(--accent);
  --marker-fill: color-mix(in srgb, var(--marker-color) 68%, #09111b);
  overflow: visible;
}

.atlas-marker::before {
  content: "";
  position: absolute;
  left: 1px;
  top: 0;
  width: 16px;
  height: 16px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  transform-origin: 50% 50%;
  background: var(--marker-fill);
  border: 1px solid color-mix(in srgb, var(--marker-color) 22%, white);
  box-shadow:
    0 0 0 1px rgba(4, 8, 14, 0.88),
    0 6px 14px rgba(0, 0, 0, 0.28),
    0 0 10px color-mix(in srgb, var(--marker-color) 16%, transparent);
}

.atlas-marker::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: -1px;
  width: 13px;
  height: 5px;
  transform: translateX(-50%);
  border-radius: var(--radius-pill);
  background: rgba(0, 0, 0, 0.22);
  filter: blur(4px);
}

.atlas-marker-core,
.atlas-marker-accent {
  position: absolute;
  pointer-events: none;
}

.atlas-marker-core {
  left: 50%;
  top: 7px;
  width: 6px;
  height: 6px;
  transform: translate(-50%, -50%);
  border-radius: var(--radius-pill);
  background: rgba(246, 250, 252, 0.92);
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.12);
}

.atlas-marker-accent {
  left: 50%;
  top: 7px;
  width: 10px;
  height: 10px;
  transform: translate(-50%, -50%);
  border: 1px solid rgba(246, 250, 252, 0.18);
  opacity: 0.52;
}

.atlas-marker-mosque {
  --marker-color: #7ab2e8;
}

.atlas-marker-prayer {
  --marker-color: #79c8a0;
}

.atlas-marker-reception {
  --marker-color: #d98da0;
}

.atlas-marker-mosque .atlas-marker-accent {
  border-radius: var(--radius-pill);
}

.atlas-marker-prayer .atlas-marker-accent {
  border-radius: var(--radius-xs);
  transform: translate(-50%, -50%) rotate(45deg);
}

.atlas-marker-reception .atlas-marker-accent {
  border-radius: var(--radius-xs);
}

.atlas-marker-svg {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 100%;
  filter: drop-shadow(0 2px 4px rgba(0,0,0,0.6));
}

.atlas-marker-svg svg {
  width: 100%;
  height: 100%;
}

.type-mosque {
  color: #7ab2e8;
}

.type-prayer {
  color: #79c8a0;
}

.type-other-worship {
  color: var(--other-worship);
}

.type-reception {
  color: #d98da0;
}

.type-mosque svg path,
.type-prayer svg path,
.type-other-worship svg circle,
.type-reception svg rect {
  stroke: white;
  stroke-width: 2;
  paint-order: stroke fill;
}

.type-school {
  color: #f0c94b;
}

.leaflet-tooltip {
  z-index: 650;
}

/* Leaflet ships its popup white with a white tip, which lands as a bright card
   on a dark map — the one surface here that had never been themed, because it
   is the only one Leaflet draws itself. It carries a marker's own detail (a
   place of worship, a school), so it follows the same panel tokens as every
   other surface and inherits the light theme with them. */
.leaflet-popup-content-wrapper,
.leaflet-popup-tip {
  background: var(--panel-strong);
  color: var(--text);
  box-shadow: var(--shadow-md);
}

.leaflet-popup-content-wrapper {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}

/* The tip is a rotated square: a border on it draws a stray diagonal line
   across the point, so it carries the background alone. */
.leaflet-popup-tip {
  border: 0;
}

.leaflet-popup-content {
  margin: 12px 14px;
  color: var(--text);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
}

.map-popup strong {
  font-size: var(--fs-sm);
}

.map-popup span {
  color: var(--muted);
}

.leaflet-popup-content a {
  color: var(--accent);
}

.leaflet-container a.leaflet-popup-close-button {
  color: var(--muted);
}

.leaflet-container a.leaflet-popup-close-button:hover,
.leaflet-container a.leaflet-popup-close-button:focus-visible {
  color: var(--text);
  background: transparent;
}

.mini-legend {
  display: grid;
  gap: 8px;
}

.mini-legend {
  margin-top: 10px;
  padding: 12px 12px 0;
  border-top: 1px solid rgba(112, 195, 226, 0.08);
  color: var(--muted);
  font-size: var(--fs-xs);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.mini-legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
}

.mini-legend-item:last-child {
  grid-column: 1 / -1;
}

.layer-toggle.is-hidden {
  display: none;
}

.leaflet-div-icon {
  background: none !important;
  border: none !important;
}

.legend-icon {
  display: inline-flex;
  width: 14px;
  height: 14px;
  align-items: center;
  justify-content: center;
}

.legend-icon svg {
  width: 100%;
  height: 100%;
}

details {
  margin-top: 0;
  color: var(--muted);
  font-size: var(--fs-sm);
  border-radius: var(--radius-md);
}

.panel-footnote {
  display: block;
  position: sticky;
  bottom: calc(-1 * var(--panel-pad-bottom));
  margin-top: auto;
  padding: 12px 0;
  border: 0;
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.panel-footnote::before {
  display: none;
}

details summary {
  cursor: pointer;
  color: var(--text);
  letter-spacing: 0.12em;
  font-size: var(--fs-xs);
}

.panel-footnote summary {
  margin: 0;
  line-height: var(--lh-ui);
  padding: 0;
}

.panel-footnote[open] summary {
  margin-bottom: 12px;
}

.panel-footnote > :not(summary) {
  margin: 0;
}

.panel-footnote .note {
  margin-top: 10px;
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-prose);
}

.leaflet-control-attribution {
  background: rgba(3, 5, 10, 0.8) !important;
  color: #bcd4e1 !important;
  border: 1px solid rgba(112, 195, 226, 0.12);
  border-radius: 0 !important;
  padding: 4px 8px;
}

.legend {
  background: var(--panel-card-bg);
  color: #ffffff;
  padding: 8px 10px;
  border: 1px solid var(--panel-card-border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-md);
  font-size: var(--fs-xs);
  line-height: 1.3;
  max-width: 200px;
}

.legend-scale {
  display: grid;
  gap: 6px;
  margin-top: 8px;
}

.legend-scale-item {
  display: grid;
  grid-template-columns: 18px minmax(0, 1fr);
  align-items: center;
  gap: 8px;
  font-size: var(--fs-xs);
  color: #afcad7;
}

.legend-swatch {
  display: block;
  width: 18px;
  height: 12px;
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: var(--radius-xs);
}

.leaflet-bottom.leaflet-left {
  bottom: var(--safe-bottom);
  left: var(--safe-left);
  transition: left 0.22s ease;
}

.menu-open .leaflet-bottom.leaflet-left {
  left: calc(var(--menu-actual-width, var(--panel-width)) + var(--safe-left));
}

.leaflet-bottom.leaflet-right {
  bottom: var(--safe-bottom);
  right: var(--safe-right);
}

/* The attribution control is empty (prefix:false); hide it but keep the
   bottom-right corner available for the colour-scale legend. */
.leaflet-control-attribution {
  display: none !important;
}

/* ── Zoom button theme ── */
.leaflet-control-zoom {
  border: none !important;
  box-shadow: var(--shadow) !important;
}

.leaflet-control-zoom a {
  background: rgba(15, 21, 33, 0.96) !important;
  color: var(--accent) !important;
  border: 1px solid var(--border) !important;
  width: 36px !important;
  height: 36px !important;
  line-height: 36px !important;
  font-size: var(--fs-lg) !important;
}

.leaflet-control-zoom a:hover {
  background: rgba(20, 28, 42, 0.98) !important;
  border-color: rgba(112, 195, 226, 0.32) !important;
  color: #fff !important;
}

.leaflet-control-zoom-in {
  border-radius: 10px 10px 0 0 !important;
  border-bottom: none !important;
}

.leaflet-control-zoom-out {
  border-radius: 0 0 10px 10px !important;
}

@media (max-width: 1180px) {
  :root {
    --mobile-map-modes-offset: 10px;
  }

  .mobile-map-modes {
    /* Hidden on narrow windows at the user's request (these quick-metric chips
       only ever appeared in this <=1180px range). */
    display: none;
    max-width: calc(100% - var(--safe-left) - var(--safe-right));
  }

  .info-card {
    width: clamp(192px, 28.8vw, 256px);
  }
}

@media (min-width: 1500px), (min-height: 1100px) {
  :root {
    --top-bar-height: 50px;
    --panel-width: min(390px, calc(100vw - var(--safe-left) - var(--safe-right) - 16px));
    --stats-width: clamp(288px, 19.2vw, 368px);
  }

  .top-bar {
    padding-inline: 0;
  }

  .top-bar-tabs-mount {
    max-width: 100%;
  }

  .top-bar-search-slot {
    max-width: 620px;
  }

  .top-bar-toggle {
    left: 14px;
    width: 42px;
    height: var(--btn-height);
  }

  .top-bar-title {
    font-size: var(--fs-md);
  }

  .top-bar-pill {
    right: 16px;
    padding: 7px 12px;
  }

  .utility-panel {
    --panel-pad-x: 14px;
    --panel-pad-top: 14px;
    --panel-pad-bottom: 14px;
    --panel-gap: 15px;
  }

  .stats-card {
    padding: 14px 15px;
    gap: 10px;
  }



  .control-group {
    padding: 12px;
  }

  .layer-button {
    height: var(--btn-height) !important;
    padding-inline: 14px;
  }

  .panel-hint,
  .layer-meta,
  .panel-footnote .note {
    font-size: var(--fs-sm);
  }

  .stats-primary-value {
    font-size: clamp(2.125rem, 3vw, 3rem);
  }

  .info-card .stats-primary-value {
    font-size: clamp(1.625rem, 2.2vw, 2.125rem);
  }
}

@media (max-width: 920px) {
  :root {
    --mobile-map-modes-offset: 58px;
  }

  .menu-open .leaflet-bottom.leaflet-left {
    left: var(--safe-left);
  }

  .app-shell {
    display: block;
  }

  .top-bar {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--top-bar-height);
    padding: 0;
    border-bottom-color: rgba(112, 195, 226, 0.16);
    background:
      linear-gradient(180deg, rgba(10, 14, 22, 0.9), rgba(6, 9, 14, 0.58));
    backdrop-filter: blur(12px);
  }

  .top-bar-layout.top-bar-layout--elm {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--top-bar-height);
    border-bottom-color: rgba(112, 195, 226, 0.16);
    background:
      linear-gradient(180deg, rgba(10, 14, 22, 0.9), rgba(6, 9, 14, 0.58));
    backdrop-filter: blur(12px);
  }

  body.light-theme .top-bar,
  body.light-theme .top-bar-layout.top-bar-layout--elm {
    background: rgba(255, 255, 255, 0.82);
  }

  .top-bar-layout {
    padding: 0 calc(var(--safe-right) + 8px) 0 calc(var(--safe-left) + 6px);
  }

  .top-bar-toggle {
    width: 38px;
    height: 36px !important;
  }

  .top-bar-title,
  .top-bar-pill {
    display: none;
  }

  .top-bar-search-slot {
    max-width: 360px;
  }

  .top-bar-tabs {
    display: none;
  }

  .mobile-nav-dropdown {
    display: flex;
    align-items: center;
    max-width: min(48vw, 210px);
  }

  .mobile-nav-trigger {
    width: 100%;
    justify-content: center;
    min-height: 36px;
  }

  .map-stage {
    position: fixed;
    inset: 0;
    height: 100dvh;
  }

  .cartogram-stage {
    position: fixed;
    inset: 0;
    height: 100dvh;
  }

  /* .app-shell drops to display:block here, so these stages stop being grid
     items and no longer inherit the shell's height -- they grow to fit their
     content instead, and their own overflow-y never has anything to scroll.
     Pinning them to the viewport gives the scrollport back. */
  .home-stage,
  .knowledge-stage,
  .history-stage,
  .tools-stage,
  .info-stage,
  .area-stage {
    position: fixed;
    inset: 0;
    height: 100dvh;
    max-height: 100dvh;
    overflow-y: auto;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
  }

  /* Pinch-zoom releases the shell. See initPinchZoomRelease() in app_ui.js for
     why: while the stage is a fixed, self-scrolling box the document has
     nothing to scroll, so the browser can only pan inside one screenful and
     every drag after that turns into a scroll of the inner box. Zoomed in,
     that leaves half of each line off-screen with no way to reach it.

     The map and the cartogram keep their fixed shell. A pinch there is the
     map's own gesture, not the browser's. */
  /* A pinch on the map is the map's own zoom, so the browser gets no page
     zoom to start there: the gesture is resolved as the intersection of the
     touch-action values down the hit-test path, so pan-y here keeps one-finger
     scrolling in the panels, stops the browser zoom from starting anywhere in
     the view, and still loses to the Leaflet container's own `none`. Without
     this a pinch begun on the top bar or the side panel scaled the whole fixed
     interface with nothing to scroll and no way back but pinching out. */
  body[data-view="map"],
  body[data-view="cartogram"] {
    touch-action: pan-y;
  }

  html.is-pinch-zoomed,
  html.is-pinch-zoomed body,
  html.is-pinch-zoomed .app-shell {
    overflow: visible;
    height: auto;
    max-height: none;
  }

  html.is-pinch-zoomed .home-stage,
  html.is-pinch-zoomed .knowledge-stage,
  html.is-pinch-zoomed .history-stage,
  html.is-pinch-zoomed .tools-stage,
  html.is-pinch-zoomed .info-stage,
  html.is-pinch-zoomed .area-stage {
    position: static;
    height: auto;
    max-height: none;
    overflow: visible;
  }

  /* Pinned to the top of the layout viewport, a bar sits over the content the
     reader zoomed in to read. It scrolls away with the page instead. */
  html.is-pinch-zoomed .top-bar,
  html.is-pinch-zoomed .top-bar-layout.top-bar-layout--elm {
    position: absolute;
  }

  .home-stage {
    padding-top: calc(var(--safe-top) + var(--top-bar-height) + 28px);
  }

  /* The area and origin pages both render into a .area-stage. They arrived
     after the rule above was written and were never added to it, so on mobile
     they grew to their own content inside a display:block shell, their
     overflow-y had nothing left to scroll, and .app-shell clipped everything
     past the first screen. */
  .area-stage {
    padding-top: calc(var(--safe-top) + var(--top-bar-height));
  }

.knowledge-stage {
    padding-top: calc(var(--safe-top) + var(--top-bar-height));
  }

  .utility-panel {
    top: var(--top-bar-height);
    left: calc(var(--safe-left) + 10px);
    right: calc(var(--safe-right) + 10px);
    bottom: auto;
    width: auto;
    min-width: 0;
    max-width: none;
    height: auto;
    max-height: min(82dvh, calc(100dvh - var(--top-bar-height) - var(--safe-bottom)));
    padding-top: 14px;
    padding-bottom: max(16px, var(--safe-bottom));
    border: 1px solid rgba(112, 195, 226, 0.22);
    border-right: 1px solid rgba(112, 195, 226, 0.22);
    border-radius: var(--radius-lg);
    resize: none;
    box-shadow: var(--shadow-lg);
    transform: translateY(-12px) scale(0.98);
    transform-origin: top center;
    visibility: hidden;
  }

  .utility-panel.is-open {
    transform: translateY(0) scale(1);
    visibility: visible;
  }

  .mobile-map-modes {
    left: 0;
    right: 0;
    max-width: none;
    flex-wrap: nowrap;
    align-items: center;
    gap: 7px;
    overflow-x: auto;
    overflow-y: hidden;
    padding: 0 calc(var(--safe-right) + 10px) 7px calc(var(--safe-left) + 10px);
    scroll-padding-inline: calc(var(--safe-left) + 10px) calc(var(--safe-right) + 10px);
    scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch;
    -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
    mask-image: linear-gradient(90deg, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
    transition: opacity 0.16s ease, visibility 0.16s ease, transform 0.16s ease;
  }

  .menu-open .mobile-map-modes {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
    transform: translateY(-6px);
  }

  .mobile-map-mode-button {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    height: auto;
    min-height: 40px;
    max-width: min(68vw, 230px);
    padding-inline: 14px;
    scroll-snap-align: start;
  }
  .mini-legend {
    grid-template-columns: 1fr;
  }

  .info-card {
    left: calc(var(--safe-left) + 8px);
    right: calc(var(--safe-right) + 8px);
    top: auto;
    bottom: calc(var(--safe-bottom) + 8px);
    width: auto;
    max-height: min(42dvh, calc(100dvh - var(--top-bar-height) - var(--safe-top) - 96px));
    border-radius: var(--radius-lg);
    transform: translateY(18px);
    z-index: 735;
  }

  .info-card.is-visible {
    transform: translateY(0);
  }
}

@media (max-width: 640px) {
  :root {
    --mobile-map-modes-offset: 56px;
  }

  .utility-panel {
    top: var(--top-bar-height);
    left: calc(var(--safe-left) + 8px);
    right: calc(var(--safe-right) + 8px);
    max-height: min(84dvh, calc(100dvh - var(--top-bar-height) - var(--safe-bottom)));
    padding: 12px 12px max(16px, var(--safe-bottom));
    --panel-gap: 10px;
  }

  .mobile-map-modes {
    display: none !important;
    gap: 7px;
    flex-wrap: wrap;
    overflow: visible;
    padding: 0 calc(var(--safe-right) + 10px) 7px calc(var(--safe-left) + 10px);
    -webkit-mask-image: none;
    mask-image: none;
    scroll-snap-type: none;
  }

  .mobile-map-mode-button {
    flex: 1 1 calc(33.333% - 7px);
    width: auto;
    min-width: 0;
    max-width: none;
    min-height: 42px;
    padding-inline: 14px;
    letter-spacing: 0.08em;
    line-height: 1.2;
    text-align: center;
    white-space: normal;
  }

  .stats-value-row,
  .stats-supplementary-row {
    flex-direction: column;
    align-items: flex-start;
  }

  .control-group {
    padding: 9px;
  }

  .custom-dropdown-trigger,
  .mun-search-input,
  .layer-button,
  .map-quick-city-btn {
    min-height: 42px;
  }
}

/* ── Mobile nav dropdown ── */
.mobile-nav-dropdown {
  display: none;
  position: relative;
  min-width: 0;
  /* Reset the generic <details> card box so only the pill trigger shows
     (otherwise a 14px-rounded rectangle frames the round button). */
  border: none;
  background: transparent;
  border-radius: 0;
}

.mobile-nav-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  /* Gives way before the buttons beside it do: on the narrowest phones the
     label truncates rather than pushing them off their own column. */
  min-width: 0;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  min-height: 32px;
  padding: 0 12px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  outline: none;
  color: var(--text);
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s, border-color 0.15s, color 0.15s, box-shadow 0.15s;
  white-space: nowrap;
}
.mobile-nav-trigger > span {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mobile-nav-trigger::-webkit-details-marker {
  display: none;
}
.mobile-nav-trigger::marker {
  content: "";
}
.mobile-nav-trigger:hover,
.mobile-nav-trigger.is-open,
.mobile-nav-dropdown[open] .mobile-nav-trigger {
  background: var(--control-hover-bg);
  border-color: var(--control-hover-border);
  color: var(--accent);
  box-shadow: var(--control-shadow-ring);
}
.mobile-nav-chevron {
  width: 10px; height: 6px;
  flex-shrink: 0;
  transition: transform 0.2s;
}
.mobile-nav-trigger.is-open .mobile-nav-chevron,
.mobile-nav-dropdown[open] .mobile-nav-chevron {
  transform: rotate(180deg);
}
.mobile-nav-menu {
  display: none;
  position: fixed;
  z-index: 9000;
  background: var(--panel-strong);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  padding: 4px;
  list-style: none;
  margin: 0;
  box-shadow: var(--menu-surface-shadow);
  backdrop-filter: blur(10px);
  min-width: 132px;
  max-width: calc(100vw - 16px);
}

.mobile-nav-menu.is-open {
  display: block;
}

.mobile-nav-dropdown[open] .mobile-nav-menu:not(.is-open) {
  display: block;
  position: absolute;
  top: calc(100% + 4px);
  left: 0;
}

.mobile-nav-menu li {
  padding: 0;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0.02em;
  color: var(--muted);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
}
.mobile-nav-link {
  display: block;
  padding: 9px 12px;
  border-radius: inherit;
  color: inherit;
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mobile-nav-item--action {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid var(--panel-rule);
}

.mobile-nav-search {
  display: block;
  width: 100%;
  background: none;
  border: 0;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.mobile-nav-menu li:hover { background: rgba(112, 195, 226, 0.1); color: var(--text); }

.mobile-nav-menu li[aria-selected="true"] {
  color: var(--accent);
  background: rgba(112, 195, 226, 0.08);
}

/* ── Chart Stage ── */
.chart-stage {
  position: relative;
  width: 100%;
  height: 100%;
  /* 94dvh, not 85: the sidebar is the tallest thing in the card, and at 85 it
     began scrolling on any viewport shorter than about 1080 CSS px -- which is
     most laptops once display scaling is applied. Measured across the whole
     chart view, 94dvh is the smallest value that leaves every sidebar
     unscrolled down to a 950 px viewport, and it still leaves ~60px of the next
     card showing so the stage reads as scrollable. */
  --chart-shell-height: 94dvh;
  --chart-stage-top-gap: clamp(20px, 1.8vw, 26px);
  /* Separate from --chart-stage-top-gap (which also drives ordinary visual
     margins, e.g. the TOC sidebar/drawer) so bumping the scroll offset for
     the fixed top bar on mobile can't push those unrelated margins around. */
  --chart-scroll-margin: var(--chart-stage-top-gap);
  --chart-toc-width: clamp(238px, 18.75vw, 290px);
  --chart-toc-peek: 0px;
  --chart-toc-transition: 260ms cubic-bezier(0.22, 1, 0.36, 1);
  padding: 0 clamp(22px, 2vw, 30px) 0 clamp(22px, 2vw, 30px);
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  justify-content: center;
  gap: clamp(18px, 2vw, 32px);
  overflow: auto;
  scroll-behavior: auto;
  scroll-padding-top: var(--chart-scroll-margin);
  scroll-padding-bottom: 0;
  /* Snapping is handled in JS (snapChartStageToNearestTarget), which centres the
     target. Native CSS scroll-snap targeted `start` instead, so the browser
     re-snapped right after the JS animation and the view juddered. */
  scroll-snap-type: none;
}

.chart-container-shell.is-deferred {
  display: none;
}

/* On desktop a deferred chart keeps its box in the layout and only hides its
   contents. The shell height is fixed (--chart-shell-height) whether or not the
   chart has drawn, so reserving the space costs nothing, and revealing a chart no
   longer reflows the page: with display:none all ten shells dropped into the flow
   at once when revealChartContainers() ran, measuring a cumulative layout shift
   of 1.29 on dev (0.1 is the "good" threshold).

   Deliberately desktop-only. The mobile stage scrolls one card at a time, so a
   display:none shell already displaced nothing there — dev measures 0.008 — and
   reserving space instead reflowed the visible card's own scroll wrapper, taking
   mobile to 0.31. Keep the two paths separate.

   Note this only reproduces against a real CDN. Served locally the bundles
   arrive uncompressed and serially, the reveal lands before first paint, and
   both variants measure ~0.07 — so verify this one on dev, not localhost. */
@media (min-width: 769px) {
  .chart-container-shell.is-deferred {
    display: flex;
    visibility: hidden;
  }
}

.chart-hover-overlay {
  position: absolute;
  inset: 0;
  z-index: 5;
  pointer-events: all;
}

.chart-methodology-panel {
  width: 100%;
  max-width: min(1680px, 100%);
  margin: 0 auto;
  scroll-snap-align: start;
  scroll-margin-top: var(--chart-scroll-margin);
  box-sizing: border-box;
  background: var(--panel-strong);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 22px;
}

/* The origin groups, explained once for every chart that names them. It sits
   just above the sources panel and wears the same card. */
.chart-groups {
  width: 100%;
  max-width: min(1680px, 100%);
  margin: 0 auto 20px;
  scroll-snap-align: start;
  scroll-margin-top: var(--chart-scroll-margin);
  box-sizing: border-box;
  background: var(--panel-strong);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
  padding: 22px;
}

.chart-groups-title {
  margin: 0 0 10px;
  font-size: var(--fs-lg);
  font-weight: 600;
}

.chart-groups p {
  margin: 0 0 12px;
  max-width: 74ch;
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
}

.chart-groups p:last-child {
  margin-bottom: 0;
}

.chart-groups-list {
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  gap: 6px 18px;
  margin: 0 0 14px;
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
}

.chart-groups-list dt {
  font-weight: 600;
  color: var(--text);
}

.chart-groups-list dd {
  margin: 0;
  color: var(--muted);
}

@media (max-width: 560px) {
  .chart-groups {
    padding: 14px;
  }

  .chart-groups-list {
    grid-template-columns: minmax(0, 1fr);
    gap: 2px;
  }

  .chart-groups-list dd {
    margin-bottom: 8px;
  }
}

.chart-groups-link {
  color: var(--accent);
  font-size: var(--fs-xs);
}

.welfare-legend-block .chart-groups-link {
  display: inline-block;
  margin-top: 8px;
}

.chart-methodology-panel > summary {
  cursor: pointer;
  color: var(--muted);
  letter-spacing: 0.12em;
  font-size: var(--fs-xs);
  user-select: none;
}

.chart-methodology-panel > summary:hover {
  color: var(--text);
}

.chart-methodology-body {
  margin-top: 20px;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.utility-panel-methodology {
  margin: 4px 0 0;
  padding: 12px 14px;
  font-size: var(--fs-xs);
}

.utility-panel-methodology .chart-methodology-body {
  margin-top: 14px;
  gap: 14px;
}

.utility-panel-methodology .chart-method-title {
  font-size: var(--fs-xs);
}

.utility-panel-methodology .chart-method-block {
  padding-bottom: 12px;
}

.chart-method-block {
  padding-bottom: 18px;
  border-bottom: 1px solid rgba(112, 195, 226, 0.07);
}

.chart-method-section-label {
  margin: 4px 0 -4px;
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
}

.chart-method-block:last-child {
  border-bottom: none;
  padding-bottom: 0;
}

.chart-method-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
  margin: 0 0 4px 0;
}

.chart-method-source {
  font-size: var(--fs-xs);
  color: var(--muted-soft);
  margin: 0 0 8px 0;
}

.chart-method-block p {
  font-size: var(--fs-sm);
  color: var(--muted-soft);
  line-height: var(--lh-prose);
  margin: 0 0 6px 0;
}

.chart-method-block p:last-child {
  margin-bottom: 0;
}

.chart-method-block strong {
  color: var(--text);
  font-weight: 600;
}

.chart-container-shell {
  width: 100%;
  max-width: min(1680px, 100%);
  margin: 0 auto;
  height: var(--chart-shell-height);
  min-height: var(--chart-shell-height);
  max-height: var(--chart-shell-height);
  scroll-snap-align: start;
  scroll-margin-top: var(--chart-scroll-margin);
  content-visibility: auto;
  contain-intrinsic-size: var(--chart-shell-height);
  background: var(--panel-strong);
  backdrop-filter: blur(12px);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  box-shadow: var(--shadow);
  overflow: hidden;
}

.chart-body-row {
  display: flex;
  flex-direction: row;
  flex: 1;
  gap: 20px;
  min-height: 0;
  overflow: hidden;
}

/* Population chart: keep compact so the key-figure cards fit above the fold */
#popChartWrap {
  min-height: 0;
}
#popChartWrap .chart-body-row {
  flex: 1 1 0;
}
#popChartWrap .chart-main {
  min-height: 0;
  flex: 1 1 0;
}
#popChartWrap .main-svg-chart {
  height: 100%;
}

.chart-sidebar {
  width: 210px;
  flex-shrink: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: 2px;
  scrollbar-gutter: stable;
}

.chart-sidebar-section {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.chart-sidebar-mun {
  flex: none;
  display: flex;
  flex-direction: column;
}

.chart-sidebar-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
}

.chart-mun-search-wrap {
  position: relative;
  display: flex;
  align-items: center;
  margin-bottom: 10px;
}

#natBgPickerGroup .chart-mun-search-wrap {
  margin-top: 10px;
}

.chart-mun-search {
  width: 100%;
  box-sizing: border-box;
  min-height: 36px;
  background: var(--control-bg);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-md);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.03em;
  padding: 8px 34px 8px 12px;
  outline: none;
  transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
}

.chart-mun-search::placeholder {
  color: var(--muted);
}

.chart-mun-search:focus {
  border-color: var(--control-hover-border);
  background: var(--control-hover-bg);
  box-shadow: var(--control-shadow-ring);
}

/* hide the native clear button in webkit */
.chart-mun-search::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

/* Both clear-search buttons render an inline SVG X instead of a text glyph.
   The SVG has no intrinsic size, so it is sized here; stroke is currentColor,
   so the buttons' own hover colour still drives it. */
.search-clear-icon {
  width: 11px;
  height: 11px;
  display: block;
  flex-shrink: 0;
  pointer-events: none;
}

.chart-mun-search-clear {
  position: absolute;
  right: 8px;
  width: 20px;
  height: 20px;
  display: none;
  align-items: center;
  justify-content: center;
  background: rgba(112, 195, 226, 0.06);
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  color: var(--muted);
  cursor: pointer;
  font-size: var(--fs-xs);
  line-height: 1;
  padding: 0;
  opacity: 0.85;
  transition: opacity 0.1s ease, color 0.1s ease, background 0.1s ease, border-color 0.1s ease;
}

.chart-mun-search-clear.is-visible {
  display: inline-flex;
}

.chart-mun-search-clear:hover {
  opacity: 1;
  color: var(--text);
  background: var(--control-hover-bg);
  border-color: var(--control-border);
}

.chart-mun-divider {
  height: 1px;
  background: var(--border);
  margin: 4px 2px;
  opacity: 0.6;
}

.chart-mun-presets {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

/* Which question the election map answers. It governs the party list below it,
   so it is separated by a rule rather than reading as three more presets. */
.election-metric-modes {
  margin-bottom: 10px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

/* Turnout is a property of the area, not of a party, so the party list has
   nothing to say while it is showing. Dimmed rather than hidden: the selection
   is still there, and still visible, when the reader switches back. */
#partyPickerGroup.is-inactive > *:not(.election-metric-modes):not(:first-child) {
  opacity: 0.45;
  pointer-events: none;
}

.chart-preset-option {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
  font-size: var(--fs-sm);
  /* Leading, not 1: a label long enough to wrap ("Työttömiä työnhakijoita
     (lkm)") had its two lines sitting straight on top of each other. Single-line
     pills are unaffected — min-height still sets their height. */
  line-height: var(--lh-ui);
  color: var(--text);
  min-height: 34px;
  padding: 8px 10px 8px 8px;
  border: 1px solid rgba(112, 195, 226, 0.1);
  border-radius: var(--radius-pill);
  background: var(--surface-raised);
  box-shadow: inset 0 1px 0 var(--card-inset-border);
}

.chart-preset-option input[type="radio"] {
  appearance: none;
  -webkit-appearance: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border-radius: 50%;
  border: 2px solid var(--muted);
  flex-shrink: 0;
  cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
  position: relative;
}

.chart-preset-option input[type="radio"]:checked {
  border-color: var(--accent);
  background: radial-gradient(circle, var(--accent) 45%, transparent 46%);
}

.chart-preset-option input[type="radio"]:hover:not(:checked) {
  border-color: var(--accent);
}

.chart-preset-option:hover {
  border-color: rgba(112, 195, 226, 0.18);
}

.chart-std-toggle {
  display: flex;
  align-items: center;
  gap: 9px;
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  min-height: 34px;
  padding: 7px 10px;
  border: 1px solid rgba(112, 195, 226, 0.1);
  border-radius: var(--radius-md);
  background: var(--surface-raised);
  box-shadow: inset 0 1px 0 var(--card-inset-border);
}

.chart-std-toggle input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  width: 34px;
  height: 18px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--muted);
  background: rgba(112, 195, 226, 0.08);
  flex-shrink: 0;
  cursor: pointer;
  position: relative;
  transition: background 0.15s, border-color 0.15s;
}

.chart-std-toggle input[type="checkbox"]::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 2px;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: var(--muted);
  transform: translateY(-50%);
  transition: left 0.15s, background 0.15s;
}

.chart-std-toggle input[type="checkbox"]:checked {
  border-color: var(--accent);
  background: var(--accent);
}

.chart-std-toggle input[type="checkbox"]:checked::after {
  left: 18px;
  background: #06222b;
}

.chart-std-toggle input[type="checkbox"]:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.chart-std-details {
  margin: 5px 2px 0;
  border: none;
  background: none;
  border-radius: 0;
}

/* A bare arrow + text disclosure — no box, no uppercase. */
.chart-std-summary {
  list-style: none;
  cursor: pointer;
  font-size: var(--fs-xs);
  color: var(--muted);
  text-transform: none;
  letter-spacing: normal;
  opacity: 0.85;
  user-select: none;
  outline: none;
  border: none;
  background: none;
  box-shadow: none;
  padding: 0;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}

.chart-std-summary::-webkit-details-marker {
  display: none;
}

.chart-std-summary::before {
  content: "▸";
  display: inline-block;
  font-size: var(--fs-xs);
  line-height: 1;
  transition: transform 0.15s ease;
}

.chart-std-details[open] .chart-std-summary::before {
  transform: rotate(90deg);
}

.chart-std-summary:hover {
  opacity: 1;
}

.chart-std-details[open] .chart-std-summary {
  margin-bottom: 4px;
}

/* Sidebar sections hidden via the `hidden` attribute (e.g. the standardisation
   toggle when the current metric can't be standardised). */
.chart-sidebar-section[hidden] {
  display: none !important;
}

.chart-std-hint {
  margin: 5px 2px 0;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.chart-std-details .chart-std-hint {
  margin: 0 0 2px;
}

.chart-mun-list {
  flex: 1;
  overflow-y: auto;
  border: 1px solid rgba(112, 195, 226, 0.1);
  border-radius: var(--radius-md);
  padding: 6px 6px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-height: 120px;
  max-height: 220px;
  background: var(--surface-raised);
  box-shadow: inset 0 1px 0 var(--card-inset-border);
}

/* The crime-by-nationality sidebar carries six presets plus the offence-type
   dropdown and the standardisation toggle, so at the shared 220px cap above its
   content overflowed and turned the whole sidebar into a scroller. Trim only
   this list — the sidebar's own scrollbar is the worse of the two. */
.nat-country-list {
  max-height: 176px;
}

/* Per-chart list heights: these sidebars have room to spare, so give their
   long lists more of it (municipalities ~1.2x, languages ~1.5x the shared cap). */
#popChartWrap .nat-country-list {
  max-height: 210px;
}
#langSpeakersWrap .nat-country-list {
  max-height: 264px;
}

/* Short desktop viewports. Without this the sidebar starts scrolling at about
   920px of height, well before the screen is actually full. The country list is
   the only block with give -- it floors at its own min-height -- and the
   section gaps hold the rest. Width-guarded because the narrow layout below
   768px sizes this sidebar on its own terms. */
@media (min-width: 769px) and (max-height: 940px) {
  .chart-sidebar {
    gap: 10px;
  }

  /* The two id-scoped caps below have to be repeated here: a media query adds
     no specificity, so #popChartWrap would otherwise keep winning. */
  .chart-mun-list,
  .nat-country-list,
  #popChartWrap .nat-country-list,
  #langSpeakersWrap .nat-country-list {
    max-height: 150px;
  }
}

@media (min-width: 769px) and (max-height: 870px) {
  .chart-mun-list,
  .nat-country-list,
  #popChartWrap .nat-country-list,
  #langSpeakersWrap .nat-country-list {
    max-height: 110px;
    min-height: 90px;
  }
}

/* The municipality picker was capped at 150px on a laptop while the sidebar
   left about 200px below it unused: five of 308 municipalities visible in a
   list 10 790px long, with room for twelve. Flex cannot take that space -- the
   sidebar scrolls rather than distributing a fixed height, so the cap is what
   sizes the list -- so the cap follows the viewport instead of being one
   constant. Measured at three heights before and after: 1080px gives 7 rows ->
   18, 900px gives 5 -> 12, 820px gives 4 -> 10, each leaving the sidebar a few
   pixels of slack rather than overflowing it. The max() keeps every earlier
   value as a floor, so no screen gets a shorter list than it had. */
@media (min-width: 769px) {
  .chart-mun-list,
  .nat-country-list,
  #popChartWrap .nat-country-list,
  #langSpeakersWrap .nat-country-list {
    max-height: max(150px, calc(100vh - 570px));
  }
}

.chart-mun-item {
  display: flex;
  align-items: center;
  min-width: 0;
  gap: 7px;
  padding: 5px 8px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-xs);
  color: var(--text);
  transition: background 0.1s, border-color 0.1s;
  border: 1px solid transparent;
}

.chart-mun-item:hover {
  background: rgba(112, 195, 226, 0.07);
  border-color: rgba(112, 195, 226, 0.08);
}

.chart-mun-cb {
  appearance: none;
  -webkit-appearance: none;
  width: 15px;
  height: 15px;
  border-radius: var(--radius-xs);
  border: 2px solid var(--muted);
  flex-shrink: 0;
  cursor: pointer;
  position: relative;
  transition: border-color 0.15s, background 0.15s;
}

.chart-mun-cb:checked {
  border-color: var(--accent);
  background: var(--accent);
}

/* The tick is drawn on ::before, not ::after, because this checkbox also
   carries a data-tip and the tooltip is drawn on [data-tip]:hover::after. Both
   rules would land on the same pseudo-element, and the tick's rotate(40deg)
   would then tilt the tooltip's text diagonally across the list. */
.chart-mun-cb:checked::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 3px;
  height: 6px;
  border: 1.5px solid var(--bg);
  border-top: none;
  border-left: none;
  transform: translate(-50%, -58%) rotate(40deg);
}

.chart-mun-cb:hover:not(:checked) {
  border-color: var(--accent);
}

.chart-mun-name {
  cursor: pointer;
  flex: 1;
  min-width: 0;
  user-select: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chart-mun-name:hover {
  color: var(--accent);
}

.chart-main-area {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
  overflow: hidden;
}

.chart-view-title {
  padding-left: 0;
  margin: 0;
}

.chart-select {
  height: 36px;
  width: 100%;
  padding: 0 12px;
  border-radius: var(--radius-md);
  background: var(--panel);
  border: 1px solid var(--line);
  color: var(--text);
  font-size: var(--fs-sm);
  cursor: pointer;
}

.chart-toggle-btn {
  height: 34px;
  padding: 0 14px;
  border-radius: var(--radius-md);
  background: var(--panel);
  border: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-sm);
  cursor: pointer;
  transition: background 0.15s, color 0.15s, border-color 0.15s;
  align-self: flex-start;
}

.chart-toggle-btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #000;
}

.chart-toggle-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.chart-main {
  position: relative;
  width: 100%;
  flex: 1 1 0;
  min-height: 0;
  overflow: hidden;
}

.main-svg-chart {
  width: 100%;
  height: 100%;
  overflow: visible;
}

/* The reader's two missing pieces, both written from the chart's own spec:
   a line saying what it currently shows, and the numbers behind it. The line
   sits above the chart because it is the thing to read first, the figures sit
   under the source note because they are the thing to check last. */
.chart-finding {
  margin: 0 0 10px;
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--muted);
  max-width: 72ch;
}

/* The bare `details` in this sheet is a bordered, filled panel. Around a
   one-word toggle that drew an empty box the full width of the card and took a
   row off the chart for nothing - the same trap .kb-article-more documents.
   Closed, this has to read as one more word under the source line. */
.chart-figures {
  margin: 6px 0 0;
  border: 0;
  border-radius: 0;
  background: none;
  padding: 0;
  font-size: var(--fs-sm);
}

/* The sheet's `details summary` is a small-caps control. Here it is a quiet
   word beside the source, so it takes that line's size and spacing back. */
.chart-figures-summary {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  cursor: pointer;
  /* The row was 16px tall between a caveat line and the source line, which is
     both cramped to read and small to hit. The padding is inside the control,
     so it grows the target rather than only the gap. */
  padding: 6px 0;
  color: var(--muted);
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: normal;
  list-style: none;
}

.chart-figures-summary:hover,
.chart-figures-summary:focus-visible,
.chart-figures[open] > .chart-figures-summary {
  color: var(--accent);
}

.chart-figures-summary::-webkit-details-marker { display: none; }

.chart-figures-summary::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid currentColor;
  border-top: 3.5px solid transparent;
  border-bottom: 3.5px solid transparent;
  opacity: 0.8;
  transition: transform 0.15s ease;
}

.chart-figures[open] .chart-figures-summary::before { transform: rotate(90deg); }

/* A table of 36 years by ten series is wider and taller than the card, so it
   scrolls in both directions rather than stretching the chart shell. */
.chart-figures-scroll {
  max-height: 320px;
  overflow: auto;
  margin-top: 6px;
  border: 1px solid rgba(112, 195, 226, 0.16);
  border-radius: 10px;
}

.chart-figures-table {
  border-collapse: collapse;
  width: 100%;
  font-variant-numeric: tabular-nums;
}

.chart-figures-table th,
.chart-figures-table td {
  padding: 6px 12px;
  text-align: right;
  white-space: nowrap;
  border-bottom: 1px solid rgba(112, 195, 226, 0.1);
}

.chart-figures-table thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  /* The header has to hide the rows that scroll under it. --panel-bg is a 3 %
     tint in the dark theme, so on its own it showed every row straight
     through. Paint the same tint over the opaque page background instead:
     the same colour, with nothing showing behind it. */
  background-color: var(--bg);
  background-image: linear-gradient(var(--panel-bg), var(--panel-bg));
  text-align: right;
  font-weight: 600;
}

.chart-figures-table tbody th[scope="row"],
.chart-figures-table thead th:first-child {
  text-align: left;
  font-weight: 600;
}

@media (prefers-reduced-motion: reduce) {
  .chart-figures-summary::before { transition: none; }
}

.chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  font-size: var(--fs-xs);
  color: var(--muted);
  flex: 0 0 auto;
  min-height: 18px;
  max-height: 60px;
  overflow-y: auto;
  overflow-x: hidden;
}

.chart-legend-item {
  display: flex;
  align-items: center;
  gap: 5px;
}

.chart-legend-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* Welfare chart: legend lives in the left sidebar column, below the controls,
   so the plot gets the full width instead of losing a column to the legend. */
.welfare-sidebar-col {
  display: flex;
  flex-direction: column;
  gap: 16px;
  flex-shrink: 0;
  width: 210px;
  min-height: 0;
}
.welfare-sidebar-col #welfareControlsMount {
  flex-shrink: 0;
}
.welfare-sidebar-col .chart-sidebar {
  width: 100%;
}
.welfare-legend-block {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex-shrink: 0;
}

.welfare-chart-row {
  display: flex;
  flex-direction: row;
  gap: 16px;
  flex: 1 1 0;
  min-height: 0;
}

.welfare-chart-row .welfare-chart-main {
  flex: 1 1 0;
  min-width: 0;
}

.chart-legend--side {
  flex-direction: column;
  flex-wrap: nowrap;
  gap: 10px;
  width: 188px;
  max-height: none;
  overflow: visible;
  flex: 0 0 auto;
  align-self: flex-start;
  padding-top: 4px;
  line-height: 1.3;
}

.chart-legend--side .chart-legend-item {
  align-items: flex-start;
}

.chart-legend--side .chart-legend-dot {
  margin-top: 3px;
}

/* Sidebar variant: fill the column width instead of the fixed 188px */
.chart-legend--sidebar {
  width: 100%;
  padding-top: 0;
}

@media (max-width: 900px) {
  .welfare-chart-row {
    flex-direction: column;
  }

  .chart-legend--side {
    flex-direction: row;
    flex-wrap: wrap;
    width: auto;
    max-height: 60px;
    gap: 8px 16px;
  }

  .chart-legend--side .chart-legend-item {
    align-items: center;
  }

  .chart-legend--side .chart-legend-dot {
    margin-top: 0;
  }
}

.chart-tooltip {
  position: absolute;
  background: var(--panel-strong);
  border: 1px solid var(--accent);
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  pointer-events: none;
  z-index: 10;
  /* Matches .chart-elm-tooltip so the JS-drawn charts and the Elm islands
     read at the same size. */
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  box-shadow: var(--shadow-sm);
  transform: translate(-50%, -100%);
  margin-top: -10px;
  white-space: nowrap;
}

/* ── Municipality search ── */
.mun-search-section {
  padding: 12px;
  gap: 10px;
}

.utility-panel-search-card {
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.03),
    0 12px 28px rgba(0, 0, 0, 0.24);
}

.mun-search-wrap {
  position: relative;
}

.mun-search-input {
  display: flex;
  align-items: center;
  width: 100%;
  box-sizing: border-box;
  min-height: 36px;
  padding: 0 14px 0 38px;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  border-radius: var(--radius-md);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--text);
  outline: none;
  transition: border-color 0.15s ease, background 0.15s ease, box-shadow 0.15s ease;
  appearance: none;
  -webkit-appearance: none;
}

.mun-search-input::placeholder {
  color: var(--muted);
  font-weight: 400;
}

.mun-search-input:focus {
  border-color: var(--control-hover-border);
  background: var(--control-hover-bg);
  box-shadow: var(--control-shadow-ring);
}

.mun-search-input::-webkit-search-cancel-button {
  -webkit-appearance: none;
}

/* Same affordance as the chart search lists' clear button. Pinned to the
   input's own height rather than centred in .mun-search-wrap, because the
   wrapper also holds the results list and would drag the button down with it. */
.mun-search-clear {
  position: absolute;
  right: 9px;
  top: 19px;
  transform: translateY(-50%);
  width: 20px;
  height: 20px;
  display: none;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  background: rgba(112, 195, 226, 0.06);
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: 1;
  cursor: pointer;
  opacity: 0.85;
  transition: opacity 0.1s ease, color 0.1s ease, background 0.1s ease, border-color 0.1s ease;
}

.mun-search-clear.is-visible {
  display: inline-flex;
}

.mun-search-clear:hover {
  opacity: 1;
  color: var(--text);
  background: var(--control-hover-bg);
  border-color: var(--control-border);
}

.mun-search-input {
  padding-right: 34px;
}

.mun-search-icon {
  position: absolute;
  left: 14px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  color: var(--muted);
  pointer-events: none;
  opacity: 0.9;
}

.mun-search-results {
  width: 100%;
}

.map-quick-city-panel {
  display: block;
}

/* Selected-areas box in the search card. JS toggles [hidden] on the section, so
   it only exists visually once more than one area is selected. */
/* "Avaa kuvaaja" under the info box's trend chart. */
.stats-chart-xlink-row {
  display: flex;
  margin-top: 10px;
}

.stats-chart-xlink-row:empty {
  display: none;
}

.stats-chart-xlink-row .chart-xlink {
  width: 100%;
  justify-content: center;
}

.map-selection-panel {
  display: grid;
  gap: 8px;
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--panel-card-border);
}

.map-selection-panel[hidden] {
  display: none;
}

.map-selection-heading {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.map-selection-title {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.1em;
  color: var(--muted);
}

.map-selection-clear {
  border: 0;
  background: none;
  padding: 0;
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 700;
  cursor: pointer;
}

.map-selection-clear:hover {
  text-decoration: underline;
}

.map-selection-list {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.map-selection-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: 0 8px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(255, 91, 123, 0.35);
  background: var(--surface-raised);
  color: var(--text);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  cursor: pointer;
  max-width: 100%;
  transition: border-color 0.18s ease, background 0.18s ease;
}

.map-selection-chip:hover {
  border-color: rgba(255, 91, 123, 0.75);
}

.map-selection-chip-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.map-selection-chip-x {
  color: var(--muted);
}

.map-selection-chip:hover .map-selection-chip-x {
  color: rgba(255, 91, 123, 0.95);
}

.map-selection-hint {
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
  opacity: 0.75;
}

.map-quick-city-buttons {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 8px;
}

.map-quick-city-btn {
  min-height: 32px;
  padding: 0 8px;
  border-radius: var(--radius-md);
  border: 1px solid rgba(112, 195, 226, 0.14);
  background: var(--surface-raised);
  box-shadow: inset 0 1px 0 var(--card-inset-border);
  color: var(--text);
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  transition: border-color 0.18s ease, background 0.18s ease, color 0.18s ease, box-shadow 0.18s ease;
}

.map-quick-city-btn:hover,
.map-quick-city-btn:focus-visible {
  border-color: var(--control-hover-border);
  background: var(--control-hover-bg);
  color: var(--accent);
  box-shadow: var(--control-shadow-ring);
  outline: none;
}

.map-quick-city-btn.is-wide {
  grid-column: 1 / -1;
  padding-inline: 12px;
}

.utility-panel-attribution {
  padding: 6px 0 2px;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.utility-panel-attribution a {
  color: inherit;
  text-decoration-color: rgba(112, 195, 226, 0.3);
  text-underline-offset: 2px;
}

.utility-panel-attribution a:hover {
  color: var(--text);
}

/* ── Nationality crime bar chart ── */
.chart-sidebar-hint {
  font-size: var(--fs-xs);
  color: var(--muted-soft);
}

/* The two sidebar disclosures. The list one has no persistence on purpose:
   opening the full list is a per-task act, and a list left open would put the
   hundreds of boxes back on the first screen next time. */
.chart-list-disclosure,
.chart-advanced {
  border: 0;
  padding: 0;
}

/* Both read as a line of text with a chevron, the same as the map's dataset
   and metric pickers. The chevron already says the row opens something, so a
   box around it was saying it twice. The padding stays, because the row is a
   click target and needs the height. */
.chart-list-disclosure-summary,
.chart-advanced-summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  list-style: none;
  cursor: pointer;
  padding: 7px 2px;
  border: 0;
  background: none;
  box-shadow: none;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--muted);
}

.chart-list-disclosure-summary::-webkit-details-marker,
.chart-advanced-summary::-webkit-details-marker {
  display: none;
}

.chart-list-disclosure-summary::after,
.chart-advanced-summary::after {
  content: "";
  width: 6px;
  height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg);
  flex: 0 0 auto;
  margin-right: 2px;
}

.chart-list-disclosure[open] > .chart-list-disclosure-summary::after,
.chart-advanced[open] > .chart-advanced-summary::after {
  transform: rotate(-135deg);
}

/* With no box to light up, the row itself carries the state: the label and the
   chevron go to the accent colour on hover and while it is open. */
.chart-list-disclosure-summary:hover,
.chart-advanced-summary:hover,
.chart-list-disclosure[open] > .chart-list-disclosure-summary,
.chart-advanced[open] > .chart-advanced-summary {
  color: var(--accent);
}

.chart-list-disclosure[open] > .chart-mun-list {
  margin-top: 6px;
}

.chart-mun-list--chosen {
  margin-bottom: 6px;
}

.chart-advanced[open] > .chart-sidebar-section {
  margin-top: 10px;
}

.chart-data-note {
  font-size: var(--fs-xs);
  color: var(--muted-soft);
  flex: 0 0 auto;
  margin-top: 8px;
}
.chart-data-note a { color: var(--accent); text-decoration: none; }
.chart-subtitle {
  font-size: var(--fs-sm);
  color: var(--muted);
  opacity: 0.8;
  margin-bottom: 4px;
}

.chart-source-link {
  color: var(--accent);
  text-decoration: none;
}
.chart-source-link:hover {
  opacity: 1;
  text-decoration: underline;
}

.nat-count-badge {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--accent-dim);
  background: rgba(112, 195, 226, 0.07);
  border: 1px solid rgba(112, 195, 226, 0.12);
  border-radius: var(--radius-sm);
  padding: 1px 6px;
  letter-spacing: 0;
  text-transform: none;
}

/* Nat bar chart grows to fill available column space, leaving room for slider */
.nat-bar-chart-main {
  position: relative;
  flex: 1;
  min-height: 0;
  margin-top: 30px;
  overflow: hidden;
}

.nat-bar-chart-main .main-svg-chart,
.subsidy-bar-chart-main .main-svg-chart,
.lang-speakers-chart-main .main-svg-chart,
#originWelfareWrap .main-svg-chart,
#natBarChartWrap .main-svg-chart {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

/* ── Subsidy dependency bar chart ── */
.subsidy-group-note {
  flex: 0 0 auto;
  margin: 6px 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: var(--lh-prose);
}

/* Subsidy chart: swap the origin-group bottom notes for the language note when
   the breakdown is "by mother tongue". */
.subsidy-language-note { display: none; line-height: var(--lh-prose); }
#subsidyBarWrap.is-language-breakdown .subsidy-origin-note { display: none; }
#subsidyBarWrap.is-language-breakdown .subsidy-language-note { display: block; }
/* Only a handful of areas in language mode, so the search box is just clutter. */
#subsidyBarWrap.is-language-breakdown .chart-mun-search-wrap { display: none; }

#subsidyMetricDropdown .custom-dropdown-label,
#econMetricDropdown .custom-dropdown-label,
#migriDatasetDropdown .custom-dropdown-label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1;
}

.subsidy-bar-chart-main {
  position: relative;
  flex: 1;
  min-height: 0;
}

/* ── Elm chart renderer mount + hover tooltip ── */
.subsidy-elm-mount { width: 100%; height: 100%; }
.chart-elm-wrap { position: relative; width: 100%; height: 100%; touch-action: pan-y; }
/* On a phone the box would sit under the finger and, at the foot, over half
   the plot. It is a one-strip readout across the top of the chart instead:
   the year and the values on one wrapped line, where the Elm-set left and
   top no longer apply. */
@media (max-width: 700px) {
  .chart-elm-tooltip {
    left: 0 !important;
    right: 0;
    top: 0 !important;
    max-width: none;
    padding: 6px 10px;
    display: flex;
    flex-wrap: wrap;
    gap: 2px 12px;
    align-items: baseline;
  }

  .chart-elm-tooltip-title {
    margin: 0;
    padding: 0;
    border-bottom: 0;
  }

  .chart-elm-tooltip-line {
    font-size: var(--fs-xs);
    line-height: var(--lh-ui);
  }
}

.chart-elm-tooltip {
  position: absolute;
  z-index: 20;
  pointer-events: none;
  /* No fixed cap: with white-space nowrap a hard max-width makes long lines
     (e.g. the pyramid's per-background rows) overflow the box instead of
     widening it. The viewport bound is just a safety net. */
  max-width: calc(100vw - 16px);
  padding: 10px 13px;
  border-radius: var(--radius-sm);
  background: rgba(10, 14, 22, 0.97);
  border: 1px solid rgba(150, 214, 255, 0.1);
  box-shadow: var(--shadow-md);
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  white-space: nowrap;
}
.chart-elm-tooltip-title {
  color: var(--text);
  font-weight: 700;
  font-size: var(--fs-sm);
  margin-bottom: 6px;
  padding-bottom: 6px;
  border-bottom: 1px solid rgba(150, 214, 255, 0.1);
}
.chart-elm-tooltip-line { color: var(--muted); line-height: var(--lh-prose); }

#subsidyBarWrap .main-svg-chart {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

#migriWrap .chart-main-area {
  gap: 6px;
}

#migriCountryList {
  flex: 0 0 340px;
  min-height: 340px;
  max-height: 340px;
}

#netMigrCountryList {
  flex: 0 0 240px;
  min-height: 240px;
  max-height: 240px;
}

#netMigrLegend {
  flex: 0 0 auto;
  flex-wrap: nowrap;
  overflow-x: auto;
  overflow-y: hidden;
  min-height: 18px;
  scrollbar-width: thin;
}

#netMigrLegend .chart-legend-item {
  flex: 0 0 auto;
}

#netMigrWrap .main-svg-chart {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

#migriWrap .main-svg-chart {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.lang-speakers-chart-main {
  position: relative;
  flex: 1;
  min-height: 0;
  overflow: hidden;
}

#originWelfareWrap .chart-main {
  overflow: hidden;
  flex: 1;
  min-height: 0;
}

#langSpeakersWrap .main-svg-chart {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}
/* Every chart shell floors at zero. The shells sit in flex columns that give
   them their height, and a min-height above zero stops them shrinking, which
   is what broke the mobile layout when per-chart heights were tried. Six such
   attempts sat above this rule and never took effect, because this one is
   later and wins. Set the height on the chart body if a chart needs one. */
#popChartWrap,
#natBarChartWrap,
#unempBarWrap,
#subsidyBarWrap,
#econContribWrap,
#netMigrWrap,
#migriWrap,
#langSpeakersWrap,
#originWelfareWrap {
  min-height: 0;
}

#subsidyBarWrap .subsidy-bar-chart-main,
#migriWrap .nat-bar-chart-main {
  min-height: 0;
}
/* ── Nationality bar chart year range slider ── */
.nat-year-slider-section {
  flex: 0 0 auto;
  padding: 8px 4px 4px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.nat-year-slider-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nat-year-range-label {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.04em;
}

.nat-dual-slider-wrap {
  position: relative;
  height: 36px;
  margin-bottom: 22px; /* room for tick labels below */
  display: flex;
  align-items: center;
}

/* Compact variant: no tick labels below, so no extra bottom margin needed */
.nat-dual-slider-wrap--compact {
  margin-bottom: 0;
}

/* Min/max range labels shown below a compact slider */
.nat-slider-range-labels {
  display: flex;
  justify-content: space-between;
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: -4px; /* tighten gap after compact wrap */
}

/* Compact slider section: tighter top padding */
.nat-year-slider-section--compact {
  padding-top: 4px;
}

.nat-slider-track {
  position: absolute;
  left: 0;
  right: 0;
  height: 4px;
  border-radius: var(--radius-xs);
  background: var(--line);
  z-index: 0;
}

.nat-slider-fill {
  position: absolute;
  height: 100%;
  border-radius: var(--radius-xs);
  background: var(--accent);
  opacity: 0.7;
}

.nat-range-input {
  position: absolute;
  width: 100%;
  height: 4px;
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  pointer-events: none;
  z-index: 2;
  outline: none;
}

.nat-range-input::-webkit-slider-thumb {
  appearance: none;
  -webkit-appearance: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  box-shadow: 0 0 0 1px var(--accent);
  cursor: pointer;
  pointer-events: all;
  transition: transform 0.1s;
}

.nat-range-input::-webkit-slider-thumb:hover {
  transform: scale(1.2);
}

.nat-range-input::-moz-range-thumb {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
  box-shadow: 0 0 0 1px var(--accent);
  cursor: pointer;
  pointer-events: all;
}

/* End handle: white-filled ring — visible on both dark and light backgrounds */
.nat-range-to::-webkit-slider-thumb {
  background: #ffffff;
  border: 3px solid var(--accent);
  box-shadow: 0 0 0 2px var(--accent), 0 2px 6px rgba(0, 0, 0, 0.3);
  width: 22px;
  height: 22px;
}

.nat-range-to::-moz-range-thumb {
  background: #ffffff;
  border: 3px solid var(--accent);
  box-shadow: 0 0 0 2px var(--accent), 0 2px 6px rgba(0, 0, 0, 0.3);
  width: 22px;
  height: 22px;
}

.nat-slider-ticks {
  position: absolute;
  left: 0;
  right: 0;
  top: 100%;
  margin-top: 6px;
  display: flex;
  justify-content: space-between;
  pointer-events: none;
}

.nat-slider-tick {
  font-size: var(--fs-xs);
  color: var(--muted);
  text-align: center;
  flex: 1;
}

.nat-slider-tick.is-active {
  color: var(--accent);
  font-weight: 600;
}

@media (min-width: 1800px), (min-height: 1300px) {
  :root {
    --top-bar-height: 56px;
    --panel-width: min(465px, calc(100vw - var(--safe-left) - var(--safe-right) - 20px));
    --stats-width: clamp(336px, 20vw, 448px);
  }

  .top-bar {
    padding-inline: 0;
  }

  .top-bar-tabs-mount {
    max-width: 100%;
  }

  .top-bar-toggle {
    left: 18px;
    width: 46px;
    height: var(--btn-height);
  }

  .top-bar-title {
    font-size: var(--fs-md);
    letter-spacing: 0.2em;
  }

  .top-bar-pill {
    right: 18px;
    padding: 8px 14px;
    font-size: var(--fs-sm);
  }

  .utility-panel {
    --panel-pad-x: 16px;
    --panel-pad-top: 16px;
    --panel-pad-bottom: 16px;
    --panel-gap: 18px;
  }

  .info-card {
    width: clamp(336px, 20vw, 432px);
  }

  .stats-card {
    padding: 16px 18px;
    gap: 12px;
  }

  .menu-section-title {
    font-size: var(--fs-2xl);
  }


  .panel-hint,
  .layer-meta,
  .panel-footnote .note {
    font-size: var(--fs-sm);
  }
  .layer-name,
.stats-ranking-name,
.stats-ranking-value {
    font-size: var(--fs-md);
  }

  .layer-button,
  .mobile-map-mode-button {
    height: var(--btn-height) !important;
    padding-inline: 14px;
    font-size: var(--fs-xs);
  }

  .stats-primary-value {
    font-size: clamp(2.375rem, 3vw, 3.375rem);
  }

  .info-card .stats-primary-value {
    font-size: clamp(1.75rem, 2vw, 2.375rem);
  }
}

/* ── European Statistics Stage ── */
.europe-stage {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
  padding: 0;
  display: none;
  overflow: hidden;
}

/* Full-bleed map with floating overlay panels, mirroring the Finnish map view. */
.europe-shell {
  position: relative;
  width: 100%;
  height: 100%;
  min-height: 0;
}

.europe-control-panel {
  position: absolute;
  top: 14px;
  left: 14px;
  z-index: 600;
  width: min(330px, calc(100% - 28px));
  max-height: calc(100% - 28px);
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 16px;
  overflow-y: auto;
  overflow-x: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(8, 12, 18, 0.9);
  backdrop-filter: blur(14px);
  box-shadow: var(--shadow);
  scrollbar-width: thin;
}

.europe-heading {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.europe-kicker,
.europe-info-kicker {
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.12em;
}

.europe-heading h1,
.europe-info-panel h2 {
  margin: 0;
  color: var(--text);
  line-height: 1.05;
}

.europe-heading h1 {
  font-size: var(--fs-lg);
}

.europe-heading p {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-prose);
}

.europe-control-group {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.europe-dataset-tabs {
  display: grid;
  grid-template-columns: 1fr;
  gap: 7px;
}

.europe-dataset-tab {
  min-height: 34px;
  padding: 8px 10px;
  border: 1px solid var(--control-border);
  border-radius: var(--radius-md);
  background: var(--control-bg);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 800;
  letter-spacing: 0.04em;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease;
}

.europe-dataset-tab:hover,
.europe-dataset-tab:focus-visible {
  background: var(--control-hover-bg);
  border-color: var(--control-hover-border);
  color: var(--text);
  outline: none;
}

.europe-dataset-tab.is-active {
  background: rgba(112, 195, 226, 0.11);
  border-color: rgba(112, 195, 226, 0.26);
  color: var(--accent);
  box-shadow: var(--control-shadow-ring);
}

.europe-metric-dropdown {
  width: 100%;
}

#europeMetricDropdownMenu {
  overscroll-behavior: contain;
}

.europe-metric-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.europe-metric-meta span {
  display: inline-flex;
  align-items: center;
  min-height: 26px;
  padding: 0 10px;
  border: 1px solid rgba(112, 195, 226, 0.1);
  border-radius: var(--radius-pill);
  background: rgba(112, 195, 226, 0.055);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.04em;
}

/* Per-metric methodology / comparability note shown below the meta chips. */
.europe-metric-caveat {
  flex-basis: 100%;
  margin: 2px 0 0;
  padding: 0;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 500;
  line-height: var(--lh-ui);
  letter-spacing: 0;
}

/* The frame (region / country) toggle reuses the dataset-tab styling but reads
   as a compact two-up segmented control. */
.europe-mode-tabs {
  grid-template-columns: 1fr 1fr;
}
.europe-mode-tabs .europe-dataset-tab {
  text-align: center;
}

.europe-legend {
  position: absolute;
  right: 16px;
  bottom: 16px;
  z-index: 500;
  width: min(260px, calc(100% - 32px));
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding: 12px 14px;
  border: 1px solid rgba(112, 195, 226, 0.1);
  border-radius: var(--radius-md);
  background: rgba(8, 12, 18, 0.88);
  backdrop-filter: blur(10px);
  box-shadow: var(--shadow);
}

.europe-legend-bar {
  display: flex;
  overflow: hidden;
  height: 12px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(255, 255, 255, 0.08);
  background: linear-gradient(90deg, #2b74c8, #33a6d5, #f0d66a, #f06b45, #c51f2f);
}

.europe-legend-bar span {
  flex: 1 1 0;
}

.europe-legend-labels {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
}

.europe-sources {
  margin-top: auto;
  max-height: min(180px, 32dvh);
  overflow: auto;
}

.europe-sources-body {
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-prose);
}

.europe-sources-body p {
  margin: 0 0 8px;
}

.europe-sources-body ul {
  margin: 0;
  padding-left: 16px;
}

.europe-map-card {
  position: absolute;
  inset: 0;
  min-width: 0;
  min-height: 0;
  overflow: hidden;
  border: none;
  border-radius: 0;
  background:
    radial-gradient(circle at 18% 20%, rgba(39, 130, 176, 0.16), transparent 28%),
    radial-gradient(circle at 72% 24%, rgba(240, 214, 106, 0.08), transparent 26%),
    radial-gradient(circle at 58% 84%, rgba(197, 31, 47, 0.1), transparent 30%),
    linear-gradient(180deg, rgba(8, 12, 18, 0.96), rgba(5, 8, 12, 0.96));
}

.europe-map-card::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background-image:
    linear-gradient(rgba(112, 195, 226, 0.035) 1px, transparent 1px),
    linear-gradient(90deg, rgba(112, 195, 226, 0.035) 1px, transparent 1px);
  background-size: 38px 38px;
  opacity: 0.5;
  z-index: 400;
}

.europe-map {
  width: 100%;
  height: 100%;
  min-height: 420px;
  background: transparent;
}

.europe-map .leaflet-container,
.europe-map.leaflet-container {
  background: transparent;
}

.europe-map .leaflet-interactive {
  cursor: pointer;
}

.europe-map .leaflet-control-zoom {
  border: 1px solid rgba(112, 195, 226, 0.12);
  border-radius: var(--radius-md);
  overflow: hidden;
  box-shadow: var(--shadow);
}

.europe-map .leaflet-control-zoom a {
  background: rgba(8, 12, 18, 0.9);
  color: var(--text);
  border-bottom-color: rgba(112, 195, 226, 0.1);
}

.europe-info-panel {
  position: absolute;
  right: 14px;
  top: 14px;
  z-index: 540;
  width: min(330px, calc(100% - 28px));
  max-height: calc(100% - 28px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 9px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: rgba(8, 12, 18, 0.92);
  backdrop-filter: blur(14px);
  box-shadow: var(--shadow);
}

.europe-info-panel h2 {
  font-size: clamp(1.125rem, 1.2vw, 1.5rem);
}

.europe-info-value {
  color: var(--accent);
  font-size: clamp(1.5rem, 2vw, 2.25rem);
  font-weight: 900;
  letter-spacing: -0.03em;
  line-height: 1;
}

.europe-info-meta {
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-prose);
}

.europe-info-details {
  display: grid;
  gap: 6px;
  padding-top: 5px;
}

.europe-detail-row {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding-top: 6px;
  border-top: 1px solid rgba(112, 195, 226, 0.08);
  color: var(--muted);
  font-size: var(--fs-xs);
}

.europe-detail-row strong {
  color: var(--text);
  text-align: right;
}

/* ── Language Toggle ── */
.lang-dropdown {
  position: relative;
  right: auto;
  top: auto;
  transform: none;
  z-index: 100;
  flex: 0 0 auto;
  /* Reset the generic <details> card box so only the pill trigger shows. */
  border: none;
  background: transparent;
  border-radius: 0;
}

.lang-dropdown-trigger {
  display: flex;
  align-items: center;
  gap: 6px;
  min-height: 32px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--text);
  cursor: pointer;
  font-size: var(--fs-xs);
  font-weight: 600;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
  list-style: none;
}

.lang-dropdown-trigger::-webkit-details-marker {
  display: none;
}

.lang-dropdown-trigger:hover,
.lang-dropdown.is-open .lang-dropdown-trigger,
.lang-dropdown[open] .lang-dropdown-trigger {
  background: var(--control-bg-hover);
  border-color: var(--control-border-strong);
  color: var(--accent);
  box-shadow: 0 0 0 1px rgba(112, 195, 226, 0.06);
}

.lang-dropdown-chevron {
  width: 10px;
  height: 6px;
  transition: transform 0.2s ease;
  flex-shrink: 0;
}

.lang-dropdown.is-open .lang-dropdown-chevron,
.lang-dropdown[open] .lang-dropdown-chevron {
  transform: rotate(180deg);
}

.lang-dropdown-menu {
  display: none;
  position: fixed;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--surface-overlay);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
  box-shadow: var(--menu-surface-shadow);
  backdrop-filter: blur(10px);
  z-index: 9999;
  min-width: 100px;
}

.lang-dropdown-menu.is-open {
  display: block;
}

.lang-dropdown[open] > .lang-dropdown-menu:not(.is-open) {
  display: block;
}

.lang-dropdown > .lang-dropdown-menu {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
}

.lang-dropdown-menu li {
  padding: 0;
  list-style: none;
}

.lang-dropdown-link {
  display: none;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  color: var(--muted);
  text-decoration: none;
  font-size: var(--fs-sm);
  font-weight: 500;
  letter-spacing: 0.02em;
  white-space: nowrap;
  transition: background 0.15s, color 0.15s;
  cursor: pointer;
  box-sizing: border-box;
}

.lang-dropdown-link:hover {
  background: rgba(112, 195, 226, 0.1);
  color: var(--text);
}

.lang-dropdown-menu li[aria-selected="true"] .lang-dropdown-link {
  color: var(--accent, #70c3e2);
  background: rgba(112, 195, 226, 0.12);
}

body[data-view="home"] .lang-dropdown-link--home,
html:not(.js) body:has(#homeStage:target) .lang-dropdown-link--home,
body[data-view="map"] .lang-dropdown-link--map,
html:not(.js) body:has(#mapStage:target) .lang-dropdown-link--map,
body[data-view="chart"] .lang-dropdown-link--chart,
html:not(.js) body:has(#chartStage:target) .lang-dropdown-link--chart,
body[data-view="cartogram"] .lang-dropdown-link--cartogram,
html:not(.js) body:has(#cartogramStage:target) .lang-dropdown-link--cartogram,
body[data-view="europe"] .lang-dropdown-link--europe,
html:not(.js) body:has(#europeStage:target) .lang-dropdown-link--europe,
body[data-view="knowledge"] .lang-dropdown-link--knowledge,
html:not(.js) body:has(#knowledgeStage:target) .lang-dropdown-link--knowledge,
/* History was in TopBar.elm's route list but not here, so its menu opened
   empty and the view could not be switched out of. */
body[data-view="history"] .lang-dropdown-link--history,
html:not(.js) body:has(#historyStage:target) .lang-dropdown-link--history,
body[data-view="tools"] .lang-dropdown-link--tools,
html:not(.js) body:has(#toolsStage:target) .lang-dropdown-link--tools,
body[data-view="info"] .lang-dropdown-link--info,
html:not(.js) body:has(#infoStage:target) .lang-dropdown-link--info,
body[data-view="area"] .lang-dropdown-link--area,
html:not(.js) body:has(#areaStage:target) .lang-dropdown-link--area,
body[data-view="origin"] .lang-dropdown-link--origin,
html:not(.js) body:has(#originStage:target) .lang-dropdown-link--origin {
  display: flex;
}

.lang-dropdown-menu .lang-code {
  display: inline-block;
  width: 22px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  opacity: 0.6;
}

/* ── Light Theme Toggle & Overrides ── */
.theme-toggle-btn {
  position: static;
  transform: none;
  flex: 0 0 auto;
  width: 34px;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-pill);
  border: 1px solid var(--control-border);
  background: var(--control-bg);
  color: var(--muted);
  cursor: pointer;
  transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease;
  z-index: 100;
}

.theme-toggle-btn[aria-pressed="true"] {
  color: var(--accent);
  border-color: var(--control-border-strong);
  background: var(--control-bg-hover);
  box-shadow: 0 0 0 1px rgba(112, 195, 226, 0.06);
}

.theme-toggle-btn:hover {
  background: var(--control-bg-hover);
  border-color: var(--control-border-strong);
  color: var(--text);
  box-shadow: 0 0 0 1px rgba(112, 195, 226, 0.06);
}

.theme-toggle-btn svg {
  width: 16px;
  height: 16px;
}

/* Two pixels more than the other top-bar icons, and after .theme-toggle-btn svg
   in the file so it wins on equal specificity. The temples are the part that
   says "glasses", and at 16 px they collapse into the lenses. */
.advanced-toggle-btn svg {
  width: 18px;
  height: 18px;
}

body.theme-switching,
body.theme-switching *,
body.theme-switching *::before,
body.theme-switching *::after {
  transition: none !important;
}

body.light-theme {

  /* The same three steps, lighter: on a light ground a 0.45 alpha shadow reads
     as dirt rather than height. */
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
  --shadow-md: 0 8px 24px rgba(0, 0, 0, 0.12);
  --shadow-lg: 0 18px 40px rgba(0, 0, 0, 0.16);
  --shadow-up: 0 -10px 32px rgba(0, 0, 0, 0.12);
  --bg: #f4f7f9;
  --panel: rgba(255, 255, 255, 0.95);
  --panel-strong: rgba(255, 255, 255, 0.98);
  --panel-soft: rgba(0, 0, 0, 0.05);
  --text: #111827;
  --muted: #4b5563;
  --muted-soft: #5b6471;
  --border: rgba(0, 0, 0, 0.15);
  --line: rgba(0, 0, 0, 0.18);
  --accent: #0369a1;
  --accent-rgb: 3, 105, 161;
  --accent-hot: #b91c1c;
  --accent-warm: #a04a06;
  --accent-dim: #075985;
  --card-bg: #ffffff;
  --shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
  background: transparent;
  background-image: none;
}

html:has(body.light-theme) {
  background: #f4f7f9;
}

body.light-theme::before,
body.light-theme::after {
  display: none;
}

body.light-theme .app-shell::before {
  opacity: 0.1;
  filter: invert(1);
}

body.light-theme {
  --control-border: rgba(82, 99, 115, 0.14);
  --control-border-strong: rgba(82, 99, 115, 0.22);
  --control-bg: #ffffff;
  --control-bg-hover: rgba(82, 99, 115, 0.08);
  --control-hover-border: rgba(82, 99, 115, 0.18);
  --control-hover-bg: rgba(82, 99, 115, 0.08);
  --control-shadow-ring: 0 0 0 1px rgba(82, 99, 115, 0.06);
  --topbar-surface: rgba(255, 255, 255, 0.78);
  --topbar-surface-border: rgba(82, 99, 115, 0.12);
  --panel-card-bg: rgba(245, 247, 250, 0.95);
  --panel-card-border: rgba(0, 0, 0, 0.1);
  --card-inset-border: rgba(255, 255, 255, 0.66);
  --surface-raised: rgba(245, 247, 250, 0.96);
  --surface-panel: #ffffff;
  --surface-bar: #ffffff;
  --surface-overlay: #ffffff;
  --kb-migmap-bg-radial: rgba(2, 132, 199, 0.07);
  --kb-migmap-bg-1: rgba(255, 255, 255, 0.98);
  --kb-migmap-bg-2: rgba(238, 244, 249, 0.98);
  --kb-migmap-border: rgba(0, 0, 0, 0.12);
  --kb-migmap-dest-fill: rgba(2, 132, 199, 0.10);
  --kb-migmap-dest-stroke: rgba(2, 132, 199, 0.4);
  --kb-migmap-path-stroke: rgba(71, 85, 105, 0.3);
  --kb-migmap-label: rgba(180, 83, 9, 0.85);
  --kb-migmap-dot-hot-settled: rgba(194, 65, 12, 0.6);
  --kb-migmap-dot-hot-flight: #dd5316;
  --kb-migmap-dot-settled: rgba(37, 99, 235, 0.35);
  --kb-migmap-dot-flight: #3b82f6;
  --migmap-out: rgba(51, 65, 85, 0.45);
  --migmap-unknown: rgba(100, 116, 139, 0.8);
  --migmap-land: rgba(203, 216, 226, 0.75);
  --migmap-land-stroke: rgba(100, 130, 155, 0.42);
  /* The light ramp is its own scale, not the dark one lightened. On white a
     reader orders the classes by depth, and the old ramp did not: its
     luminance ran 0.67, 0.40, 0.17, then back up to 0.37 for the 10-20 band
     and down again, so the middle class was darker than the two above it and
     the map read as a dip. This one falls at every step -- 0.74, 0.52, 0.30,
     0.26, 0.13, 0.05 -- with the hue turning from blue to gold at 10 %, which
     is where the scale's own break is. Every label clears 4.5 against its
     fill: ink up to the gold band, white on the two reds. */
  /* Same lifted ramp as the map's light palette in app_state_scale.js. */
  --kb-pew-fill-30: #a54452;
  --kb-pew-fill-20: #c66a55;
  --kb-pew-fill-10: #dcb257;
  --kb-pew-fill-7: #9fc9e6;
  --kb-pew-fill-3: #c7e1f4;
  --kb-pew-fill-0: #eaf4fb;
  --kb-pew-text-30: #ffffff;
  --kb-pew-text-20: #ffffff;
  --kb-pew-text-10: #0a2230;
  --kb-pew-text-7: #0a2230;
  --kb-pew-text-3: #0a2230;
  --kb-pew-text-0: #0a2230;
  /* Almost the page, so a country with no figure reads as absent rather than
     as a seventh class. The dark theme does the same in reverse. */
  --kb-pew-no-data: #eef1f4;
  /* A hairline, not a border: the old solid slate was heavier than the palest
     fills it separated. */
  --kb-pew-stroke: rgba(15, 23, 42, 0.32);
  --kb-map-tip-bg: #ffffff;
  --kb-map-tip-border: rgba(15, 23, 42, 0.22);
  --kb-map-tip-ink: #0f172a;
  --kb-consang-zero: #ffffff;
  /* The same palette mixed for paper. Each clears 3:1 against the page, which
     is what a line or a swatch needs, and they sit in one band (3.2 to 3.6) so
     no series reads as heavier than its neighbours. The blue is its own value
     rather than --accent, which has to stay dark enough to carry link text. */
  --series-1: #2b86b8;
  --series-2: #b4771f;
  --series-3: #3d9a50;
  --series-4: #e0526a;
  --series-5: #8272e4;
  --series-6: #158f83;
  --series-7: #a8871f;
  --series-8: #cf6a1e;
  --series-9: #4692ba;
  --series-10: #bd6a8c;
  --tint-azure: #2c8dc4;
  --tint-sky: #008fb8;
  --tint-coral: #e04a12;
  --tint-sand: #b37919;
  --tint-cobalt: #1a79d6;

  /* These are lines and dots, not text, so the bar they have to clear is 3:1
     against the page and not 4.5:1. They had been darkened to the text bar,
     which put them at a luminance of 0.11 to 0.16 against a 0.93 page: on
     paper that reads as near-black with a hue rather than as the colour. Each
     is lifted one step and no further, and each still clears 3:1 -- 3.35,
     3.49, 3.56, 3.50 and 3.29 in the order below. */
  --series-child: #bf761d;
  --area-line-1: #b4771f;
  --area-line-2: #8272e4;
  --area-line-4: #e0526a;
  --area-line-5: #3d9a50;
  --panel-border: rgba(15, 23, 42, 0.16);
  --panel-rule: rgba(15, 23, 42, 0.09);
  --panel-bg: #ffffff;
}

body.light-theme .chart-toggle-btn {
  background: var(--control-bg);
  border-color: var(--border);
  color: var(--muted);
}

body.light-theme .chart-toggle-btn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}

/* Status colours written for the dark theme: a warm orange and a green that
   both read at 2-3:1 once the surface behind them turns white. Their
   light-theme steps keep the same meaning and clear AA on both the page and
   their own tinted chip. */
body.light-theme .site-info-refresh--derived {
  color: #a04a06;
  border-color: color-mix(in srgb, #a04a06 42%, transparent);
}

body.light-theme .site-info-tag--fixed {
  color: #166534;
  border-color: rgba(22, 101, 52, 0.42);
  background: rgba(22, 101, 52, 0.10);
}

/* Filled accent buttons carry dark ink on the dark theme, where the accent is a
   pale cyan. On the light theme the accent is a deep blue and the ink has to
   flip, or the label sits at 2.8:1 on its own button. */
body.light-theme .area-action {
  color: #ffffff;
}

/* The pressed faith chip kept the dark theme's pale gold, which on a light chip
   is 1.2:1 — the label was effectively invisible. */
body.light-theme .faith-toggle-chip[aria-pressed="true"] {
  border-color: rgba(146, 106, 20, 0.65);
  background: rgba(180, 132, 24, 0.14);
  color: #7a5504;
}

body.light-theme select,
body.light-theme input,
body.light-theme button {
  background: #ffffff;
  color: var(--text);
  border-color: var(--border);
}

/* The rule above paints every input white, which on a range input is a box
   around the track rather than a field. It used to be given #f0f0f0 instead,
   which is the grey box that showed under every slider on a light background.
   The element itself carries nothing: what is drawn is the track and the
   thumb. */
body.light-theme input[type="range"] {
  background: transparent;
}

/* The default track is a wash of the dark theme's cyan, which on white is
   almost invisible. The year slider already had a light-theme track: the rest
   of them get the same one. Two rules, not one selector list -- a browser that
   does not know one of these pseudo-elements drops the whole list. */
body.light-theme input[type="range"]::-webkit-slider-runnable-track {
  background: rgba(82, 99, 115, 0.2);
}

body.light-theme input[type="range"]::-moz-range-track {
  background: rgba(82, 99, 115, 0.2);
}

/* The dark theme's cyan tick disappears on the light track. */
body.light-theme .tool-slider-mark {
  background: rgba(82, 99, 115, 0.55);
}

body.light-theme .year-range {
  --year-track: rgba(82, 99, 115, 0.2);
}

body.light-theme .year-range::-webkit-slider-runnable-track {
  background: var(--year-track);
}

body.light-theme .year-range::-moz-range-track {
  background: var(--year-track);
}

body.light-theme .year-range::-webkit-slider-thumb {
  background: #ffffff;
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(2, 132, 199, 0.2), 0 2px 6px rgba(0, 0, 0, 0.15);
}

body.light-theme .year-range:hover::-webkit-slider-thumb {
  box-shadow: 0 0 0 4px rgba(2, 132, 199, 0.12), 0 2px 6px rgba(0, 0, 0, 0.15);
}

body.light-theme .year-range::-moz-range-thumb {
  background: #ffffff;
  border-color: var(--accent);
  box-shadow: 0 0 0 1px rgba(2, 132, 199, 0.2), 0 2px 6px rgba(0, 0, 0, 0.15);
}

body.light-theme option {
  background: #ffffff;
  color: var(--text);
}

body.light-theme .theme-toggle-btn {
  background: #ffffff;
  border-color: rgba(82, 99, 115, 0.14);
}
body.light-theme .theme-toggle-btn:hover {
  background: rgba(82, 99, 115, 0.08);
  color: var(--text);
  box-shadow: 0 0 0 1px rgba(82, 99, 115, 0.06);
}

body.light-theme .top-bar-tabs {
  background: var(--topbar-surface);
  border-color: var(--topbar-surface-border);
}

/* Stay below the .top-bar-tab hover (0.08) and active (0.1) washes so the
   sliding indicator still reads against the trough. */
body.light-theme .top-bar-tab-pair {
  background: rgba(82, 99, 115, 0.06);
  border-color: rgba(82, 99, 115, 0.14);
}

body.light-theme .top-bar-tab:hover {
  background: transparent;
}

body.light-theme .top-bar-tab.is-active,
body.light-theme[data-view="home"] #tabHome,
body.light-theme[data-view="map"] #tabMap,
body.light-theme[data-view="chart"] #tabChart,
body.light-theme[data-view="europe"] #tabEurope,
body.light-theme[data-view="knowledge"] #tabKnowledge {
  background: transparent;
  color: var(--accent);
  box-shadow: none;
}

body.light-theme[data-view="cartogram"] #tabCartogram,
body.light-theme body:has(#cartogramStage:target) #tabCartogram,
body.light-theme body:has(#mapStage:target) #tabMap {
  background: rgba(82, 99, 115, 0.1);
  color: var(--accent);
  box-shadow: none;
}

body.light-theme .boundary-hover-popup {
  box-shadow: var(--shadow-md);
}

body.light-theme .stats-primary-value {
  color: #1a2430;
}

body.light-theme .stats-card {
  background: rgba(245, 247, 250, 0.95) !important;
  border-color: rgba(0, 0, 0, 0.1) !important;
}

body.light-theme #statsPanel .stats-card,
body.light-theme #statsPanel .stats-card-primary {
  background: rgba(245, 247, 250, 0.98) !important;
}

body.light-theme .custom-dropdown-menu,
body.light-theme .school-switcher-menu,
body.light-theme .mun-search-results,
body.light-theme .lang-dropdown-menu {
  box-shadow: var(--shadow-md);
}

body.light-theme .lang-dropdown-menu li:hover {
  background: rgba(82, 99, 115, 0.08);
  color: var(--text);
}

body.light-theme .lang-dropdown-menu li[aria-selected="true"] {
  color: var(--accent);
  background: rgba(82, 99, 115, 0.1);
}

body.light-theme .custom-dropdown-trigger,
body.light-theme .school-switcher-select,
body.light-theme .school-switcher-single,
body.light-theme .chart-mun-search,
body.light-theme .mun-search-input {
  background: #ffffff;
}

body.light-theme .lang-dropdown-trigger,
body.light-theme .theme-toggle-btn,
body.light-theme .mobile-nav-trigger,
body.light-theme .top-bar-toggle,
body.light-theme .map-share-btn,
body.light-theme .chart-share-btn {
  background: #ffffff;
  border-color: rgba(82, 99, 115, 0.14);
}

/* Same veil as the dark theme's, restated because the rule above is a class
   deeper and would otherwise repaint the bar's controls solid white. */
body.light-theme .top-bar-layout .top-bar-toggle,
body.light-theme .top-bar-layout .map-share-btn,
body.light-theme .top-bar-layout .lang-dropdown-trigger,
body.light-theme .top-bar-layout .theme-toggle-btn,
body.light-theme .top-bar-layout .mobile-nav-trigger {
  background: var(--topbar-surface);
  border-color: var(--topbar-surface-border);
}

body.light-theme .custom-dropdown-trigger:hover,
body.light-theme .custom-dropdown.is-open .custom-dropdown-trigger {
  background: rgba(82, 99, 115, 0.08);
  border-color: rgba(82, 99, 115, 0.18);
  box-shadow: 0 0 0 1px rgba(82, 99, 115, 0.06);
}

body.light-theme .school-switcher-select:hover,
body.light-theme .lang-dropdown-trigger:hover,
body.light-theme .lang-dropdown.is-open .lang-dropdown-trigger,
body.light-theme .mobile-nav-trigger:hover,
body.light-theme .mobile-nav-trigger.is-open,
body.light-theme .mobile-nav-dropdown[open] .mobile-nav-trigger,
body.light-theme .top-bar-toggle:hover,
body.light-theme .map-share-btn:hover,
body.light-theme .chart-share-btn:hover,
body.light-theme .chart-mun-search:focus,
body.light-theme .mun-search-input:focus {
  background: rgba(82, 99, 115, 0.08);
  border-color: rgba(82, 99, 115, 0.18);
  box-shadow: 0 0 0 1px rgba(82, 99, 115, 0.06);
}

body.light-theme .utility-panel-search-card {
  box-shadow: none;
}

body.light-theme .atlas-marker::before {
  box-shadow:
    0 0 0 1px rgba(0, 0, 0, 0.12),
    0 3px 8px rgba(0, 0, 0, 0.12);
}

body.light-theme .atlas-marker::after {
  background: rgba(0, 0, 0, 0.1);
}

body.light-theme .atlas-marker-svg {
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.2));
}

body.light-theme .leaflet-control-attribution {
  background: rgba(255, 255, 255, 0.92) !important;
  color: #526373 !important;
  border-color: rgba(82, 99, 115, 0.18);
}

body.light-theme .leaflet-control-attribution a {
  color: var(--accent);
}

body.light-theme .leaflet-control-zoom {
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12) !important;
}

body.light-theme .stats-ranking-item {
  background: rgba(82, 99, 115, 0.06);
  border-color: rgba(82, 99, 115, 0.12);
}

body.light-theme .stats-ranking-item:hover {
  background: rgba(82, 99, 115, 0.12);
  border-color: rgba(82, 99, 115, 0.22);
}

body.light-theme .stats-ranking-item.is-selected {
  background: linear-gradient(135deg, rgba(0, 120, 180, 0.1), transparent 72%),
    rgba(82, 99, 115, 0.08);
  border-color: rgba(0, 120, 180, 0.28);
}

body.light-theme .stats-ranking-item.is-selected .stats-ranking-name,
body.light-theme .stats-ranking-item.is-selected .stats-ranking-value {
  color: var(--text);
}

body.light-theme .stats-ranking-value {
  color: var(--text);
}

body.light-theme .leaflet-control-zoom a {
  background: #ffffff !important;
  color: var(--accent) !important;
  border-color: rgba(82, 99, 115, 0.18) !important;
}

body.light-theme .leaflet-control-zoom a:hover {
  background: rgba(82, 99, 115, 0.08) !important;
  border-color: rgba(82, 99, 115, 0.28) !important;
  color: var(--text) !important;
}

body.light-theme .legend {
  color: var(--text);
  box-shadow: var(--shadow-sm);
}

body.light-theme .legend-scale-item {
  color: var(--muted);
}

body.light-theme .legend-swatch {
  border-color: rgba(0, 0, 0, 0.18);
}

body.light-theme .mobile-map-mode-button {
  background: #ffffff;
  box-shadow: var(--shadow-sm);
}

body.light-theme .mobile-map-mode-button.is-active {
  background: #f0f9ff;
  border-color: var(--accent);
}

body.light-theme .kb-entry,
body.light-theme .kb-article {
  background: rgba(255, 255, 255, 0.95);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.07), 0 0 0 1px rgba(0, 0, 0, 0.06);
}

body.light-theme .chart-elm-tooltip {
  background: #ffffff;
  border-color: rgba(0, 0, 0, 0.09);
  box-shadow: var(--shadow-md);
}

body.light-theme .chart-elm-tooltip-title {
  border-bottom-color: rgba(0, 0, 0, 0.08);
}

body.light-theme .claim-actions {
  border-top-color: rgba(0, 0, 0, 0.07);
}

body.light-theme .claim-badge {
  background: rgba(2, 132, 199, 0.07);
  color: #0369a1;
  border-color: rgba(2, 132, 199, 0.18);
}

body.light-theme .claim-badge--fact {
  background: rgba(22, 163, 74, 0.07);
  color: #166534;
  border-color: rgba(22, 163, 74, 0.18);
}

body.light-theme #map {
  background: transparent;
}

body.light-theme .map-basemap-fallback {
  background:
    radial-gradient(circle at 24% 18%, rgba(82, 126, 135, 0.13), transparent 34%),
    radial-gradient(circle at 72% 58%, rgba(68, 110, 122, 0.12), transparent 42%),
    linear-gradient(180deg, #edf4f1 0%, #e5efec 100%);
}

body.light-theme .map-basemap-fallback__grid {
  background:
    linear-gradient(100deg, transparent 0%, rgba(43, 78, 86, 0.034) 46%, transparent 64%),
    radial-gradient(circle at 58% 35%, rgba(43, 78, 86, 0.032), transparent 32%);
}

body.light-theme .cartogram-stage,
body.light-theme .cartogram-basemap {
  background:
    radial-gradient(circle at 48% 38%, rgba(76, 119, 134, 0.13), transparent 36%),
    linear-gradient(180deg, #edf4f1 0%, #e5efec 100%);
}

body.light-theme .cartogram-basemap::before {
  background:
    linear-gradient(90deg, rgba(49, 86, 96, 0.04), transparent 26%, transparent 74%, rgba(255, 91, 123, 0.018)),
    radial-gradient(circle at 50% 50%, transparent 0 28%, rgba(222, 235, 232, 0.28) 78%);
}

body.light-theme .cartogram-basemap::after {
  background:
    linear-gradient(100deg, transparent 0%, rgba(43, 78, 86, 0.030) 48%, transparent 66%),
    linear-gradient(180deg, transparent 0%, rgba(43, 78, 86, 0.06) 100%);
}

body.light-theme .cartogram-map-grid--minor {
  stroke: rgba(43, 78, 86, 0.038);
}

body.light-theme .cartogram-map-grid--major {
  stroke: rgba(43, 78, 86, 0.072);
}

body.light-theme .cartogram-map-boundaries {
  stroke: rgba(43, 78, 86, 0.13);
}

body.light-theme .cartogram-backdrop-node {
  stroke: rgba(43, 78, 86, 0.078);
}

body.light-theme .cartogram-backdrop-shadow-node {
  fill: rgba(49, 86, 96, 0.04);
}

body.light-theme .cartogram-backdrop-tone-node {
  opacity: 0.035;
}

body.light-theme .map-quick-city-btn {
  background: #ffffff;
  border-color: rgba(82, 99, 115, 0.14);
}

body.light-theme .map-quick-city-btn:hover,
body.light-theme .map-quick-city-btn:focus-visible {
  background: rgba(82, 99, 115, 0.08);
  border-color: rgba(82, 99, 115, 0.18);
  box-shadow: 0 0 0 1px rgba(82, 99, 115, 0.06);
  color: #2f6a8f;
}

body.light-theme .share-toast {
  border-color: var(--border);
}

body.light-theme .chart-share-btn:hover {
  background: rgba(0, 0, 0, 0.05);
}

body.light-theme .europe-map-card {
  background:
    radial-gradient(circle at 18% 20%, rgba(73, 132, 177, 0.16), transparent 28%),
    radial-gradient(circle at 72% 24%, rgba(203, 174, 103, 0.12), transparent 26%),
    radial-gradient(circle at 58% 84%, rgba(177, 76, 73, 0.08), transparent 30%),
    linear-gradient(180deg, #eef3f8 0%, #e6edf5 100%);
}

body.light-theme .europe-info-panel {
  background: rgba(255, 255, 255, 0.88);
}

body.light-theme .europe-legend,
body.light-theme .europe-metric-meta span,
body.light-theme .europe-dataset-tab {
  background: rgba(255, 255, 255, 0.62);
}

body.light-theme .europe-dataset-tab.is-active {
  background: rgba(2, 132, 199, 0.1);
  border-color: rgba(2, 132, 199, 0.2);
}

@media (max-width: 1120px) {
  .top-bar-tabs-mount { display: none; }
  .top-bar-search-slot { display: none; }
  .mobile-nav-dropdown {
    display: flex;
    align-items: center;
    max-width: min(44vw, 230px);
  }
  .mobile-nav-trigger {
    width: 100%;
    justify-content: center;
    min-height: 34px;
  }
}

/* ── Mobile chart + nav (≤768px) ── */
@media (max-width: 768px) {
  /* Replace tabs with dropdown */
  .top-bar-tabs-mount { display: none; }
  .top-bar-tabs { display: none; }
  .top-bar-search-slot { display: none; }

  /* Search moved into the view menu; keeping the icon out here as well is what
     lets the two sides of the bar weigh the same. */
  .top-bar-right .site-search-trigger { display: none; }

  /* Equal side columns, each at least as wide as its own buttons, so the view
     pill sits in the middle of the bar rather than wherever the wider side
     leaves it. */
  .top-bar-layout {
    grid-template-columns: minmax(max-content, 1fr) auto minmax(max-content, 1fr);
  }
  .mobile-nav-dropdown {
    display: flex;
    align-items: center;
    max-width: min(48vw, 210px);
  }
  .mobile-nav-trigger {
    width: 100%;
    justify-content: center;
    min-height: 36px;
  }
  body[data-view="chart"] #chartTocToggleBtn { display: flex; }
  html:not(.js) body:has(#chartStage:target) #chartTocToggleBtn { display: flex; }

  /* Below the top bar, pinned to the map/cartogram canvas's own top-right
     corner — not squeezed into the top bar row next to the language and theme
     buttons, where it kept overlapping them on narrow screens. The wrapper is
     what gets pinned: the menu is positioned against it, so pinning the summary
     instead would open the menu back up in the top bar. */
  .share-dropdown {
    position: fixed;
    top: calc(var(--safe-top) + var(--top-bar-height) + 10px);
    right: calc(var(--safe-right) + 10px);
    /* Above the stats panel (735): the two do not overlap at today's sizes, but
       an open panel must never end up behind the card the reader just opened. */
    z-index: 740;
  }

  .map-share-btn {
    position: static;
    transform: none;
    height: 34px;
    width: 34px;
    min-height: 34px;
    padding: 0;
    justify-content: center;
    border-radius: var(--radius-pill);
    box-shadow: none;
  }
  /* Hidden on the views with nothing to share. The area and origin pages
     joined that list only because serializeState() had no case for them; now
     that their whole state is one hash parameter, the button works there and
     stays. */
  body:not([data-view="map"]):not([data-view="cartogram"]):not([data-view="area"]):not([data-view="origin"]) .map-share-btn,
  body:not([data-view="map"]):not([data-view="cartogram"]):not([data-view="area"]):not([data-view="origin"]) .share-dropdown {
    display: none;
  }
  .menu-open .map-share-btn,
  .menu-open .share-dropdown {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }

  .lang-dropdown { right: calc(var(--safe-right) + 48px); }
  .lang-dropdown-trigger {
    height: 34px;
    padding: 0 7px;
    gap: 3px;
  }
  .theme-toggle-btn {
    right: calc(var(--safe-right) + 8px);
    width: 34px;
    height: 34px;
  }
  .top-bar-toggle { left: calc(var(--safe-left) + 6px); }

  .chart-stage {
    --chart-shell-height: 65dvh;
    --chart-stage-top-gap: 10px;
    /* The fixed top bar sits above .chart-stage's own scrollport, so a plain
       10px gap left a card's title row (and its Valinnat/filters button)
       tucked underneath and unclickable whenever a scrollIntoView (TOC link,
       snap) landed it at the very top. Clear the bar's full height instead. */
    --chart-scroll-margin: calc(var(--safe-top) + var(--top-bar-height) + 10px);
    padding: 0 10px 0;
    gap: 12px;
    flex-direction: column;
    align-items: stretch;
  }
  .chart-stage-col {
    flex-basis: 100%;
    max-width: 100%;
  }
  /* On mobile, TOC is shown as a collapsible block above the content */
  .chart-toc-wrap {
    position: static;
    width: 100%;
    max-height: 0;
    overflow: hidden;
    margin-right: 0;
    margin-top: 0;
    padding-top: 0;
    transform: none;
    opacity: 0;
    transition: max-height var(--chart-toc-transition), opacity var(--chart-toc-transition);
  }
  .chart-stage.is-toc-open .chart-toc-wrap {
    max-height: 600px;
    opacity: 1;
    padding-top: 4px;
    margin-top: var(--chart-stage-top-gap);
  }
  .chart-stage.is-toc-open .chart-stage-col {
    padding-left: 12px;
  }
  .chart-toc-toggle-btn {
    left: 10px;
    top: calc(var(--top-bar-height) + 8px);
  }

  .chart-container-shell + .chart-container-shell {
    border-top: none;
    padding-top: 0;
  }

  .europe-stage {
    padding: 0;
    overflow: hidden;
  }

  .europe-control-panel {
    top: calc(var(--top-bar-height) + 8px);
    left: 8px;
    right: 8px;
    width: auto;
    max-height: 46%;
  }



  .europe-sources {
    max-height: 120px;
  }

  .europe-map {
    min-height: 0;
  }

  .europe-info-panel {
    top: auto;
    bottom: 10px;
    right: 10px;
    left: 10px;
    width: auto;
    max-height: 52%;
    padding: 13px;
  }

  .europe-legend {
    right: 10px;
    bottom: 10px;
  }

  .chart-container-shell {
    flex-direction: column !important;
    height: var(--chart-shell-height);
    min-height: var(--chart-shell-height);
    max-height: var(--chart-shell-height);
    padding: 14px;
    gap: 14px;
    border-radius: var(--radius-lg);
    background: var(--panel-strong);
    backdrop-filter: blur(12px);
    border: 1px solid var(--border);
    box-shadow: var(--shadow);
    overflow: auto;
  }

  .chart-body-row {
    flex-direction: column;
    overflow: visible;
  }

  .welfare-sidebar-col {
    width: 100%;
    gap: 10px;
  }

  .chart-sidebar {
    width: 100%;
    gap: 10px;
    flex: none;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 0;
    overflow: visible;
    padding-right: 0;
  }

  .chart-sidebar-section { gap: 7px; }
  .chart-sidebar-label { letter-spacing: 0.09em; }

  .chart-sidebar-mun {
    flex: none;
    gap: 8px;
  }

  .chart-mun-list,
  .nat-country-list {
    max-height: 150px;
    min-height: 110px;
    padding: 6px;
  }

  .chart-main-area { width: 100%; min-width: 0; gap: 8px; overflow: visible; }

  .pop-key-figures {
    max-height: none;
    overflow: visible;
  }

  .chart-title-row--hoisted {
    order: -1;
    width: 100%;
    padding: 0 0 10px;
    border-bottom: 1px solid var(--border);
    margin-bottom: 2px;
  }

  .chart-title-row {
    align-items: center;
    gap: 10px;
    margin-top: 14px;
  }

  /* A phone's title row is about 200px wide once the three controls beside it
     have taken theirs, and most of these titles are longer than that. Ellipsised
     they read "Ulkomaalaiset tyött..." and the reader cannot tell which chart
     it is, so here the title wraps instead and the card grows with it. */
  .chart-title-row .chart-view-title {
    font-size: var(--fs-md);
    line-height: 1.25;
    white-space: normal;
    overflow: visible;
    text-overflow: clip;
    overflow-wrap: anywhere;
  }

  .chart-main {
    max-height: 280px;
    min-height: unset;
    border-radius: var(--radius-md);
  }
  .chart-main .main-svg-chart { height: auto; }
  #migriWrap, #langSpeakersWrap { min-height: unset; }
  #migriWrap .chart-main-area { min-height: unset; }

  /* flex (not a rigid px height) so this shrinks to whatever the card's fixed
     mobile height actually has left over, instead of forcing the card taller
     than its budget and needing to scroll internally to reach the sliders/
     caption below it. Floor is deliberately low — on the shortest/landscape
     screens the legend+sliders+caption alone can approach the card's whole
     budget, and a taller floor would just force the overflow back. */
  .nat-bar-chart-main,
  .subsidy-bar-chart-main,
  .lang-speakers-chart-main {
    flex: 1 1 0;
    height: auto;
    min-height: 50px;
  }

  /* The same treatment for three that were left on the 280px cap while their
     card had the room: the jobseeker series, the segregation measures and the
     employment sex gap all drew into 240px with 447px of main area around them
     — the employment chart was leaving about 170px of its own card empty. A
     monthly line and a dumbbell need the height more than a bar chart does,
     since theirs is where the whole signal lives. */
  #jobseekerWrap .chart-main,
  #postalConcWrap .chart-main,
  #employmentGapWrap .chart-main,
  #bgCountryWrap .chart-main,
  #agePyramidWrap .chart-main,
  #fertilityWrap .chart-main,
  #originWelfareWrap .chart-main {
    flex: 1 1 0;
    height: auto;
    max-height: none;
    min-height: 150px;
  }

  /* Population structure: the key-figure cards stack under the chart on mobile
     and are tall (~290px), so the card's fixed dvh budget cannot hold them plus
     a legible chart. The chart row is `flex: 1 1 0; min-height: 0`, so it lost
     that fight and shrank below its own legend + plot + source note; the row is
     `overflow: visible` here, so the excess painted on top of the cards. Same
     auto-height + take-out-of-the-flex-chain fix as the family above: size the
     card to chart + cards so neither overlaps nor scrolls. */
  #popChartWrap {
    height: auto;
    min-height: 0;
    max-height: none;
    overflow: visible;
  }

  #popChartWrap .chart-body-row,
  #popChartWrap .chart-main-area {
    flex: none;
  }

  /* Definite height rather than flex:1 1 0, which has no leftover space to grow
     into once the shell above is auto-height. */
  #popChartWrap .chart-main {
    flex: none;
    height: 180px;
    min-height: 180px;
  }

  .nat-bar-chart-main,
  .subsidy-bar-chart-main {
    overflow: hidden;
  }

  /* Unemployment/crime-by-nationality: these two have an extra min-population
     slider on top of the year slider, so their legend+2-sliders+caption
     content alone can exceed the card's fixed dvh height budget even with
     chart-main shrunk to its floor. Let the card size to its content instead
     of clamping to a fixed height and scrolling internally to reach the
     sliders below the chart. */
  #unempBarWrap,
  #natBarChartWrap {
    height: auto;
    min-height: 0;
    max-height: none;
    overflow: visible;
  }

  /* .chart-body-row and .chart-main-area are flex:1-based (flex-grow into
     whatever space the shell hands down), which only makes sense when the
     shell has a definite height. With the shell now auto, flex-basis:0%
     items in an intrinsically-sized flex container don't reliably size to
     their content in every browser — empirically the card collapsed to a
     sliver instead of growing. Take both out of the flex-grow chain so
     their height is plain content-driven, like a normal block. */
  #unempBarWrap .chart-body-row,
  #natBarChartWrap .chart-body-row,
  #unempBarWrap .chart-main-area,
  #natBarChartWrap .chart-main-area {
    flex: none;
  }

  /* flex-grow has no "leftover space" to distribute once the shell above is
     auto-height, so give the chart itself a fixed height here instead of
     relying on flex:1 1 0 (which would otherwise settle at the family's
     50px floor — legible, but needlessly cramped when the card is free to
     grow to fit it). */
  #unempBarWrap .nat-bar-chart-main,
  #natBarChartWrap .nat-bar-chart-main {
    flex: none;
    height: 180px;
    min-height: 180px;
  }

  /* Subsidy / economic contribution / net migration / migri: the opposite
     problem from unemployment/crime above — their content (chart + a single
     caption or slider, no extra min-population slider) is naturally shorter
     than the card's fixed dvh budget, leaving blank space at the bottom
     instead of needing to scroll. Same auto-height + take-out-of-flex-chain
     fix, just sizing the card down to its content rather than up. */
  #subsidyBarWrap,
  #econContribWrap,
  #netMigrWrap,
  #migriWrap {
    height: auto;
    min-height: 0;
    max-height: none;
    overflow: visible;
  }

  #subsidyBarWrap .chart-body-row,
  #econContribWrap .chart-body-row,
  #netMigrWrap .chart-body-row,
  #migriWrap .chart-body-row,
  #subsidyBarWrap .chart-main-area,
  #econContribWrap .chart-main-area,
  #netMigrWrap .chart-main-area,
  #migriWrap .chart-main-area {
    flex: none;
  }

  #subsidyBarWrap .subsidy-bar-chart-main,
  #econContribWrap .subsidy-bar-chart-main,
  #netMigrWrap .nat-bar-chart-main,
  #migriWrap .nat-bar-chart-main {
    flex: none;
    height: 220px;
    min-height: 220px;
  }

  /* Up to 768 px these four bar charts lie down, one row per bar, when
     standing bars would be narrower than 64 px (ChartView.elm, viewBarRows),
     and are then as tall as their rows. The fixed heights above were for
     standing bars in a drawing that scrolled sideways. The cards already size
     to their content, so the chart area can too. */
  #unempBarWrap .chart-main:has(.chart-elm-wrap--rows),
  #natBarChartWrap .chart-main:has(.chart-elm-wrap--rows),
  #subsidyBarWrap .chart-main:has(.chart-elm-wrap--rows),
  #econContribWrap .chart-main:has(.chart-elm-wrap--rows) {
    height: auto;
    min-height: 0;
    max-height: none;
  }

  #unempBarWrap .chart-main:has(.chart-elm-wrap--rows) .chart-scroll-wrapper,
  #natBarChartWrap .chart-main:has(.chart-elm-wrap--rows) .chart-scroll-wrapper,
  #subsidyBarWrap .chart-main:has(.chart-elm-wrap--rows) .chart-scroll-wrapper,
  #econContribWrap .chart-main:has(.chart-elm-wrap--rows) .chart-scroll-wrapper {
    height: auto;
  }

  /* Let this chart's plot area fill the card's fixed height instead of leaving
     dead space below it — its sidebar/legend content is shorter than the others. */
  #langSpeakersWrap .lang-speakers-chart-main {
    flex: 1 1 0;
    height: auto;
    max-height: none;
    min-height: 180px;
  }
  .nat-bar-chart-main .main-svg-chart,
  .subsidy-bar-chart-main .main-svg-chart,
  .lang-speakers-chart-main .main-svg-chart {
    position: absolute !important;
    inset: 0;
    width: 100% !important;
    height: 100% !important;
  }

  /* These sit alongside the legend/chart-main/etc. as siblings in
     .chart-main-area, which already spaces them via flex `gap` — their own
     extra padding/margin was stacking on top of that gap, giving each
     section a different amount of breathing room from its neighbour. */
  .nat-year-slider-section { padding: 0; gap: 6px; }
  .chart-data-note { margin-top: 0; }

  /* Charts with two stacked slider sections (min-population + year range,
     e.g. unempBarWrap/natBarChartWrap/migriWrap) need this shorter to have
     any chance of fitting a mobile card without the card scrolling
     internally — the touch target shrinks a bit but stays draggable. */
  .nat-dual-slider-wrap {
    height: 28px;
    margin-bottom: 16px;
  }

  .chart-share-btn span, .map-share-btn span { display: none; }
  .chart-share-btn, .map-share-btn { padding: 0 8px; min-width: 32px; }
  /* Cross-view links collapse to icons on mobile like the share button —
     but keep their labels inside the map sidebar row, where there is space. */
  .chart-title-row .chart-xlink span { display: none; }
  .chart-title-row .chart-xlink { padding: 0 8px; min-width: 32px; }

  .chart-preset-option {
    align-items: center;
    padding: 8px 10px 8px 8px;
    border: 1px solid rgba(112, 195, 226, 0.14);
    border-radius: var(--radius-pill);
    background: rgba(112, 195, 226, 0.05);
    line-height: 1;
  }

  .chart-mun-search {
    min-height: 38px;
    padding: 7px 28px 7px 10px;
    border-radius: var(--radius-sm);
  }

  .chart-mun-item {
    gap: 8px;
    padding: 6px 7px;
    font-size: var(--fs-sm);
    border-radius: var(--radius-sm);
  }

  .chart-legend {
    gap: 6px 10px;
    min-height: 0;
  }

  .chart-legend > span,
  .chart-legend-item {
    min-width: 0;
    overflow-wrap: anywhere;
  }

  .chart-subtitle,
  .chart-data-note,
  .chart-sidebar-hint,
  .subsidy-group-note {
    line-height: var(--lh-ui);
  }

  .nat-year-range-label {
    letter-spacing: 0.02em;
  }

  .chart-methodology-panel { padding: 12px; border-radius: var(--radius-lg); }
}

/* ── Very small screens (≤480px) ── */
@media (max-width: 480px) {
  .top-bar {
    padding-right: 0;
    padding-left: 0;
  }
  .mobile-nav-dropdown {
    max-width: min(40vw, 160px);
  }
  .map-quick-city-buttons {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
  .lang-dropdown { right: calc(var(--safe-right) + 42px); }
  .lang-dropdown-trigger {
    min-width: 0;
    padding: 0 6px;
  }
  .chart-stage {
    --chart-shell-height: 62dvh;
    --chart-stage-top-gap: 8px;
    --chart-scroll-margin: calc(var(--safe-top) + var(--top-bar-height) + 8px);
    padding: 0 8px 0;
    gap: 10px;
    flex-direction: column;
    align-items: stretch;
  }
  .chart-stage-col {
    flex-basis: 100%;
    max-width: 100%;
  }
  .chart-toc-wrap {
    width: 100%;
  }
  .chart-toc-toggle-btn {
    left: 8px;
    top: calc(var(--top-bar-height) + 6px);
    padding: 6px 10px 6px 8px;
    font-size: var(--fs-xs);
  }

  .europe-stage { padding: 0; }
  .europe-control-panel {
    padding: 13px;
    left: 6px;
    right: 6px;
  }

  .chart-container-shell { padding: 12px; border-radius: var(--radius-lg); }
  .chart-sidebar { padding-bottom: 10px; }
  .chart-legend { gap: 5px 8px; }
  .subsidy-group-note,
  .chart-sidebar-hint { line-height: 1.4; }

  .chart-main {
    max-height: 240px;
  }

  /* The dumbbells are rows, and each row needs a legible height: capped at
     240 px, fifteen of them were squeezed to 7 px text. Their chart area takes
     the height the rows need and the card, which scrolls on a phone, carries
     it. Keyed on a class the renderer always sets, not on whether the chart
     overflows, so the height cannot feed back into itself. */
  .chart-main:has(.chart-elm-wrap--rows) {
    max-height: none;
    flex: 0 0 auto;
  }

  .chart-main:has(.chart-elm-wrap--rows) .chart-scroll-wrapper {
    height: auto;
  }

  .nat-bar-chart-main,
  .subsidy-bar-chart-main,
  .lang-speakers-chart-main { height: 220px; min-height: 220px; }
}

/* ── Cartogram stage ── */
.cartogram-stage {
  flex: 1 1 0;
  min-width: 0;
  min-height: 0;
  height: 100%;
  position: relative;
  overflow: hidden;
  background:
    radial-gradient(circle at 48% 38%, rgba(41, 93, 118, 0.13), transparent 36%),
    linear-gradient(180deg, #08131d 0%, #02070d 100%);
}

.cartogram-basemap {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  background:
    radial-gradient(circle at 50% 35%, rgba(70, 129, 154, 0.12), transparent 34%),
    radial-gradient(circle at 50% 72%, rgba(255, 91, 123, 0.035), transparent 34%),
    linear-gradient(180deg, #08131d 0%, #02070d 100%);
}

.cartogram-basemap::before,
.cartogram-basemap::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.cartogram-basemap::before {
  background:
    linear-gradient(90deg, rgba(112, 195, 226, 0.035), transparent 26%, transparent 74%, rgba(255, 91, 123, 0.025)),
    radial-gradient(circle at 50% 50%, transparent 0 28%, rgba(2, 7, 13, 0.22) 78%);
  opacity: 0.72;
}

.cartogram-basemap::after {
  background:
    linear-gradient(100deg, transparent 0%, rgba(132, 171, 190, 0.030) 48%, transparent 66%),
    linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.18) 100%);
  opacity: 0.5;
}

.cartogram-basemap .leaflet-control-container {
  pointer-events: none;
}

.cartogram-basemap .leaflet-control-attribution {
  opacity: 0.82;
}

.cartogram-elm-mount {
  position: absolute;
  inset: 0;
  z-index: 2;
}

/* Cartogram colour-scale legend: float bottom-left over the stage, matching the
   map view's bottom-left legend (was previously in the sidebar). */
.cartogram-floating-legend {
  position: absolute;
  right: var(--safe-right);
  bottom: var(--safe-bottom);
  z-index: 6;
  pointer-events: none;
}

/* On a tall screen the cloud fills the frame and its dense end is the south, at
   the bottom. Lapland tapers, so the top-left corner is empty — the legend goes
   there rather than over Uusimaa. */
@media (orientation: portrait) and (max-width: 920px) {
  .cartogram-floating-legend {
    right: auto;
    bottom: auto;
    left: var(--safe-left, 8px);
    top: calc(var(--top-bar-height) + 12px);
  }

  /* The legend has the top of a portrait cartogram; the Europe pill takes the
     bottom so the two never overlap. */
  #cartogramStage .europe-mode-pill {
    top: auto;
    bottom: calc(var(--safe-bottom, 0px) + 18px);
  }
}

.cartogram-sidebar-legend {
  display: none;
}

.cartogram-stage svg {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: block;
  width: 100%;
  height: 100%;
}

.cartogram-svg {
  cursor: grab;
}

.cartogram-backdrop {
  pointer-events: none;
}

.cartogram-map-lines {
  pointer-events: none;
}

.cartogram-map-grid,
.cartogram-map-boundaries {
  fill: none;
  vector-effect: non-scaling-stroke;
}

.cartogram-map-grid {
  shape-rendering: crispEdges;
}

.cartogram-map-grid--minor {
  stroke: rgba(119, 158, 176, 0.035);
  stroke-width: 0.55;
}

.cartogram-map-grid--major {
  stroke: rgba(119, 158, 176, 0.075);
  stroke-width: 0.8;
}

.cartogram-map-boundaries {
  stroke: rgba(157, 205, 224, 0.135);
  stroke-width: 0.7;
  stroke-linejoin: round;
  stroke-linecap: round;
}

/* The island handles pan/pinch itself, so the browser must not consume touch
   gestures on the SVG for scrolling or page zoom first. */
.cartogram-svg {
  touch-action: none;
}

.cartogram-backdrop-shadow-node {
  fill: rgba(111, 162, 186, 0.045);
}

.cartogram-backdrop-tone-node {
  opacity: 0.04;
}

.cartogram-backdrop-node {
  fill: transparent;
  stroke: rgba(157, 198, 216, 0.08);
  stroke-width: 0.55;
  vector-effect: non-scaling-stroke;
}

.cartogram-circle {
  cursor: pointer;
}
/* ── End cartogram stage ── */

/* ── Population key figures ── */
.pop-key-figures {
  display: flex;
  flex: 0 0 auto;
  gap: 10px;
  flex-wrap: wrap;
  min-width: 0;
  max-height: 176px;
  overflow-y: auto;
  overflow-x: hidden;
  padding: 2px 2px 0 0;
  scrollbar-gutter: stable;
}
.pop-kf-card {
  flex: 1 1 180px;
  min-width: 0;
  box-sizing: border-box;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 12px 14px 14px;
}
.pop-kf-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  margin-bottom: 6px;
  min-height: 0;
  display: flex;
  align-items: flex-end;
  /* Finnish compounds ("Ulkomaalaistaustaisten") are wider than this card on a
     phone. anywhere cut them at whatever character landed on the edge; auto
     hyphenation breaks them at syllables instead, and the document carries a
     real lang attribute in both languages for it to work from. */
  overflow-wrap: break-word;
  hyphens: auto;
}
.pop-kf-value {
  font-size: clamp(1.2em, 1.7vw, 1.6em);
  font-weight: 700;
  color: var(--text);
  line-height: 1;
  /* No break at all. The thousands separator is already U+00A0, so "660 800"
     holds together on its own -- anywhere was overriding that and splitting
     the digits themselves. */
  overflow-wrap: normal;
}
.pop-kf-sub {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: 4px;
}
.pop-kf-pair {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  min-width: 0;
}
.pop-kf-label + .pop-kf-pair {
  margin-top: 8px;
}
.pop-kf-pair-item {
  flex: 1;
  display: flex;
  flex-direction: column;
}
.pop-kf-change-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  line-height: 1.3;
  margin-top: 6px;
  overflow-wrap: break-word;
  hyphens: auto;
}
.pop-kf-change-val {
  font-size: clamp(1.2em, 1.7vw, 1.6em);
  font-weight: 700;
  line-height: 1;
  overflow-wrap: normal;
}
.pop-kf-up   { color: var(--accent); }
.pop-kf-down { color: var(--muted);  }

@media (max-width: 700px) {
  .pop-key-figures {
    max-height: none;
    overflow: visible;
  }
}

.chart-category-header {
  display: none;
}
.chart-stage-col > .chart-category-header:first-child {
  padding-top: 8px;
}
.chart-main > .chart-category-header:first-child {
  padding-top: 4px;
}

/* ── Chart table of contents ── */
/* ── Chart TOC toggle button ── */
.chart-toc-toggle-btn.is-open {
  background: var(--control-hover-bg);
  border-color: var(--control-hover-border);
  color: var(--text);
}

.chart-toc-wrap {
  position: sticky;
  top: 0;
  flex-shrink: 0;
  align-self: flex-start;
  width: var(--chart-toc-width);
  margin-top: var(--chart-stage-top-gap);
  padding-top: 4px;
  opacity: 1;
  z-index: 1;
  pointer-events: auto;
  overflow: visible;
  transition: width var(--chart-toc-transition), opacity var(--chart-toc-transition);
}
.chart-stage.is-toc-open .chart-toc-wrap {
  width: var(--chart-toc-width);
  opacity: 1;
  pointer-events: auto;
  overflow: visible;
}
/* Desktop: collapse the chart TOC column when the toggle is off. */
.chart-stage:not(.is-toc-open) {
  gap: 0;
}
.chart-stage:not(.is-toc-open) .chart-toc-wrap {
  width: 0;
  margin: 0;
  padding-top: 0;
  opacity: 0;
  pointer-events: none;
  overflow: hidden;
}
.chart-toc {
  padding: 4px 4px 8px;
  overflow: visible;
  pointer-events: auto;
}
.chart-toc-title {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  color: var(--muted);
  margin-bottom: 12px;
  padding: 0 4px;
}
.chart-toc-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  counter-reset: toc-section;
}
.chart-toc-list > li {
  display: flex;
  flex-direction: column;
  gap: 2px;
  counter-increment: toc-section;
}
.chart-toc-list > li > a {
  display: flex;
  align-items: baseline;
  gap: 6px;
  text-decoration: none;
  font-weight: 600;
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--text);
  padding: 4px 4px;
  border-radius: var(--radius-xs);
  transition: color 150ms ease;
}
.chart-toc-list > li > a::before {
  content: counter(toc-section) ".";
  color: var(--muted);
  font-weight: 700;
  font-size: var(--fs-xs);
  min-width: 1.4em;
  flex-shrink: 0;
}
.chart-toc-list > li > a:hover {
  color: var(--accent);
}
.chart-toc-list > li:not(.toc-group-active) > .chart-toc-sublist {
  display: none;
}

.chart-toc-sublist {
  margin: 0;
  padding: 0 0 6px 22px;
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 1px;
}
.chart-toc-sublist li {
  display: flex;
}
.chart-toc-sublist a {
  width: 100%;
  text-decoration: none;
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  padding: 3px 4px;
  border-radius: var(--radius-xs);
  transition: color 150ms ease;
}
.chart-toc-sublist a:hover {
  color: var(--accent);
}
.chart-toc-list > li > a.toc-active {
  color: var(--accent);
}
/* The active entry is heavier, but not by a font-weight: at 600 the longest
   labels grew past the column and wrapped onto a second line, so scrolling
   past a section made the whole list jump. A stroke of the text's own colour
   thickens the glyphs without changing a single metric. */
.chart-toc-sublist a.toc-active {
  color: var(--text);
  text-shadow: 0 0 0.4px currentColor, 0 0 0.4px currentColor;
}

/* ── Chart stage content column ── */
.chart-stage-col {
  flex: 1 1 auto;
  width: 100%;
  max-width: min(1680px, 100%);
  min-width: 0;
  margin: 0 auto;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  gap: 20px;
  padding-left: 0;
  /* Keep a sliver of the page's own dark background below the last card
     (the "Lähteet ja huomiot" methodology panel) instead of it sitting
     flush against the bottom of the scrollport. */
  padding-bottom: 20px;
}

/* ─────────────────────────────────────────────
   History view: migration flow map (src/MigrationMap.elm)
───────────────────────────────────────────── */
/* One arc per country per year, and the year is a control rather than a
   caption. The map and the numbers beside it are one figure: the arcs carry the
   shape, the panel carries the values, because an arc's thickness answers "which
   is bigger" and never "how many". */
.history-migration-map {
  margin: 18px 0 26px;
}

.migmap {
  border: 1px solid var(--kb-migmap-border);
  border-radius: var(--radius-sm);
  background: var(--surface-panel);
  padding: 14px 14px 12px;
}

.migmap-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 10px;
}

.migmap-title {
  font-size: var(--fs-sm);
  font-weight: 650;
  margin: 0;
  color: var(--text);
}

.migmap-year {
  font-size: 1.5rem;
  font-weight: 700;
  line-height: 1;
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

/* The measure switch, above the map because it changes what the arcs count and
   not merely which of them are shown. Chip metrics match the knowledge base's
   theme filter, which is the site's other row of this shape. */
.migmap-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin: 0 0 10px;
}

.migmap-filter {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 5px 12px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.migmap-filter:hover {
  color: var(--text);
  border-color: var(--accent);
}

.migmap-filter.is-active {
  color: var(--text);
  border-color: var(--accent);
  background: var(--surface-panel);
}

@media (prefers-reduced-motion: reduce) {
  .migmap-filter { transition: none; }
}

.migmap-body {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 220px;
  gap: 14px;
  align-items: start;
}

.migmap-mapwrap {
  overflow: hidden;
  border: 1px solid var(--kb-migmap-border);
  border-radius: var(--radius-sm);
  background:
    radial-gradient(circle at 30% 18%, var(--kb-migmap-bg-radial), transparent 34%),
    linear-gradient(180deg, var(--kb-migmap-bg-1), var(--kb-migmap-bg-2));
}

.migmap-svg {
  display: block;
  width: 100%;
  height: auto;
}

.migmap-land {
  fill: var(--migmap-land);
  stroke: var(--migmap-land-stroke);
  stroke-width: 0.4;
}

.migmap-land--finland {
  fill: var(--kb-migmap-dest-fill);
  stroke: var(--kb-migmap-dest-stroke);
  stroke-width: 0.8;
}

.migmap-finland {
  fill: var(--accent);
}

.migmap-arc {
  fill: none;
  stroke-linecap: round;
  opacity: 0.82;
  transition: opacity 0.12s ease;
}

.migmap-arc--out {
  opacity: 0.5;
  stroke-dasharray: 3 3;
}

.migmap-arc.is-dimmed,
.migmap-head.is-dimmed {
  opacity: 0.12;
}

.migmap-arc.is-focused {
  opacity: 1;
}

.migmap-head {
  opacity: 0.85;
  transition: opacity 0.12s ease;
}

/* An arc a few pixels wide is a hard thing to point at, so every arc carries an
   invisible one ten pixels wide underneath the pointer. */
.migmap-hit {
  fill: none;
  stroke: transparent;
  stroke-width: 10;
  cursor: pointer;
  /* The stroke is invisible, and a browser is entitled to skip hit-testing what
     it does not paint. This says the stroke is the target whether or not it has
     a colour. */
  pointer-events: stroke;
}

.migmap-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
  min-width: 0;
}

.migmap-total {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.migmap-total-value {
  font-size: 1.35rem;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.migmap-total-label {
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.3;
}

.migmap-list,
.migmap-legend-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.migmap-list-row,
.migmap-legend-item {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--fs-xs);
  color: var(--text);
  line-height: 1.25;
}

.migmap-dot {
  width: 9px;
  height: 9px;
  border-radius: var(--radius-xs);
  flex: 0 0 auto;
  display: inline-block;
}

.migmap-dot--out {
  background: var(--migmap-out);
  height: 3px;
  border-radius: var(--radius-xs);
}

.migmap-list-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.migmap-list-value {
  font-variant-numeric: tabular-nums;
  color: var(--muted);
  flex: 0 0 auto;
}

.migmap-detail {
  border: 1px solid var(--kb-migmap-border);
  border-radius: var(--radius-sm);
  padding: 8px 9px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.migmap-detail-name {
  font-weight: 650;
  font-size: var(--fs-sm);
  color: var(--text);
}

.migmap-detail-row {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: var(--fs-xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.migmap-detail-ground {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--fs-xs);
  color: var(--text);
  margin-top: 2px;
}

.migmap-detail-note {
  margin: 2px 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: var(--lh-ui);
}

.migmap-legend-title,
.migmap-legend-note {
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: var(--lh-ui);
}

.migmap-legend-title {
  margin-bottom: 5px;
  font-weight: 600;
}

.migmap-legend-note {
  margin: 6px 0 0;
}

.migmap-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-top: 10px;
}

.migmap-play {
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--text);
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 5px 14px;
  cursor: pointer;
  flex: 0 0 auto;
}

.migmap-play:hover {
  border-color: var(--accent);
}

.migmap-slider {
  flex: 1 1 auto;
  min-width: 0;
  accent-color: var(--accent);
}

.migmap-scale-end {
  font-size: var(--fs-xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
}

.migmap--empty {
  color: var(--muted);
  font-size: var(--fs-sm);
}

/* Below the panel's own width the numbers go under the map rather than beside
   it, and the map keeps the full column: an arc drawn 220 px narrower is the
   part that stops being readable first. */
@media (max-width: 860px) {
  .migmap-body {
    grid-template-columns: minmax(0, 1fr);
  }

  .migmap-panel {
    flex-direction: column;
  }

  .migmap-year {
    font-size: 1.25rem;
  }
}

/* ─────────────────────────────────────────────
   History view: year-by-year timeline
───────────────────────────────────────────── */
/* A year is a row on a vertical rule. Quotations carry the section, so they
   are set large and in the text colour, with the speaker under them, while laws
   and figures sit in the muted colour: the reader should meet the words first
   and the paperwork second. */
.hist-filter {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin: 16px 0 0;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: rgba(10, 18, 28, 0.34);
}

.hist-filter-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 10px;
}

.hist-filter-label {
  min-width: 11.5em;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
}

.hist-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.hist-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 30px;
  padding: 5px 10px;
  border: 1px solid rgba(112, 195, 226, 0.16);
  border-radius: var(--radius-md);
  background: rgba(10, 18, 28, 0.54);
  color: var(--text);
  font: inherit;
  font-size: var(--fs-sm);
  line-height: 1.1;
  cursor: pointer;
}

.hist-filter-chip:hover {
  border-color: rgba(112, 195, 226, 0.34);
}

.hist-filter-chip[aria-pressed="true"] {
  border-color: rgba(214, 177, 98, 0.72);
  background: rgba(214, 177, 98, 0.16);
  color: #ffe2a0;
}

.hist-filter-count {
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}

.hist-filter-chip[aria-pressed="true"] .hist-filter-count {
  color: inherit;
}

.hist-timeline {
  list-style: none;
  margin: 18px 0 0;
  padding: 0 0 0 22px;
  border-left: 2px solid var(--border);
}

.hist-year {
  position: relative;
  padding: 0 0 26px;
}

.hist-year--compact {
  padding-bottom: 12px;
}

.hist-year::before {
  content: "";
  position: absolute;
  left: -28px;
  top: 6px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg);
}

.hist-year--compact::before {
  width: 6px;
  height: 6px;
  left: -26px;
  top: 8px;
  background: var(--muted);
}

.hist-year-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px 14px;
}

.hist-year-num {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.hist-year--compact .hist-year-num {
  font-size: var(--fs-sm);
  color: var(--muted);
}

.hist-figures {
  font-size: var(--fs-sm);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.hist-figures strong {
  color: var(--text);
  font-weight: 650;
}

.hist-items {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.hist-item {
  max-width: 68ch;
}

.hist-kind {
  display: inline-block;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 3px;
}

.hist-text {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--text);
}

.hist-quote {
  margin: 0;
  padding: 0 0 0 14px;
  border-left: 3px solid var(--accent);
}

.hist-quote p {
  margin: 0;
  font-size: 1.02rem;
  line-height: var(--lh-prose);
  color: var(--text);
}

.hist-quote p::before {
  content: "”";
}

.hist-quote p::after {
  content: "”";
}

.hist-quote-who {
  margin-top: 6px;
  font-size: var(--fs-sm);
  color: var(--muted);
}

.hist-note {
  margin: 8px 0 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
}

/* Underline as text decoration, not a box border: a label that wraps to two
   lines gets a line under each line of text instead of one line under the
   whole box that extends past the last word. */
.hist-source {
  display: inline-block;
  margin-top: 6px;
  font-size: var(--fs-xs);
  color: var(--muted);
  text-decoration: underline;
  text-decoration-color: var(--border);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
}

.hist-chart {
  margin-top: 14px;
  max-width: 900px;
}

/* One demand against one outcome: two thin bars on the chart scale, inside
   the year row. Label, track and value on one line so the strip is a third
   of a screen at most instead of a full chart frame. */
.hist-compare {
  margin: 22px 0 10px;
  padding: 14px 0 0;
  border-top: 1px solid var(--border);
  max-width: 720px;
  display: grid;
  gap: 12px;
}

.hist-compare-title {
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--text);
  margin-bottom: 4px;
}

/* Equal rows, so the bars are evenly spaced whatever the labels do. A label
   that wraps to three lines would otherwise make its own row taller and push
   its bar out of the rhythm the other rows keep. 1fr auto-rows all resolve to
   the tallest row's height. */
.hist-compare-rows {
  display: grid;
  grid-auto-rows: 1fr;
  gap: 12px;
}

.hist-compare-row {
  display: grid;
  grid-template-columns: minmax(170px, 1.1fr) 3fr auto;
  align-items: center;
  gap: 14px;
  font-size: var(--fs-sm);
}

.hist-compare-label {
  color: var(--text);
  line-height: var(--lh-ui);
}

.hist-compare-label em {
  font-style: normal;
  color: var(--muted);
}

.hist-compare-track {
  display: block;
  height: 20px;
  background: color-mix(in srgb, var(--border) 55%, transparent);
  border-radius: var(--radius-xs);
  overflow: hidden;
}

.hist-compare-bar {
  display: block;
  height: 100%;
  border-radius: var(--radius-xs);
}

.hist-compare-value {
  font-variant-numeric: tabular-nums;
  font-weight: 650;
  font-size: var(--fs-sm);
  min-width: 5ch;
  text-align: right;
}

.hist-compare-row--actual .hist-compare-value {
  color: var(--tint-sky);
}

/* The sky tint as text read at 3.5:1 on the light theme's panel. */
body.light-theme .hist-compare-row--actual .hist-compare-value {
  color: #00708f;
}

.hist-compare-foot {
  margin: 0;
  font-size: var(--fs-xs);
  line-height: var(--lh-prose);
  color: var(--muted);
  max-width: 68ch;
}

.hist-compare-foot:first-of-type {
  margin-top: 6px;
}

.hist-compare-foot--scale {
  font-size: var(--fs-xs);
}

@media (max-width: 600px) {
  .hist-compare-row {
    grid-template-columns: 1fr auto;
    /* The track drops to a line of its own here, so the row holds two lines.
       Packed to the bottom, the spare height an equalised row carries falls
       above the label rather than being split around the track, so the bar
       lands at the same offset in every row. */
    align-content: end;
  }

  .hist-compare-track {
    grid-column: 1 / -1;
  }
}

.hist-source:hover {
  color: var(--text);
  text-decoration-color: var(--accent);
}

@media (max-width: 600px) {
  .hist-timeline {
    padding-left: 16px;
  }

  .hist-year::before {
    left: -22px;
  }

  .hist-year--compact::before {
    left: -20px;
  }

  .hist-quote p {
    font-size: var(--fs-sm);
  }
}

/* ─────────────────────────────────────────────
   Knowledge base (Tietopankki)
───────────────────────────────────────────── */
/* ── Knowledge base: data-only mode ──────────────────────────────────────────
   One toggle, CSS does the rest. The hiding is a class on #knowledgeStage and
   a set of class rules, NOT edits to the rendered HTML, because the knowledge
   view has two renderers (static prerender and the Elm island) that share
   these class names -- a CSS-only mode cannot drift between them. The button
   itself sits on the stage outside the Elm mount, so hydration keeps it. */
/* This is the site's own slide toggle, .ui-switch, which was written generic so
   that "the next on/off control in the sidebar can reuse it rather than growing
   a third toggle shape". This control grew the third shape anyway: a pill with
   its own fill and outline, wrapping a 26x14 track. Sitting in a column of
   filter chips, the pill read as one more chip rather than as a switch, and
   inside it the slider was the smallest thing on the row.
   So it carries .ui-switch now and only its ON state is defined here. */

/* Separates the mode button from the topic chips. Stretches to the row's own
   height rather than a fixed one, so it stays right whatever the chips do. */
/* The switch takes the first line alone, so the rule between it and the chips
   is a line break rather than a vertical bar: a full-width, zero-height item
   in the wrapping row. Before this the chips packed in beside the switch and
   the column read as one dense block. */
.kb-filter-divider {
  flex-basis: 100%;
  height: 0;
  margin: 0;
  /* --border is tuned for a chip outline, which is too faint to read as a rule
     between two groups of chips, so this is its own value in both themes. */
  background: rgba(112, 195, 226, 0.34);
}

body.light-theme .kb-filter-divider {
  background: rgba(0, 0, 0, 0.22);
}

/* The ON look keys off the stage class rather than the switch's own
   aria-checked, which is what .ui-switch normally uses. The Elm island
   re-creates the button on every render with aria-checked "false" and the JS
   re-syncs it on the next frame, so an attribute-driven rule flicks the switch
   off and back on mid-render. The stage sits outside the island and keeps its
   state, so keying off it is steady. These mirror the .ui-switch checked rules
   exactly -- 14px of travel on a 32px track, and the knob colour each theme
   needs against a filled track. */
#knowledgeStage.kb-data-only .kb-data-mode-btn .ui-switch-track {
  background: var(--accent);
  border-color: var(--accent);
}

#knowledgeStage.kb-data-only .kb-data-mode-btn .ui-switch-knob {
  transform: translateX(14px);
  background: #000;
}

#knowledgeStage.kb-data-only .kb-data-mode-btn .ui-switch-label {
  color: var(--text);
}

body.light-theme #knowledgeStage.kb-data-only .kb-data-mode-btn .ui-switch-knob {
  background: #ffffff;
}

html:not(.js) :is(.kb-data-mode-btn, .kb-filter-divider) {
  display: none;
}

/* What data mode keeps: the main title, the intro that states the idea of the
   knowledge base, the theme filter, group and section headings, every figure
   (charts, ranked bars, stat tiles), and the comparison grid -- a list of the
   statistical sources other countries publish, which is data work rather than
   narrative. Everything narrative goes: section and group leads, notes, source
   cards, survey findings. A section, country block or whole group with no
   figure in it disappears too, or it would render as a bare heading. */
#knowledgeStage.kb-data-only :is(
  .kb-section-lead,
  .kb-group-lead,
  .kb-section-note,
  .kb-country-lead,
  .kb-articles,
  .kb-articles-disclosure,
  .kb-article-group,
  .kb-surveys
) {
  display: none !important;
}

#knowledgeStage.kb-data-only .kb-country:not(:has(
  .kb-chart, .kb-catbar-chart, .kb-line-chart, .kb-div-chart,
  .kb-gdiv-chart, .kb-rank-chart, .kb-stat-tiles-chart, .kb-stat-tiles,
  .kb-matrix-chart
)),
#knowledgeStage.kb-data-only .kb-section:not(:has(
  .kb-chart, .kb-catbar-chart, .kb-line-chart, .kb-div-chart,
  .kb-gdiv-chart, .kb-rank-chart, .kb-stat-tiles-chart, .kb-stat-tiles,
  .kb-matrix-chart
)),
#knowledgeStage.kb-data-only .kb-group:not(:has(
  .kb-chart, .kb-catbar-chart, .kb-line-chart, .kb-div-chart,
  .kb-gdiv-chart, .kb-rank-chart, .kb-stat-tiles-chart, .kb-stat-tiles,
  .kb-matrix-chart
)) {
  display: none !important;
}

.knowledge-stage {
  flex-direction: column;
  align-items: stretch;
  overflow-y: auto;
  /* The nav floats over the stage (which starts at viewport top), so TOC
     jumps must clear the bar's height plus a small gap, or the target
     section's title lands hidden under the nav. */
  scroll-padding-top: calc(var(--top-bar-height) + 16px);
  padding: 0;
  min-height: 0;
}

/* History reuses the knowledge base's column: same content widths, same
   scrolling, because its sections are built from the same parts. This rule
   must stay at the top level. Inside a media query it hands the stage no
   overflow on desktop, and the view cannot be scrolled at all. */
/* A view that is still being built: no nav entry in a production build. The
   attribute is written by repo_support/build.mjs from the UNRELEASED_VIEWS set
   in build_stage.mjs, and is absent from a staging build, where the view is
   meant to be reachable. The desktop tab is drawn by the Elm top bar after
   hydration, which is why this is a stylesheet rule and not a strip of the
   shell HTML. */
body[data-unreleased~="history"] #tabHistory,
body[data-unreleased~="history"] #mobileNavMenu li[data-view="history"],
body[data-unreleased~="history"] .lang-dropdown-link--history {
  display: none !important;
}
body[data-unreleased~="tools"] #tabTools,
body[data-unreleased~="tools"] #mobileNavMenu li[data-view="tools"],
body[data-unreleased~="tools"] .lang-dropdown-link--tools {
  display: none !important;
}

/* ── Tools view: five calculators ─────────────────────────────────────── */
.tools-stage {
  flex-direction: column;
  align-items: stretch;
  overflow-y: auto;
  padding: 0;
  min-height: 0;
}
@media (max-width: 920px) {
  .tools-stage {
    padding-top: calc(var(--safe-top) + var(--top-bar-height));
  }
}
.tools-page {
  width: 100%;
  padding: 28px 20px 72px;
}
.tools-inner {
  max-width: 1080px;
  margin: 0 auto;
}
.tools-title {
  font-size: clamp(1.7rem, 3vw, 2.2rem);
  margin: 0 0 8px;
}
/* A view's few sentences on what it holds, above its first card: the charts
   view's, and the map's in its dataset panel. */
.view-lead {
  color: var(--muted);
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
  max-width: 68ch;
  margin: 0 0 18px;
}

.utility-panel-card-heading .view-lead {
  font-size: var(--fs-sm);
  margin: 0;
}

.tools-lead {
  color: var(--muted);
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
  max-width: 68ch;
  margin: 0 0 18px;
}
.tools-error {
  color: var(--accent-hot);
}
/* The tool picker: one card per calculator, the question it answers under
   the name, so the reader picks by what they want to know. */
.tools-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(176px, 1fr));
  gap: 10px;
  margin: 0 0 26px;
}
.tools-card {
  appearance: none;
  display: flex;
  flex-direction: column;
  gap: 4px;
  text-align: left;
  border: 1px solid var(--border);
  background: var(--card-bg);
  color: var(--text);
  font: inherit;
  padding: 12px 14px;
  border-radius: 14px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.tools-card:hover {
  border-color: rgba(var(--accent-rgb), 0.6);
  transform: translateY(-1px);
}
.tools-card.is-active {
  background: rgba(var(--accent-rgb), 0.12);
  border-color: var(--accent);
}
.tools-card-title {
  font-size: var(--fs-md);
  font-weight: 700;
}
.tools-card.is-active .tools-card-title {
  color: var(--accent);
}
.tools-card-question {
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--muted);
}
@media (max-width: 600px) {
  .tools-cards {
    grid-template-columns: 1fr 1fr;
  }
  .tools-card-question {
    display: none;
  }
}
/* 6px put the question all but on top of the panels below it, so it read as a
   label attached to the first box rather than as the heading for both. */
.tool-title {
  font-size: clamp(1.25rem, 2.2vw, 1.5rem);
  margin: 0 0 18px;
}
/* The headline strip sticks under the top bar while the controls below it
   scroll, so every adjustment is answered by a figure that stays in view. */
/* The result card's header row: the live figure on the left, the two
   actions on the right, a rule under both. */
.tool-headline {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 8px 16px;
  padding: 2px 0 12px;
  margin: 0 0 6px;
  border-bottom: 1px solid var(--border);
}
.tool-headline-main {
  min-width: 0;
  flex: 1 1 320px;
}
.tool-headline .tool-big {
  margin-bottom: 0;
}
/* Controls in a grid of groups: who, what, how far along the range. */
.tool-controls {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 14px 28px;
  padding: 16px 18px 18px;
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--card-bg);
  margin-top: 16px;
}

/* Wide viewports put the controls beside the answer rather than under it, so a
   slider and the number it moves are on screen together. This was tried once
   before and removed as "a tall slider column next to a short result card",
   which measuring says is true of exactly one calculator. At full width the
   controls are the SHORTER column in four of the five — 259 against 933,
   392/507, 216/579, 135/367 — so for those the pair balances, and the panel's
   own auto-fit grid collapses to a single column in a sidebar without being
   told to.
   The exception is the cost calculator, whose controls are 1068px against a
   551px result. It is not a panel of sliders but an itemised fold, a table of
   every expenditure line, and a table does not belong in a sidebar. It is
   matched on that content rather than on a width guess, and keeps one column.
   Everything here is CSS: the five views are untouched. */
@media (min-width: 1080px) {
  .tool:not(:has(> .tool-controls .tool-fold)) {
    display: grid;
    grid-template-columns: minmax(270px, 320px) minmax(0, 1fr);
    column-gap: 24px;
    align-items: start;
  }

  /* The vertical rhythm is margins, not row-gap. The panel below spans a row
     count, and every implicit row it reaches past the content costs a row-gap
     even at zero height: on the smallest calculator that was 17 empty rows and
     272px of nothing, which made the two-column layout taller than the stack. */
  .tool:not(:has(> .tool-controls .tool-fold)) > * {
    grid-column: 2;
    margin-top: 16px;
  }

  .tool:not(:has(> .tool-controls .tool-fold)) > .tool-result {
    margin-top: 0;
  }

  .tool:not(:has(> .tool-controls .tool-fold)) > .tool-title {
    grid-column: 1 / -1;
    grid-row: 1;
    margin-top: 0;
  }

  /* `span 20` rather than `2 / -1`. With no explicit rows declared, -1 resolves
     to the first line, so the range reversed and the panel was placed in row 1,
     above the title, pushing every right-hand block onto a row of its own: the
     two-column layout came out taller than the stack it replaced. Spanning a
     count covers the implicit rows the right column creates, so the left column
     is free to be any height without adding one. */
  .tool:not(:has(> .tool-controls .tool-fold)) > .tool-controls {
    grid-column: 1;
    grid-row: 2 / span 20;
    margin-top: 0;
  }
}

.tool-group {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
}
.tool-group.is-wide {
  grid-column: 1 / -1;
}
.tool-fold {
  border: none;
  border-radius: 0;
  background: transparent;
  color: var(--text);
  font-size: inherit;
  padding: 0;
}
.tool-fold > summary {
  cursor: pointer;
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  padding: 4px 0;
}
.tool-fold[open] > summary {
  margin-bottom: 4px;
}
.tool-sub {
  padding: 10px 12px 4px;
  border-left: 2px solid rgba(var(--accent-rgb), 0.5);
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.tool-hint {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--muted);
}
.tool-seg {
  display: inline-flex;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 2px;
  margin-bottom: 12px;
}
.tool-seg-btn {
  appearance: none;
  border: none;
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 5px 12px;
  border-radius: 999px;
  cursor: pointer;
}
.tool-seg-btn.is-on {
  background: rgba(var(--accent-rgb), 0.16);
  color: var(--accent);
}
.tool-comp-sliders {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px 28px;
  padding: 6px 0 2px;
}
.tool-comp {
  display: flex;
  flex-direction: column;
  gap: 6px;
  min-width: 0;
}
.tool-comp-help {
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--muted);
}
.tool-comp-help p {
  margin: 0;
}
.tool-comp-help strong {
  color: var(--text);
  font-weight: 600;
}
.tool-comp-help details {
  border: none;
  border-radius: 0;
  background: transparent;
  padding: 0;
  font-size: inherit;
  color: inherit;
}
.tool-comp-help summary {
  cursor: pointer;
  color: var(--accent);
  font-size: var(--fs-sm);
  letter-spacing: normal;
}
.tool-comp-help details[open] summary {
  margin-bottom: 2px;
}
/* The argument tool. The claims sit in a list grouped by theme, each with its
   verdict, so the list already answers before anything opens and it keeps
   working at twenty claims where a card grid stopped at six. The open claim
   leads with its verdict, then the evidence as cards: a question for a
   heading, one kind of figure drawn the way it reads, and the source as the
   way in. The card that says where the figures do not point is set apart with
   a dashed edge, so a concession is never lost in the middle of the case. */
.tool-debate {
  display: grid;
  grid-template-columns: minmax(200px, 270px) minmax(0, 1fr);
  gap: 36px;
  align-items: start;
}

.tool-debate-list {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

/* The select is the phone's list; on a wide screen the grouped buttons are. */
.tool-debate-select-label,
.tool-debate-select {
  display: none;
}

.tool-debate-groups {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.tool-debate-group {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tool-debate-group-title {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.tool-debate-item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
  padding: 10px 12px;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: rgba(var(--accent-rgb), 0.04);
  color: var(--text);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.tool-debate-item:hover {
  border-color: var(--border);
}

.tool-debate-item.is-active {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.12);
}

.tool-debate-item-text {
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
}

.tool-debate-item.is-active .tool-debate-item-text {
  font-weight: 600;
}

.tool-debate-badge {
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  font-size: var(--fs-xs);
  font-weight: 700;
}

/* One tone per verdict step, so a five-step scale costs five declarations
   instead of five copies of every rule that paints with it. */
.tool-debate-badge,
.tool-debate-verdict {
  --verdict-tone: var(--series-2);
}

.tool-debate-badge.is-holds,
.tool-debate-verdict.is-holds {
  --verdict-tone: var(--series-3);
}

.tool-debate-badge.is-mostly,
.tool-debate-verdict.is-mostly {
  --verdict-tone: color-mix(in srgb, var(--series-3) 60%, var(--series-2));
}

.tool-debate-badge.is-weak,
.tool-debate-verdict.is-weak {
  --verdict-tone: color-mix(in srgb, var(--series-4) 60%, var(--series-2));
}

.tool-debate-badge.is-fails,
.tool-debate-verdict.is-fails {
  --verdict-tone: var(--series-4);
}

.tool-debate-badge.is-holds,
.tool-debate-badge.is-mostly,
.tool-debate-badge.is-partly,
.tool-debate-badge.is-weak,
.tool-debate-badge.is-fails {
  background: color-mix(in srgb, var(--verdict-tone) 18%, transparent);
  color: var(--verdict-tone);
}

.tool-debate-main {
  display: flex;
  flex-direction: column;
  gap: 20px;
  min-width: 0;
}

.tool-debate-head {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tool-debate-eyebrow {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
}

/* The claim is the thing being tested, so it is quoted rather than asserted. */
.tool-debate-claim {
  margin: 0;
  max-width: 44ch;
  font-size: var(--fs-xl);
  font-weight: 600;
  font-style: italic;
  line-height: 1.35;
  color: var(--text);
}

/* Who said it, under the quote. Quiet, because the claim is the subject and
   the speaker is only how to find it again. */
.tool-debate-claim-src {
  margin: 0;
  max-width: 44ch;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.tool-debate-verdict {
  display: grid;
  grid-template-columns: minmax(170px, 220px) minmax(0, 1fr);
  gap: 24px;
  align-items: start;
  padding: 16px 20px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-panel);
}

.tool-debate-verdict.is-holds,
.tool-debate-verdict.is-mostly,
.tool-debate-verdict.is-partly,
.tool-debate-verdict.is-weak,
.tool-debate-verdict.is-fails {
  border-color: color-mix(in srgb, var(--verdict-tone) 45%, transparent);
}

.tool-debate-verdict-side {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tool-debate-verdict-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
}

.tool-debate-verdict-head {
  font-size: var(--fs-lg);
  line-height: var(--lh-tight);
}

.tool-debate-verdict.is-holds .tool-debate-verdict-head,
.tool-debate-verdict.is-mostly .tool-debate-verdict-head,
.tool-debate-verdict.is-partly .tool-debate-verdict-head,
.tool-debate-verdict.is-weak .tool-debate-verdict-head,
.tool-debate-verdict.is-fails .tool-debate-verdict-head {
  color: var(--verdict-tone);
}

.tool-debate-meter {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 3px;
  margin-top: 4px;
}

.tool-debate-meter-seg {
  height: 6px;
  border-radius: 3px;
  background: var(--border);
}

.tool-debate-verdict.is-holds .tool-debate-meter-seg.is-on,
.tool-debate-verdict.is-mostly .tool-debate-meter-seg.is-on,
.tool-debate-verdict.is-partly .tool-debate-meter-seg.is-on,
.tool-debate-verdict.is-weak .tool-debate-meter-seg.is-on,
.tool-debate-verdict.is-fails .tool-debate-meter-seg.is-on {
  background: var(--verdict-tone);
}

.tool-debate-meter-labels {
  display: flex;
  justify-content: space-between;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.tool-debate-verdict-text {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--text);
}

.tool-debate-cards {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.tool-debate-card {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px 18px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: rgba(var(--accent-rgb), 0.03);
}

.tool-debate-card.is-turn {
  border-style: dashed;
  border-color: color-mix(in srgb, var(--series-2) 55%, transparent);
}

.tool-debate-card-q {
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
  font-size: var(--fs-md);
  font-weight: 600;
  line-height: var(--lh-ui);
  color: var(--text);
}

.tool-debate-card-n {
  flex: 0 0 auto;
  min-width: 1.5em;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--accent);
}

.tool-debate-card.is-turn .tool-debate-card-n {
  color: var(--series-2);
}

.tool-debate-card-text {
  margin: 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--text);
}

.tool-debate-bars {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.tool-debate-bar {
  display: grid;
  grid-template-columns: minmax(0, 13em) minmax(0, 1fr) auto;
  gap: 10px;
  align-items: center;
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
}

.tool-debate-bar-label {
  color: var(--muted);
  line-height: var(--lh-ui);
}

.tool-debate-bar-track {
  display: block;
  height: 10px;
  border-radius: 3px;
  background: var(--border);
}

.tool-debate-bar-fill {
  display: block;
  height: 10px;
  border-radius: 3px;
}

.tool-debate-bar-fill.is-base {
  background: var(--series-1);
}

.tool-debate-bar-fill.is-focus {
  background: var(--series-4);
}

.tool-debate-bar-fill.is-mid {
  background: var(--muted);
}

.tool-debate-bar-value {
  min-width: 3.2em;
  text-align: right;
  font-weight: 600;
  color: var(--text);
}

.tool-debate-scale {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.tool-debate-scale-axis {
  position: relative;
  height: 22px;
}

.tool-debate-scale-axis::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 10px;
  height: 2px;
  background: var(--border);
}

.tool-debate-scale-zero {
  position: absolute;
  left: 50%;
  top: 4px;
  width: 1px;
  height: 14px;
  background: var(--muted);
}

.tool-debate-scale-dot {
  position: absolute;
  top: 4px;
  width: 14px;
  height: 14px;
  margin-left: -7px;
  border-radius: 50%;
}

.tool-debate-scale-dot.is-base,
.tool-debate-scale-legend .is-base .tool-debate-scale-key {
  background: var(--muted);
}

.tool-debate-scale-dot.is-mid,
.tool-debate-scale-legend .is-mid .tool-debate-scale-key {
  background: var(--series-1);
}

.tool-debate-scale-dot.is-focus,
.tool-debate-scale-legend .is-focus .tool-debate-scale-key {
  background: var(--series-4);
}

.tool-debate-scale-ends {
  display: flex;
  justify-content: space-between;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.tool-debate-scale-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: var(--fs-xs);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.tool-debate-scale-legend li {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.tool-debate-scale-key {
  width: 9px;
  height: 9px;
  border-radius: 50%;
}

.tool-debate-shifts {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 28px;
}

.tool-debate-shift {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tool-debate-shift-nums {
  display: flex;
  align-items: baseline;
  gap: 8px;
  font-variant-numeric: tabular-nums;
}

.tool-debate-shift-from {
  font-size: var(--fs-xl);
  font-weight: 600;
  color: var(--muted);
}

.tool-debate-shift-arrow {
  color: var(--muted);
}

.tool-debate-shift-to {
  font-size: var(--fs-xl);
  font-weight: 700;
  color: var(--text);
}

.tool-debate-shift-label {
  font-size: var(--fs-xs);
  color: var(--muted);
}

.tool-debate-stat {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.tool-debate-stat-big {
  font-size: var(--fs-2xl);
  line-height: var(--lh-tight);
  font-variant-numeric: tabular-nums;
  color: var(--text);
}

.tool-debate-stat-caption {
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.tool-debate-src {
  margin: 0;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

/* The source line is the way in to the figure, so where there is somewhere to
   go it reads as a link -- in the accent, not the browser's own blue, and with
   the same chevron the rest of the tools view uses for a way out of the page.
   The underline waits for the hover. The card's own gap already separates it
   from the figure, so it sits at the card's foot. */
a.tool-debate-src {
  display: block;
  width: fit-content;
  margin-top: auto;
  color: var(--accent);
  text-decoration: none;
}

/* Inline, so on a source that wraps the chevron follows the last word instead
   of standing alone at the far edge of the box. */
a.tool-debate-src::after {
  content: "\203A";
  margin-left: 4px;
  font-size: var(--fs-sm);
  line-height: 1;
}

a.tool-debate-src:hover {
  text-decoration: underline;
}

.tool-debate-kb {
  margin: 0;
  font-size: var(--fs-sm);
}

.tool-debate-kb a {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
}

.tool-debate-kb a::after {
  content: "\203A";
  font-size: var(--fs-md);
  line-height: 1;
}

.tool-debate-kb a:hover {
  text-decoration: underline;
}

@media (max-width: 1100px) {
  .tool-debate-cards {
    grid-template-columns: minmax(0, 1fr);
  }
}

/* A phone: the list becomes a select above the claim, the verdict stacks, and
   the cards run one under another. */
@media (max-width: 760px) {
  .tool-debate {
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
  }

  .tool-debate-groups {
    display: none;
  }

  .tool-debate-select-label {
    display: block;
    font-size: var(--fs-xs);
    font-weight: 700;
    color: var(--muted);
  }

  .tool-debate-select {
    display: block;
    width: 100%;
    min-height: 44px;
    padding: 8px 12px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--control-bg);
    color: var(--text);
    font: inherit;
    font-size: var(--fs-md);
  }

  .tool-debate-list {
    gap: 6px;
  }

  .tool-debate-claim {
    font-size: var(--fs-lg);
  }

  .tool-debate-verdict {
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
  }

  .tool-debate-bar {
    grid-template-columns: minmax(0, 1fr) auto;
  }

  .tool-debate-bar-label {
    grid-column: 1 / -1;
  }
}

/* The arithmetic behind an item: inputs in bold, the result at the end. */
.tool-arith {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 6px 8px;
  border-radius: 8px;
  background: rgba(var(--accent-rgb), 0.06);
  font-variant-numeric: tabular-nums;
}
.tool-arith-line {
  margin: 0;
  color: var(--muted);
}
.tool-arith-note {
  margin: 0;
  color: var(--muted);
}
.tool-arith-list {
  margin: 0;
  padding-left: 16px;
  color: var(--muted);
}
.tool-arith-moves {
  color: var(--accent);
  font-size: var(--fs-xs);
}
.tool-arith-onlyhigh {
  color: var(--muted);
  font-style: italic;
}
.tool-ledger {
  margin-top: 12px;
}
.tool-ledger > summary {
  color: var(--accent);
  text-transform: none;
  letter-spacing: normal;
  font-size: var(--fs-sm);
  font-weight: 600;
}
.tool-ledger-rows {
  display: flex;
  flex-direction: column;
  padding-top: 6px;
}
.tool-ledger-head {
  padding: 8px 0 2px;
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.tool-ledger-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 120px minmax(0, 200px);
  gap: 0 12px;
  padding: 4px 0;
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.tool-ledger-label {
  color: var(--text);
}
.tool-ledger-value {
  text-align: right;
  font-family: var(--font-data);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.tool-ledger-src {
  color: var(--muted);
  font-size: var(--fs-xs);
}
@media (max-width: 600px) {
  .tool-ledger-row {
    grid-template-columns: minmax(0, 1fr) 100px;
  }
  .tool-ledger-src {
    grid-column: 1 / -1;
  }
}
.tool-presets {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}
.tool-presets-label {
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin-right: 4px;
}
.tool-preset {
  appearance: none;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: var(--fs-sm);
  padding: 5px 11px;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.tool-preset:hover {
  border-color: rgba(var(--accent-rgb), 0.6);
}
.tool-preset.is-on {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.12);
  color: var(--accent);
}
.tool-result-actions {
  display: flex;
  gap: 6px;
  flex: none;
  padding-bottom: 4px;
}
.tool-action {
  appearance: none;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  padding: 5px 11px;
  border-radius: 999px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.tool-action:hover {
  color: var(--text);
  border-color: var(--muted);
}
.tool-action.is-primary {
  color: var(--accent);
  border-color: rgba(var(--accent-rgb), 0.5);
}
.tool-action.is-primary:hover,
.tool-action.is-done {
  background: rgba(var(--accent-rgb), 0.12);
  border-color: var(--accent);
}
.tool-group-title {
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 8px 0 0;
}
.tool-chips {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.tool-chip {
  appearance: none;
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  text-align: left;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font: inherit;
  padding: 8px 12px;
  border-radius: 12px;
  cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.tool-chip.is-on {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.1);
}
.tool-chip-mark {
  width: 16px;
  height: 16px;
  flex: none;
  border-radius: 5px;
  border: 1.5px solid var(--muted);
  position: relative;
}
.tool-chip.is-on .tool-chip-mark {
  border-color: var(--accent);
  background: var(--accent);
}
.tool-chip.is-on .tool-chip-mark::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 1px;
  width: 5px;
  height: 9px;
  border: solid #07090e;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.tool-chip-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
}
.tool-chip-label {
  font-weight: 600;
  font-size: var(--fs-sm);
}
.tool-chip-sub {
  display: block;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.tool-check {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 6px 4px;
  cursor: pointer;
}
.tool-check input {
  appearance: none;
  -webkit-appearance: none;
  margin: 2px 0 0;
  width: 16px;
  height: 16px;
  flex: none;
  border-radius: 5px;
  border: 1.5px solid var(--muted);
  background: transparent;
  position: relative;
  cursor: pointer;
}
.tool-check input:checked {
  border-color: var(--accent);
  background: var(--accent);
}
.tool-check input:checked::after {
  content: "";
  position: absolute;
  left: 4px;
  top: 1px;
  width: 5px;
  height: 9px;
  border: solid #07090e;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.tool-check input:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.tool-check-label {
  display: block;
  font-size: var(--fs-sm);
  font-weight: 600;
}
.tool-check-hint {
  display: block;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: var(--lh-ui);
}
.tool-items {
  padding-left: 8px;
  border-left: 2px solid var(--border);
}
.tool-link-btn {
  appearance: none;
  background: none;
  border: none;
  color: var(--accent);
  font: inherit;
  font-size: var(--fs-sm);
  padding: 2px 4px;
  cursor: pointer;
  text-align: left;
}
.tool-link-btn:hover {
  text-decoration: underline;
}
.tool-slider {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.tool-slider-head {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: baseline;
}
.tool-slider-label {
  font-size: var(--fs-sm);
  font-weight: 600;
}
.tool-slider-value {
  font-family: var(--font-data);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}
.tool-slider input[type="range"] {
  width: 100%;
  accent-color: var(--accent);
  height: 28px;
  cursor: pointer;
}
/* Flex, not block: an inline-block input adds a baseline gap under itself, and
   the wrapper would then be taller than the slider it holds. */
.tool-slider-track {
  position: relative;
  display: flex;
}
/* The tick for a value the labels below name. Its left comes from the element,
   which takes the thumb's width out of the percentage. */
.tool-slider-mark {
  position: absolute;
  top: calc(50% - 5px);
  width: 2px;
  height: 10px;
  border-radius: 1px;
  background: rgba(112, 195, 226, 0.5);
  transform: translateX(-50%);
  pointer-events: none;
}
.tool-slider-ends {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  font-size: var(--fs-xs);
  color: var(--muted);
}
/* The flat fill is the one .tool-controls uses. This panel had an accent
   gradient fading out over its top 140px instead, which made the pair read as
   two different kinds of surface when they are the same kind: the inputs on the
   left, what they produce on the right. */
.tool-result {
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 14px 20px 14px;
  min-width: 0;
  background: var(--card-bg);
}
.tool-result:empty {
  display: none;
}
.tool-big {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-bottom: 12px;
}
.tool-big-number {
  font-family: var(--font-data);
  font-size: clamp(1.9rem, 4vw, 2.6rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--accent);
}
.tool-big-caption {
  color: var(--muted);
  font-size: var(--fs-sm);
}
.tool-fact {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  padding: 6px 0;
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.tool-fact-label {
  color: var(--muted);
}
.tool-fact-value {
  font-family: var(--font-data);
  font-weight: 700;
  white-space: nowrap;
}
/* The tools' charts draw at the column's own width (--fit below), so their
   text stays the size it was written at. The scroll is a fallback for a
   drawing that is ever wider than its column. */
.tool-chart {
  margin-top: 14px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.tool-chart-svg {
  display: block;
  width: 100%;
  min-width: 640px;
  max-width: 900px;
  height: auto;
  margin: 0 auto;
}
.tool-chart-svg text {
  font-family: var(--font-data);
}
/* A chart drawn at the column's own width (Tools.elm: lineChart, and the
   bridge, which lies on its side since 2026-09-24) has nothing to scroll, so
   no minimum. */
.tool-chart-svg--fit {
  min-width: 0;
}
.tool-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 6px;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.tool-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.tool-legend-swatch {
  width: 18px;
  height: 3px;
  border-radius: 2px;
}
.tool-legend-swatch.is-dashed {
  background-image: repeating-linear-gradient(to right, transparent 0 3px, var(--bg) 3px 6px) !important;
}
.tool-note {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin: 6px 0 0;
}
.tool-empty {
  color: var(--muted);
}
.tool-method {
  margin: 14px 0 0;
  padding: 12px 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--card-bg);
  color: var(--muted);
  font-size: var(--fs-sm);
}
.tool-method summary {
  cursor: pointer;
  font-weight: 600;
  font-size: var(--fs-sm);
  letter-spacing: normal;
  color: var(--accent);
  line-height: var(--lh-ui);
}
.tool-method[open] summary {
  margin-bottom: 8px;
}
.tool-method ul {
  margin: 0;
  padding-left: 18px;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
}
.tool-method li + li {
  margin-top: 6px;
}
.tool-source {
  margin: 12px 0 0;
}
.tool-source a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid rgba(var(--accent-rgb), 0.5);
  border-radius: 999px;
  color: var(--accent);
  font-weight: 600;
  font-size: var(--fs-sm);
  text-decoration: none;
}
.tool-source a::after {
  content: "\203A";
  font-size: var(--fs-md);
  line-height: 1;
}
.tool-source a:hover {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.08);
}

/* The history contents column. The years are the page, so they are the list:
   one row per decade, opening to the years that carry an entry. */
.history-toc-list,
.history-toc-decades,
.history-toc-years {
  list-style: none;
  margin: 0;
  padding: 0;
}

.history-toc-link {
  display: block;
  padding: 5px 12px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  color: var(--muted);
  text-decoration: none;
  line-height: var(--lh-ui);
  transition: background 0.15s, color 0.15s;
}

.history-toc-link:hover {
  background: var(--panel-soft);
  color: var(--text);
}

.history-toc-link.is-active {
  background: var(--panel-soft);
  color: var(--text);
  font-weight: 600;
}

.history-toc-link--section {
  font-weight: 600;
  color: var(--text);
}

.history-toc-link--decade {
  padding: 6px 12px 4px;
  font-size: var(--fs-xs);
  font-weight: 700;
}

.history-toc-decades {
  margin-top: 4px;
}

.history-toc-link--year {
  padding-left: 22px;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}

/* Only the decade the reader is in shows its years; the rest are one line
   each, which is what keeps 46 years readable as a list. */
.history-toc-decade:not(.is-open) > .history-toc-years {
  display: none;
}

.history-stage {
  flex-direction: column;
  align-items: stretch;
  overflow-y: auto;
  scroll-padding-top: calc(var(--top-bar-height) + 16px);
  padding: 0;
  min-height: 0;
}

@media (max-width: 920px) {
  .knowledge-stage,
  .history-stage {
    padding-top: calc(var(--safe-top) + var(--top-bar-height));
  }
}

.kb-shell {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 0;
  width: 100%;
  max-width: 1200px;
  margin: 0 auto;
  padding: 24px 16px 48px;
  box-sizing: border-box;
}

.kb-toc {
  flex-shrink: 0;
  width: 220px;
  position: sticky;
  top: 24px;
  max-height: calc(100dvh - var(--top-bar-height) - 48px);
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 16px 0 16px 0;
  margin-right: 32px;
  transition: width 0.22s ease, opacity 0.22s ease, margin-right 0.22s ease;
}

/* Desktop: collapse the KB TOC column when the toggle is off. */
.kb-shell .kb-toc:not(.is-open) {
  width: 0;
  margin-right: 0;
  padding-left: 0;
  padding-right: 0;
  opacity: 0;
  pointer-events: none;
  overflow: hidden;
}

.kb-toc-title {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.1em;
  color: var(--muted);
  margin: 0 0 10px 12px;
}

.kb-main-title {
  font-size: var(--fs-2xl);
  font-weight: 700;
  color: var(--text);
  margin: 0 0 8px;
}

.kb-intro {
  max-width: 74ch;
  font-size: var(--fs-md);
  color: var(--text);
  opacity: 0.85;
  margin: 0 0 28px;
  line-height: var(--lh-prose);
}

/* Numeric figures (percentages, counts) highlighted within KB prose. */
.kb-figure {
  color: var(--accent);
  font-weight: 700;
}

.kb-comparisons {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 16px;
  margin-bottom: 32px;
}

.kb-comparisons-header {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 14px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

.kb-comparisons-label {
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--text);
  letter-spacing: 0.02em;
}

.kb-comparisons-sub {
  font-size: var(--fs-sm);
  color: var(--muted-soft);
}

.kb-comparisons-grid {
  column-count: 2;
  column-gap: 32px;
}

.kb-comparison-country {
  border: none;
  background: none;
  border-radius: 0;
  break-inside: avoid;
  display: block;
}

.kb-comparison-country-header {
  cursor: pointer;
  user-select: none;
  outline: none;
  list-style: none;
  padding: 6px 4px;
  display: flex;
  align-items: center;
  gap: 4px;
  transition: opacity 0.2s;
}

.kb-comparison-country-header::before {
  content: "▶";
  font-size: var(--fs-xs);
  color: var(--muted);
  opacity: 0.7;
  transition: transform 0.15s;
  transform-origin: center;
  flex-shrink: 0;
}

.kb-comparison-country[open] .kb-comparison-country-header::before {
  transform: rotate(90deg);
}

.kb-comparison-country-header::-webkit-details-marker {
  display: none;
}

.kb-comparison-country-header:hover {
  opacity: 0.8;
}

.kb-comparison-country-header:hover .kb-comparison-country-name {
  color: var(--accent);
}

.kb-comparison-country-name {
  font-size: var(--fs-sm);
  font-weight: 500;
  text-transform: capitalize;
  color: var(--text);
  opacity: 0.75;
  vertical-align: middle;
  display: inline-block;
}

.kb-comparison-entries {
  padding-left: 24px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.kb-comparison-entry {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 8px 12px;
}

.kb-comparison-entry-row {
  display: flex;
  align-items: baseline;
  gap: 8px;
  flex-wrap: wrap;
}

.kb-comparison-link {
  font-size: var(--fs-md);
  font-weight: 500;
  color: var(--text);
  text-decoration: none;
}

.kb-comparison-link:hover {
  color: var(--accent);
  text-decoration: underline;
}

.kb-comparison-toggle {
  font-size: var(--fs-xs);
  color: var(--muted-soft);
  cursor: pointer;
  background: none;
  border: none;
  padding: 5px 0;
  font-family: inherit;
  white-space: nowrap;
  flex-shrink: 0;
  transition: opacity 0.15s;
}

/* The caret lives here rather than in the label text, so it can turn when the
   entry opens. As a character inside the button's text node it could not be
   rotated, and the only open-state feedback was an opacity step from 0.5 to
   0.8 — too slight to read as a state change. */
.kb-comparison-toggle::before {
  content: "▸";
  display: inline-block;
  margin-right: 4px;
  transition: transform 0.15s ease;
}

.kb-comparison-toggle.is-open::before {
  transform: rotate(90deg);
}

.kb-comparison-toggle:hover {
  color: var(--text);
}

.kb-comparison-toggle.is-open {
  opacity: 0.8;
}

.kb-comparison-desc {
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: var(--lh-prose);
  margin: 2px 0 2px;
  padding-left: 8px;
  border-left: 2px solid var(--border);
  opacity: 0.85;
}

.kb-toc-list {
  list-style: none;
  margin: 0;
  padding: 0;
}

.kb-toc-link {
  display: block;
  padding: 6px 12px;
  border-radius: var(--radius-sm);
  font-size: var(--fs-sm);
  color: var(--muted);
  text-decoration: none;
  transition: background 0.15s, color 0.15s;
  line-height: var(--lh-ui);
}

.kb-toc-link:hover {
  background: var(--panel-soft);
  color: var(--text);
}

.kb-toc-link.is-active {
  background: var(--panel-soft);
  color: var(--text);
  font-weight: 600;
}

.kb-toc-group-item {
  margin-top: 6px;
}

/* ── The contents column as the place to narrow the page ─────────────────
   Search box, then the chips and the data-only toggle, then the groups folded
   to a label and a count. Only the group the reader is in shows its sections,
   and the one they are on carries a bar on its left edge. */
.kb-toc-search {
  display: block;
  width: 100%;
  box-sizing: border-box;
  margin: 0 0 18px;
  padding: 8px 14px;
  font: inherit;
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--text);
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  outline: none;
  transition: border-color 0.15s ease;
}

.kb-toc-search::placeholder {
  color: var(--muted);
}

.kb-toc-search:focus {
  border-color: var(--accent);
}

.kb-toc-search::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* In the column the chips wrap tight, and the data-only toggle takes a row of
   its own above them: a vertical rule between two groups of chips has nothing
   to separate once the row wraps. */
.kb-toc .kb-theme-filter {
  gap: 6px;
  margin: 0 0 18px;
}

.kb-toc .kb-theme-chip {
  font-size: var(--fs-xs);
  padding: 4px 10px;
}

/* Its own row above the chips, and not a smaller sibling of them: it answers a
   different question (what is shown, not which topic), so the label is a step
   up from the chips' 11px rather than matching it. The chip padding is gone
   with the pill -- a switch that pretends to be a chip is the thing this row
   was getting wrong. */
.kb-toc .kb-data-mode-btn {
  flex-basis: 100%;
  margin: 2px 0 6px;
}

.kb-toc .kb-data-mode-btn .ui-switch-label {
  font-size: var(--fs-sm);
  font-weight: 560;
}

.kb-toc .kb-filter-divider {
  display: none;
}

.kb-toc-group-label {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
}

.kb-toc-count {
  flex: none;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.kb-toc-empty {
  margin: 8px 12px 0;
  font-size: var(--fs-sm);
  color: var(--muted);
}

a.kb-toc-group-label {
  text-decoration: none;
  border-radius: var(--radius-sm);
}

a.kb-toc-group-label:hover {
  color: var(--text);
  background: var(--panel-soft);
}

.kb-toc-sublist {
  list-style: none;
  margin: 0;
  padding: 0;
}

.kb-toc-sublist {
  margin: 2px 0 8px;
}

.kb-toc-sublink {
  margin-left: 12px;
  padding: 5px 12px 5px 14px;
  border-left: 2px solid transparent;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
  font-size: var(--fs-sm);
}

/* The row the reader is on: a bar and the accent, not a pill. A filled pill on
   a sub-row read as a button rather than as a position in a list. */
.kb-toc-sublink.is-active {
  border-left-color: var(--accent);
  color: var(--accent);
  background: transparent;
  font-weight: 600;
}

.kb-group {
  margin-bottom: 12px;
}

.kb-group-title {
  font-size: var(--fs-xl);
  font-weight: 800;
  color: var(--text);
  margin: 40px 0 20px;
  padding-bottom: 12px;
  border-bottom: 2px solid var(--border);
  /* A long compound is an anonymous flex item beside the share button and
     will not shrink below its longest word; without this the button was
     pushed 9px past the stage at 390px. */
  overflow-wrap: anywhere;
}

.kb-content {
  flex: 1;
  min-width: 0;
  /* Caps every child at the same reading width as the prose below, so a
     chart or a card never runs wider than the text around it. */
  max-width: 74ch;
}

.kb-section {
  margin-bottom: 48px;
}

/* Copy-link control on knowledge-base headings. The heading becomes a flex row
   so the button can sit at its right edge and stay there however the title
   wraps; the title text is the anonymous flex item beside it.

   Hidden until the heading is hovered or the button itself is focused, so 38 of
   these do not stipple the page -- but always visible to a keyboard, which is
   the case that a hover-only control would lock out. */
.kb-section-title,
.kb-group-title {
  display: flex;
  /* The tags drop to a second line when the title fills the first; without
     this a 20 px title beside two tags broke mid-word at 390 px. */
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
}

.kb-share-btn {
  margin-left: auto;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.kb-share-btn svg {
  width: 15px;
  height: 15px;
}

.kb-section-title:hover .kb-share-btn,
.kb-group-title:hover .kb-share-btn,
.kb-article:hover .kb-share-btn,
.kb-chart-anchor:hover > .kb-share-btn,
.kb-entry-header:hover .kb-share-btn,
.kb-share-btn:focus-visible {
  opacity: 1;
}

/* A chart's share button is a sibling of the chart card, not a child of its
   title row, because the anchor wraps the card and renderKbChart knows nothing
   of anchors. In the flow it sat above the card as a line of its own and pushed
   every chart 46px down. Out of the flow it takes the slot just left of the
   download-image button, which every chart has and which sits 17px in from the
   card's top and right edges at every width: 17 + 26 for that button + the
   title row's 8px gap. */
.kb-chart-anchor {
  position: relative;
}

.kb-chart-anchor > .kb-share-btn {
  position: absolute;
  top: 17px;
  right: 51px;
  z-index: 1;
  margin: 0;
}

/* In a survey box the button rides inside the title, after the text, and must
   not stretch the header row the way margin-left: auto would. */
.kb-entry-title .kb-share-btn {
  margin-left: 6px;
  vertical-align: middle;
}

/* In an article card it ends the source line. Above the title it was a row of
   its own in the column-flex header, an invisible 26px strip at the card's top.
   The negative margins keep the source line at the height of its text. */
.kb-article-meta .kb-share-btn {
  margin: -4px 0 -4px auto;
}

.kb-share-btn:hover {
  color: var(--accent);
  border-color: rgba(112, 195, 226, 0.3);
}

.kb-share-btn.is-copied {
  opacity: 1;
  color: var(--accent);
  border-color: var(--accent);
}

/* Download-as-image, on the chart types that draw an SVG. It behaves like the
   copy-link button above and sits in the same place, but it belongs to the
   chart rather than the heading, so it reveals on hover anywhere over the
   chart card -- the title alone is a thin target above a large drawing. */
/* The chart SVGs draw in a fixed box (660 units wide) and scale to the column, so
   on a wide screen the plot went flat and a 10-unit label reached 17 px. 960 px is
   where the label is 14.5 px and the multi-line plot 436 px tall. */
.kb-chart-svg-wrap svg {
  max-width: 960px;
}

.kb-chart-title {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}

.kb-chart-image-btn {
  margin-left: auto;
  flex: 0 0 auto;
  width: 26px;
  height: 26px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  background: none;
  color: var(--muted);
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}

.kb-chart-image-btn svg {
  width: 15px;
  height: 15px;
}

.kb-chart:hover .kb-chart-image-btn,
.kb-chart-image-btn:focus-visible {
  opacity: 1;
}

.kb-chart-image-btn:hover {
  color: var(--accent);
  border-color: rgba(112, 195, 226, 0.3);
}

/* Busy while the canvas is being composed. The export lifts the chart into an
   off-screen stage for a moment, so the button is the only thing on screen
   that can say something is happening. */
.kb-chart-image-btn.is-busy {
  opacity: 1;
  color: var(--accent);
  cursor: progress;
}

/* Both of the knowledge base's per-item buttons hide at opacity 0 until their card
   is hovered. A coarse pointer has no hover to give, so without this they are laid
   out, focusable and permanently invisible. The image button was rescued here when
   the pattern was written and the share button was not, which left every section's
   copy-link control unusable on a phone. */
@media (hover: none) {
  .kb-chart-image-btn,
  .kb-share-btn { opacity: 0.55; }
  body.light-theme .kb-chart-image-btn,
  body.light-theme .kb-share-btn { opacity: 0.75; }
}

.kb-section-title {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--accent);
  margin: 0 0 20px;
  padding-bottom: 10px;
  border-bottom: 1px solid var(--border);
  /* A long compound is an anonymous flex item beside the share button and
     will not shrink below its longest word; without this the button was
     pushed 9px past the stage at 390px. */
  overflow-wrap: anywhere;
}

/* Demoted section heading (smallTitle: true) — a list caption, not a subtitle */
.kb-section-title--small {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text);
  border-bottom: none;
  padding-bottom: 0;
  margin: 0 0 12px;
}

.kb-country {
  margin-bottom: 32px;
}

/* A country block whose name heading is hidden (hideName, or a repeat of the
   country above) reads as a plain continuation of the text, so the 32px block
   separator lands as a stray gap on top of a chart card while the card below
   keeps the 20px chart rhythm. Tighten that one boundary to the same 20px. */
.kb-country:has(+ .kb-country-continued) {
  margin-bottom: 20px;
}

.kb-subsection-title {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--muted);
  margin: 20px 0 8px;
}

.kb-cases-scroll {
  max-height: 480px;
  overflow-y: auto;
  padding-right: 4px;
}

.kb-origin-title {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted-soft);
  margin: 14px 0 6px;
}

/* The chip row above the content. It changes what the page holds, so it sits
   before the first section rather than in the contents column beside it. */
.kb-theme-filter {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 8px;
  margin: 0 0 30px;
}

.kb-theme-chip {
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 6px 13px;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}

.kb-theme-chip:hover {
  color: var(--text);
  border-color: var(--accent);
}

.kb-theme-chip.is-active {
  color: var(--bg);
  background: var(--accent);
  border-color: var(--accent);
}

@media (prefers-reduced-motion: reduce) {
  .kb-theme-chip { transition: none; }
}

/* The cross-cutting themes, under the section title. Quiet on purpose: they are
   a way of finding the section, not a claim it makes, so they read as metadata
   beside the title rather than as the first thing in the argument. */
.kb-theme-tags {
  /* Inside the heading now, between the title and the share button: a tag is a
     way of finding the section, so it belongs to the heading rather than to the
     lead it used to sit above. A section whose title collapses into its group
     heading has no heading of its own, and its tags used to land between the
     group lead and the section lead, reading as if they were part of the
     argument. No margin of its own for that reason -- the heading's own flex
     gap does the spacing. */
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  margin: 0;
}

.kb-theme-tag {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 3px 9px;
  white-space: nowrap;
}

.kb-section-lead,
.kb-group-lead {
  max-width: 74ch;
  font-size: var(--fs-md);
  color: var(--text);
  line-height: var(--lh-prose);
  margin: -8px 0 20px;
}

/* Inline [label](url) links inside lead text — a lead can carry its own source
   without a separate article box. Rendered by highlightFigures/linkNode in
   src/Knowledge.elm and escapeWithPercentages in prerender_static_views.mjs. */
/* Inline links in leads carry the accent colour: at inherit they read as body
   text with a faint underline, and people report the link as missing. */
.kb-inline-link {
  color: var(--accent);
  text-decoration: underline;
  text-decoration-color: rgba(var(--accent-rgb), 0.55);
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  transition: color 0.15s ease, text-decoration-color 0.15s ease;
}

.kb-inline-link:hover,
.kb-inline-link:focus-visible {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

/* Percentage figures highlighted in the page's cyan accent throughout the KB */
.kb-pct {
  color: var(--accent);
  font-weight: 700;
}

/* ── Key-fact strips and group overviews ─────────────────────────────────
   A section opens with up to three of the site's own facts before the lead,
   and a multi-section group with one line per section. The figures light up
   the way they do in the leads (highlightFigures / escapeWithPercentages). */
.kb-keyfacts,
.kb-group-overview {
  margin: 0 0 18px;
  padding: 12px 14px 12px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
  background: var(--panel-bg);
}

.kb-keyfacts-label {
  display: block;
  margin: 0 0 8px;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}

.kb-keyfacts-list,
.kb-group-overview-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 8px 20px;
}

@media (min-width: 720px) {
  .kb-keyfacts-list {
    grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  }
}

.kb-keyfact,
.kb-group-overview-fact {
  font-family: var(--font-data);
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--text);
}

.kb-group-overview-item {
  display: grid;
  gap: 2px;
}

.kb-group-overview-link {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--accent);
  text-decoration: none;
}

.kb-group-overview-link:hover {
  text-decoration: underline;
}

.kb-group-overview-fact {
  color: var(--muted);
}

.kb-section-note {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin: -12px 0 16px;
}

/* Prominent "open the related chart" button shown under a section's chart. */
.kb-chart-link-row {
  margin: 16px 0 28px;
}

.kb-chart-link-btn {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 9px 16px;
  border-radius: var(--radius-md);
  border: 1px solid var(--accent);
  background: rgba(112, 195, 226, 0.12);
  color: var(--accent);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  transition: background 0.15s ease;
}

.kb-chart-link-btn::after {
  content: "→";
  font-size: var(--fs-md);
  line-height: 1;
}

.kb-chart-link-btn:hover {
  background: rgba(112, 195, 226, 0.22);
}

.kb-article-group {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 14px 16px 8px;
  /* The group used to set its rhythm with margin-bottom alone, with a top
     margin scoped to `.kb-articles + .kb-article-group`. That covered one
     neighbour and left the box touching every other: under the folded articles
     disclosure the two boxes met edge to edge. Adjacent margins collapse, so a
     plain top margin is a floor rather than an addition, and it holds whatever
     comes before. Two groups in a row keep their tighter 10px. */
  margin: 14px 0 10px;
}

.kb-article-group + .kb-article-group {
  margin-top: 10px;
}

.kb-article-group-title {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text);
  line-height: var(--lh-ui);
  margin-bottom: 8px;
}

.kb-article-group-summary {
  font-size: var(--fs-sm);
  color: color-mix(in srgb, var(--text) 80%, transparent);
  line-height: var(--lh-prose);
  margin: 0 0 12px;
  border-left: 3px solid color-mix(in srgb, var(--accent-dim, #6a9fb5) 80%, transparent);
  padding-left: 10px;
}

/* Highlighted key-point bullets summarising a group's central facts */
.kb-article-group-points {
  list-style: none;
  margin: 0 0 14px;
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  background: rgba(112, 195, 226, 0.05);
  border: 1px solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--radius-sm);
}

.kb-article-group-points li {
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: var(--lh-prose);
  padding-left: 18px;
  position: relative;
}

.kb-article-group-points li::before {
  content: "▸";
  position: absolute;
  left: 0;
  color: var(--accent);
  font-weight: 700;
}

.kb-article-group .kb-articles {
  gap: 10px;
}

/* kb-article cards inside a group now use the same outlined style as standalone cards */

.kb-subgroup-title {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  margin: 14px 0 6px;
  padding-top: 10px;
  border-top: 1px solid var(--border, rgba(255,255,255,0.08));
  letter-spacing: 0.01em;
}

.kb-article-group > .kb-subgroup-title:first-of-type {
  margin-top: 4px;
}

/* removed: no longer needed after unifying card style */

.kb-section-note a {
  color: var(--accent-dim);
  text-decoration: none;
}

.kb-section-note a:hover {
  color: var(--accent);
  text-decoration: underline;
}

@keyframes kb-bar-grow {
  from { transform: scaleY(0); }
  to   { transform: scaleY(1); }
}
@keyframes kb-label-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
.kb-chart-bar {
  transform-box: fill-box;
  transform-origin: center bottom;
  animation: kb-bar-grow 0.38s cubic-bezier(0.2, 0, 0.35, 1) both;
}
.kb-chart-label {
  animation: kb-label-fade 0.2s ease-out both;
}
.kb-chart {
  margin: 1.25rem 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 16px;
  box-shadow: var(--shadow-sm);
}
.kb-chart-head {
  display: flex;
  align-items: start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 0.65rem;
}
.kb-chart-title {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text);
  margin-bottom: 0.65rem;
  line-height: var(--lh-ui);
}
.kb-chart-head .kb-chart-title {
  margin-bottom: 0;
}
.kb-pew-select {
  -webkit-appearance: none;
  -moz-appearance: none;
  appearance: none;
  min-width: 200px;
  max-width: 100%;
  min-height: 36px;
  padding: 0 36px 0 16px;
  border: 1px solid var(--control-border);
  border-radius: var(--radius-md);
  background-color: var(--control-bg);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239fb2c4' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 14px 14px;
  color: var(--text);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.04em;
  line-height: 1.2;
  cursor: pointer;
  transition: border-color 0.18s ease, background-color 0.18s ease, box-shadow 0.18s ease, color 0.18s ease;
}

.kb-pew-select:hover,
.kb-pew-select:focus,
.kb-pew-select:focus-visible {
  border-color: var(--control-hover-border);
  background-color: var(--control-hover-bg);
  box-shadow: var(--control-shadow-ring);
  outline: none;
}

.kb-pew-select option {
  background: #0b1018;
  color: var(--text);
  font-weight: 500;
}

/* Elm-rendered KB dropdowns reuse the site-wide custom-dropdown component;
   this wrapper just gives it the old select's footprint inside the flex rows
   (.kb-metric-select-row, .kb-chart-head). The .kb-pew-select rules above
   remain for the static prerender fallback shown before Elm mounts. */
.kb-dropdown {
  flex: 0 1 auto;
  min-width: 200px;
  max-width: 100%;
}
.kb-metric-select-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  flex-wrap: wrap;
}
.kb-metric-select-label {
  font-size: var(--fs-sm);
  color: var(--muted);
  font-weight: 600;
}
.kb-pew-svg-wrap,
.kb-consang-svg-wrap,
.kb-migmap-svg-wrap {
  overflow: hidden;
  border: 1px solid var(--kb-migmap-border);
  border-radius: var(--radius-sm);
  background:
    radial-gradient(circle at 30% 18%, var(--kb-migmap-bg-radial), transparent 34%),
    linear-gradient(180deg, var(--kb-migmap-bg-1), var(--kb-migmap-bg-2));
}
.kb-pew-svg-wrap svg,
.kb-consang-svg-wrap svg,
.kb-migmap-svg-wrap svg {
  display: block;
}
.kb-consang-count {
  align-self: center;
  border: 1px solid var(--kb-migmap-border);
  border-radius: var(--radius-pill);
  color: var(--muted);
  flex: 0 0 auto;
  font-size: var(--fs-xs);
  font-weight: 650;
  line-height: 1.2;
  padding: 0.35rem 0.55rem;
}
.kb-pew-country {
  cursor: pointer;
}
.kb-pew-country path {
  transition: filter 0.12s ease, stroke-width 0.12s ease;
}
.kb-pew-country:hover path {
  filter: brightness(1.18);
  stroke-width: 1.3px;
}

.kb-pew-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 12px;
  margin-top: 0.65rem;
  color: var(--muted);
  font-size: var(--fs-xs);
}
.kb-pew-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.kb-pew-legend-swatch {
  width: 12px;
  height: 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  flex: 0 0 auto;
}
.kb-chart-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 14px;
  margin-top: 0.5rem;
  font-size: var(--fs-xs);
  color: var(--text);
}
.kb-chart-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.kb-chart-legend-bar {
  width: 12px;
  height: 12px;
  border-radius: var(--radius-xs);
  background: var(--accent);
  opacity: 0.85;
}
.kb-chart-legend-bar-a {
  background: rgba(176, 196, 214, 0.7);
  opacity: 1;
}
.kb-chart-legend-bar-b {
  background: rgba(112, 195, 226, 0.82);
  opacity: 1;
}
.kb-chart-legend-line {
  width: 16px;
  height: 0;
  border-top: 2.5px solid var(--accent-hot);
  position: relative;
}
/* A real element rather than ::after. A pseudo-element has no box that
   getBoundingClientRect can return, so the chart image export -- which
   transcribes backgrounds and text -- had no way to see this dot and the
   legend's line mark came out blank in every shared picture. */
.kb-chart-legend-line-dot {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent-hot);
  transform: translate(-50%, -50%);
}
.kb-chart-intro {
  /* Sits inside the chart card right under the title; both renderers place it
     there. A titleless chart falls back to this same style as a plain
     paragraph above its card. */
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: var(--lh-prose);
  margin: 2px 0 12px;
}
.kb-chart-caption {
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
  margin-top: 0.4rem;
  opacity: 0.8;
}
.kb-catbar-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin: 0.25rem 0 0.5rem;
}
.kb-catbar-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  color: var(--muted);
}
.kb-catbar-legend-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}
.kb-chart-caption a {
  color: var(--muted);
  text-decoration: none;
}
.kb-chart-caption a:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* ── KB stat tiles (compact number comparison, not a bar chart) ── */
.kb-stat-tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0.35rem 0 0.15rem;
}
.kb-stat-tile {
  position: relative;
  flex: 1 1 160px;
  min-width: 140px;
  display: flex;
  flex-direction: column;
  padding: 20px 16px 15px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-soft);
  overflow: hidden;
}
.kb-stat-tile-accent {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
}
.kb-stat-tile-value {
  font-size: 2.5rem;
  font-weight: 700;
  line-height: 1.05;
  color: var(--text);
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}
.kb-stat-tile-unit {
  margin-left: 4px;
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--muted);
}
.kb-stat-tile-label {
  margin-top: 8px;
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--muted);
}
.kb-stat-tiles-note {
  margin-top: 0.5rem;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
  opacity: 0.9;
}

/* ── KB check matrix (method scorecard: one row per estimate) ── */
/* ── Check matrix: a grid of cards ───────────────────────────────────────────
   Each row is one card. A card that has workings is a details element, so the
   whole row is the control, the chevron at its right edge turns when it opens,
   and none of it needs JavaScript. */
.kb-matrix-grid {
  --kb-m-cols: 4;
  display: flex;
  flex-direction: column;
  gap: 6px;
  font-size: var(--fs-sm);
}
.kb-m-head,
.kb-m-row {
  display: grid;
  grid-template-columns: minmax(170px, 1.5fr) minmax(120px, 0.9fr) repeat(var(--kb-m-cols), minmax(48px, 0.7fr)) 24px;
  align-items: center;
  gap: 0 8px;
}
.kb-m-head {
  padding: 0 8px 4px 16px;
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1.2;
  color: var(--muted);
}
.kb-m-head .kb-m-col {
  text-align: center;
}
.kb-m-card {
  position: relative;
  background: var(--panel-soft);
  border-radius: 8px;
}
.kb-m-row {
  padding: 9px 8px 9px 16px;
}
summary.kb-m-row {
  cursor: pointer;
  list-style: none;
  /* The global "details summary" rule is written for small tracked captions.
     A card row is body text and has to opt out of both. */
  font-size: var(--fs-sm);
  letter-spacing: normal;
}
summary.kb-m-row::-webkit-details-marker {
  display: none;
}
summary.kb-m-row:hover .kb-matrix-label {
  color: var(--accent);
}
.kb-m-tier {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 4px;
  border-radius: 8px 0 0 8px;
}
.kb-m-name {
  min-width: 0;
  overflow-wrap: anywhere;
}
.kb-m-col {
  min-width: 0;
  text-align: center;
}
/* The disclosure lives in the row's own last column, so it reads as part of
   the card rather than a control parked underneath it. */
/* Only a row that actually opens carries an arrow. */
.kb-m-chev,
.kb-work-chev {
  justify-self: end;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  color: var(--muted);
  font-size: var(--fs-xs);
  letter-spacing: normal;
  line-height: 1;
}
.kb-work-chev {
  width: 20px;
  height: 20px;
  font-size: var(--fs-xs);
}
summary.kb-m-row .kb-m-chev,
summary.kb-work-row .kb-work-chev {
  color: var(--text);
}
summary.kb-m-row .kb-m-chev::before {
  content: "▸";
}
details.kb-m-card[open] > summary .kb-m-chev::before {
  content: "▾";
}
summary.kb-m-row:hover .kb-m-chev,
summary.kb-work-row:hover .kb-work-chev {
  color: var(--accent);
}
summary.kb-work-row .kb-work-chev::before {
  content: "▸";
}
details.kb-work-item[open] > summary .kb-work-chev::before {
  content: "▾";
}
/* The card's sources, gathered from its steps and set under a rule at the foot
   of the open body. A rule rather than a gap because the list is provenance,
   not one more step in the arithmetic above it. */
.kb-m-sources {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.kb-m-sources-title {
  display: block;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--text);
  margin-bottom: 5px;
}

.kb-m-source-list {
  margin: 0;
  padding-left: 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
}

.kb-m-body {
  padding: 0 12px 10px 16px;
}
/* Sets one block of rows apart from another, so figures of different kinds
   are not read as one list. */
.kb-m-divider {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 10px 0 2px;
  font-size: var(--fs-xs);
  font-weight: 600;
  line-height: 1;
  color: var(--muted);
}
/* A real element rather than ::after, for the same reason as the legend dot
   above: the export cannot measure a pseudo-element, so this rule vanished
   from every exported matrix chart. */
.kb-m-divider-rule {
  flex: 1;
  height: 1px;
  background: var(--border);
}
.kb-matrix-label {
  display: block;
  font-weight: 600;
  line-height: 1.25;
  color: var(--text);
}
.kb-matrix-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-sm);
  line-height: 1.3;
  color: var(--muted);
}
.kb-matrix-value {
  min-width: 150px;
  font-weight: 700;
  line-height: 1.3;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
/* The quality tag sits above the number, quiet enough not to compete with it
   and loud enough that the reader cannot take a derived figure for a measured
   one. */
.kb-work-list {
  margin-top: 6px;
  font-size: var(--fs-sm);
  font-variant-numeric: tabular-nums;
}
/* The global details rule paints every details element as a bordered card.
   These are rows inside a card, so they take neither its border nor its
   background, only the separator. */
details.kb-m-card,
details.kb-work-item {
  border: 0;
}
details.kb-m-card {
  background: var(--panel-soft);
}
details.kb-work-item {
  border-radius: 0;
  background: transparent;
  color: inherit;
  font-size: inherit;
}
.kb-work-item {
  border-top: 0;
}
.kb-work-row {
  display: grid;
  grid-template-columns: 1fr auto 24px;
  gap: 0 10px;
  align-items: center;
  padding: 4px 4px 4px 8px;
}
/* The sum used to be set apart by weight and a gap alone. In a list of nine or
   ten steps that was not enough: the total read as one more step that happened
   to be bold. A rule above it is what a column of figures being added up looks
   like everywhere else, so it gets one, with the room a rule needs on both
   sides of it. */
.kb-work-row.is-total {
  margin-top: 8px;
  padding-top: 9px;
  border-top: 1px solid var(--border);
}
summary.kb-work-row {
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-sm);
  letter-spacing: normal;
}
summary.kb-work-row::-webkit-details-marker {
  display: none;
}
.kb-work-label {
  min-width: 0;
  color: var(--muted);
  line-height: var(--lh-ui);
}
.kb-work-value {
  text-align: right;
  white-space: nowrap;
  color: var(--text);
  font-weight: 600;
}
.kb-work-row.is-total .kb-work-label,
.kb-work-row.is-total .kb-work-value {
  color: var(--text);
  font-weight: 700;
}
/* Indented and italic, so a note reads as a footnote to the figure above it
   rather than as another line of the calculation. */
.kb-work-note {
  padding: 0 34px 9px 24px;
  font-size: var(--fs-xs);
  font-style: italic;
  line-height: var(--lh-prose);
  color: var(--muted);
}
/* Where the note's numbers come from, on its own line and upright, so it does
   not read as part of the sentence above it. */
.kb-work-src {
  display: block;
  margin-top: 3px;
  font-style: normal;
  font-size: var(--fs-xs);
  color: var(--muted-soft);
}
.kb-matrix-kind {
  display: block;
  margin-bottom: 3px;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.01em;
  color: var(--muted);
}
.kb-matrix-cell {
  text-align: center;
}
.kb-matrix-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  font-size: var(--fs-md);
  font-weight: 700;
  line-height: 1;
}
.kb-matrix-mark.is-yes {
  background: rgba(74, 222, 128, 0.18);
  color: #3fbf6f;
}
.kb-matrix-mark.is-part {
  background: rgba(250, 204, 21, 0.2);
  color: #d4a017;
}
.kb-matrix-mark.is-no {
  background: rgba(248, 113, 113, 0.16);
  color: #f08a88;
}
/* The marks' own colours read at 1.9-2.7:1 on their light tints. */
body.light-theme .kb-matrix-mark.is-yes { color: #1a6b3c; }
body.light-theme .kb-matrix-mark.is-part { color: #735300; }
body.light-theme .kb-matrix-mark.is-no { color: #b3261e; }
.kb-matrix-mark.is-na {
  background: rgba(148, 163, 184, 0.14);
  color: var(--muted);
}
.kb-matrix-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 16px;
  margin-top: 0.55rem;
  font-size: var(--fs-sm);
  color: var(--muted);
}
.kb-matrix-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.kb-matrix-legend-dot {
  width: 10px;
  height: 10px;
  border-radius: var(--radius-xs);
}
.kb-matrix-note {
  margin-top: 0.45rem;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
}
.kb-matrix-value-inline {
  display: none;
}
/* Phone: the figure moves under the name so the six mark columns fit the
   width without a sideways scroll, and the column heads turn vertical. */
@media (max-width: 640px) {
  .kb-m-head,
  .kb-m-row {
    grid-template-columns: 1fr repeat(var(--kb-m-cols), minmax(30px, auto)) 24px;
    gap: 0 5px;
  }
  .kb-m-head .kb-m-value,
  .kb-m-row > .kb-m-value {
    display: none;
  }
  .kb-matrix-value-inline {
    display: block;
    margin-top: 3px;
    font-weight: 700;
    color: var(--text);
  }
  .kb-matrix-value-inline .kb-matrix-kind {
    display: inline;
    margin-right: 5px;
    font-weight: 600;
  }
  .kb-m-head .kb-m-col {
    writing-mode: vertical-rl;
    transform: rotate(180deg);
    justify-self: center;
    text-align: left;
    line-height: 1.15;
  }
  .kb-m-row {
    padding: 8px 6px 8px 13px;
  }
  .kb-m-body {
    padding: 0 8px 9px 13px;
  }
}

/* ── KB ranked horizontal bar chart (rank-hbar) ── */
/* Toggle: absolute (per 100k) ↔ relative to the native baseline (× natives) */
.kb-rank-toggle-btn {
  align-self: flex-start;
  margin: 2px 0 8px;
  padding: 5px 11px;
  border: 1px solid var(--control-border);
  border-radius: var(--radius-pill);
  background: var(--control-bg);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.kb-rank-toggle-btn:hover,
.kb-rank-toggle-btn:focus-visible {
  background: var(--control-hover-bg);
  border-color: var(--control-hover-border);
  color: var(--text);
  outline: none;
}
.kb-rank-toggle-btn[aria-pressed="true"] {
  background: rgba(112, 195, 226, 0.11);
  border-color: rgba(112, 195, 226, 0.26);
  color: var(--accent);
}

.kb-rank-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 0.2rem;
}
.kb-rank-row {
  display: grid;
  grid-template-columns: 150px 1fr 46px;
  align-items: center;
  gap: 10px;
}

/* A comparison marker that moves row by row gets its own value column, because
   a tick on a track is a position and not a number a reader can take away. The
   gap drops to 8px so the fourth column costs the track only 40px. */
.kb-rank-rows.has-from .kb-rank-row {
  grid-template-columns: 150px 1fr 40px 46px;
  gap: 8px;
}

/* The header is a row of the same grid, so the two labels sit exactly over the
   columns they name rather than being guessed at from a caption. */
.kb-rank-head {
  margin-bottom: 2px;
}

.kb-rank-head .kb-rank-fromval,
.kb-rank-head .kb-rank-val {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  letter-spacing: 0.02em;
}

.kb-rank-fromval {
  font-size: var(--fs-xs);
  color: var(--muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The other case: one reference level shared by every row. Repeating it in
   twenty-one rows would be twenty-one copies of one number, so it is said once
   and the swatch ties it to the tick drawn on the bars. The swatch deliberately
   repeats .kb-rank-from's paint verbatim, including its lack of a light-theme
   override: a legend that is a different colour from the mark it names is worse
   than one that inherits the mark's own problem. */
.kb-rank-ref {
  display: flex;
  align-items: center;
  gap: 6px;
  margin: 0 0 6px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.kb-rank-ref-mark {
  display: inline-block;
  width: 2px;
  height: 11px;
  background: rgba(255, 255, 255, 0.82);
  box-shadow: 0 0 0 1px rgba(8, 12, 18, 0.55);
  flex: none;
}

.kb-rank-name {
  font-size: var(--fs-xs);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.kb-rank-track {
  position: relative;
  height: 11px;
  background: rgba(150, 214, 255, 0.06);
  border-radius: var(--radius-xs);
  overflow: hidden;
}
/* Marker for the earlier-year value on a before/after rank bar (gap to the bar
   end shows the change). */
.kb-rank-from {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: rgba(255, 255, 255, 0.82);
  box-shadow: 0 0 0 1px rgba(8, 12, 18, 0.55);
}
.kb-rank-bar {
  height: 100%;
  border-radius: var(--radius-xs);
  background: var(--muted);
  opacity: 0.5;
  transition: width 0.4s cubic-bezier(0.2, 0, 0.35, 1);
}
.kb-rank-bar.is-hot {
  background: var(--accent-hot);
  opacity: 0.9;
}
.kb-rank-bar.is-accent {
  background: var(--accent);
  opacity: 0.85;
}
.kb-rank-val {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.kb-rank-val.is-hot {
  color: var(--accent-hot);
}
.kb-rank-val.is-accent {
  color: var(--accent);
}
@media (max-width: 560px) {
  .kb-rank-row {
    grid-template-columns: 104px 1fr 38px;
    gap: 8px;
  }

  /* Only the four-column before/after variant scrolls. It never got the
     narrowing above, because `.kb-rank-rows.has-from .kb-rank-row` outranks a
     bare `.kb-rank-row`, so it kept the desktop widths and the track took
     whatever was left: measured, 72px at a 390px viewport and 42px at 360px. At
     that length a bar cannot show the change it exists to show, and the marker
     for the earlier year sits almost on top of the bar end.

     The rows keep a width they can be read at and the list scrolls instead,
     which is what the maps in this file already do rather than shrink further.
     Only the rows scroll, so the title, caption and caveats around them stay
     put. The other ten ranked charts are left alone: their three-column rows
     fit at 174px of track, and giving them a scrollbar they did not need would
     be a worse trade than the one being fixed here. */
  .kb-rank-rows.has-from {
    overflow-x: auto;
    overscroll-behavior-x: contain;
  }

  .kb-rank-rows.has-from .kb-rank-row {
    grid-template-columns: 116px 1fr 40px 44px;
    gap: 8px;
    min-width: 470px;
  }

  /* Both ends are pinned and only the track scrolls between them, so the row
     never loses the two things it is read for: which country, and where it is
     now. The earlier year's number scrolls, because that value is also drawn on
     the track as the marker. Without this the reader scrolls a row of
     unlabelled bars and has to scroll back to find out whose they were. */
  .kb-rank-rows.has-from .kb-rank-name,
  .kb-rank-rows.has-from .kb-rank-val {
    position: sticky;
    z-index: 1;
    /* Opaque on purpose. The panel token carries alpha, and a bar sliding under
       a translucent label reads as a rendering fault rather than as a column
       that stays put. */
    background: #0b1018;
  }

  .kb-rank-rows.has-from .kb-rank-name {
    left: 0;
    padding-right: 6px;
  }

  .kb-rank-rows.has-from .kb-rank-val {
    right: 0;
    padding-left: 6px;
  }

  body.light-theme .kb-rank-rows.has-from :is(.kb-rank-name, .kb-rank-val) {
    background: #ffffff;
  }
}

/* Change-dot chart: one row per category, a dot for each of two years and the
   line between them. Both numbers are printed, so a row is readable without
   decoding the dots, and the dashed lines are the reference the rows are read
   against -- one per year, in the same two colours as the dots. */
.kb-dot-legend {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 14px;
  margin: 0.35rem 0 0.55rem;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.kb-dot-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.kb-dot-legend-unit {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
}
.kb-dot-legend-ref {
  display: inline-block;
  width: 14px;
  height: 0;
  border-top: 2px dashed rgba(190, 205, 218, 0.75);
}
.kb-dot-rows {
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.kb-dot-row {
  display: grid;
  grid-template-columns: 150px 1fr 86px;
  align-items: center;
  gap: 10px;
}
.kb-dot-name {
  font-size: var(--fs-xs);
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.kb-dot-track {
  position: relative;
  height: 14px;
  background: rgba(150, 214, 255, 0.05);
  border-radius: var(--radius-xs);
}
.kb-dot-link {
  position: absolute;
  top: 50%;
  height: 3px;
  transform: translateY(-50%);
  background: rgba(190, 205, 218, 0.45);
  border-radius: 2px;
}
/* A head on the line, pointing at the later year, so the direction reads
   without checking the two dots against the legend. */
.kb-dot-link::after {
  content: "";
  position: absolute;
  top: 50%;
  width: 0;
  height: 0;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transform: translateY(-50%);
}
.kb-dot-link.is-forward::after {
  right: 4px;
  border-left: 6px solid rgba(190, 205, 218, 0.8);
}
.kb-dot-link.is-back::after {
  left: 4px;
  border-right: 6px solid rgba(190, 205, 218, 0.8);
}
.kb-dot {
  position: absolute;
  top: 50%;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  border-radius: 50%;
  transform: translateY(-50%);
}
.kb-dot--from {
  background: var(--muted);
  box-shadow: 0 0 0 1.5px rgba(8, 12, 18, 0.6);
}
.kb-dot--to {
  background: var(--accent);
  box-shadow: 0 0 0 1.5px rgba(8, 12, 18, 0.6);
}
.kb-dot--to.is-hot {
  background: var(--accent-hot);
}
.kb-dot--legend {
  position: static;
  margin: 0;
  transform: none;
}
.kb-dot-ref {
  position: absolute;
  top: -2px;
  bottom: -2px;
  width: 0;
  border-left: 2px dashed rgba(190, 205, 218, 0.5);
}
.kb-dot-ref--to {
  border-left-color: rgba(112, 195, 226, 0.65);
}
.kb-dot-vals {
  display: inline-flex;
  align-items: baseline;
  justify-content: flex-end;
  gap: 3px;
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.kb-dot-val-from,
.kb-dot-arrow {
  color: var(--muted);
  font-weight: 600;
}
.kb-dot-arrow {
  font-size: var(--fs-xs);
}
.kb-dot-val-to {
  color: var(--text);
}
.kb-dot-val-to.is-hot {
  color: var(--accent-hot);
}
.kb-dot-val-to.is-accent {
  color: var(--accent);
}
@media (max-width: 560px) {
  .kb-dot-row {
    grid-template-columns: 92px 1fr 74px;
    gap: 7px;
  }
}

/* Diverging horizontal bar chart: a shared euro scale with a zero axis; bars
   extend left for negative values and right for positive. Neutral slate for
   negatives, accent cyan for positives — no alarming colours. */
.kb-div-unit,
.kb-catbar-unit {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin: 0.1rem 0 0.4rem;
}
.kb-div-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 0.2rem;
}
.kb-div-row {
  display: grid;
  grid-template-columns: 150px 1fr 82px;
  align-items: center;
  gap: 10px;
}
/* Wraps rather than truncates: region names such as "Afghanistan, Iran, Syria
   and Iraq" are the data, and an ellipsis hides which countries a bar covers. */
.kb-div-name {
  font-size: var(--fs-xs);
  color: var(--text);
  overflow-wrap: anywhere;
}
.kb-div-track {
  position: relative;
  height: 12px;
  background: rgba(150, 214, 255, 0.06);
  border-radius: var(--radius-xs);
}
.kb-div-zero {
  position: absolute;
  top: -1px;
  bottom: -1px;
  width: 1px;
  margin-left: -0.5px;
  background: rgba(255, 255, 255, 0.32);
}
.kb-div-bar {
  position: absolute;
  top: 0;
  bottom: 0;
  border-radius: var(--radius-xs);
  transition: width 0.4s cubic-bezier(0.2, 0, 0.35, 1),
    left 0.4s cubic-bezier(0.2, 0, 0.35, 1);
}
.kb-div-bar.is-neg {
  background: rgba(176, 196, 214, 0.46);
}
.kb-div-bar.is-pos {
  background: rgba(112, 195, 226, 0.82);
}
/* Reference rows (native baseline, all-foreign-born average) get a ring. */
.kb-div-bar.is-accent {
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.55);
}
.kb-div-bar.is-accent.is-neg {
  background: rgba(176, 196, 214, 0.66);
}
.kb-div-val {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.kb-div-val.is-pos {
  color: var(--accent);
}
.kb-div-note {
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
  margin: 0.5rem 0 0;
}
@media (max-width: 560px) {
  /* 108px holds the longest label word with no hyphen to break at
     ("maahanmuuttajat", 104px), so no name is cut mid-word. */
  .kb-div-row {
    grid-template-columns: 108px 1fr 72px;
    gap: 8px;
  }
}

/* Grouped diverging bar chart (two series per row: actual + age-standardised). */
.kb-gdiv-unit {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin: 0.1rem 0 0.4rem;
}
.kb-gdiv-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-bottom: 0.5rem;
}
.kb-gdiv-key {
  font-size: var(--fs-xs);
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
.kb-gdiv-key::before {
  content: "";
  width: 12px;
  height: 8px;
  border-radius: var(--radius-xs);
  display: inline-block;
}
.kb-gdiv-key.is-a::before {
  background: rgba(176, 196, 214, 0.7);
}
.kb-gdiv-key.is-b::before {
  background: rgba(112, 195, 226, 0.82);
}
.kb-gdiv-rows {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.kb-gdiv-row {
  display: grid;
  grid-template-columns: 150px 1fr 96px;
  align-items: center;
  gap: 10px;
}
.kb-gdiv-name {
  font-size: var(--fs-xs);
  color: var(--text);
  /* Row names wrap rather than ellipsis-truncate: the fixed label column is
     too narrow for Finnish compounds like "Maahanmuuttajataustaiset,
     matematiikka", and a clipped label reads as a different group. */
  line-height: 1.2;
  hyphens: auto;
  overflow-wrap: break-word;
}
.kb-gdiv-track {
  position: relative;
  height: 16px;
  background: rgba(150, 214, 255, 0.06);
  border-radius: var(--radius-xs);
}
.kb-gdiv-zero {
  position: absolute;
  top: -1px;
  bottom: -1px;
  width: 1px;
  margin-left: -0.5px;
  background: rgba(255, 255, 255, 0.32);
}
.kb-gdiv-bar {
  position: absolute;
  height: 6px;
  border-radius: var(--radius-xs);
}
.kb-gdiv-bar.is-a {
  top: 2px;
  background: rgba(176, 196, 214, 0.7);
}
.kb-gdiv-bar.is-b {
  bottom: 2px;
  background: rgba(112, 195, 226, 0.82);
}
.kb-gdiv-vals {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  line-height: 1.25;
}
.kb-gdiv-v {
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.kb-gdiv-v.is-a {
  color: var(--muted);
}
.kb-gdiv-v.is-b {
  color: var(--accent);
}
.kb-gdiv-note {
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  color: var(--muted);
  margin: 0.5rem 0 0;
}
@media (max-width: 560px) {
  .kb-gdiv-row {
    grid-template-columns: 124px 1fr 70px;
    gap: 8px;
  }
  .kb-gdiv-name {
    /* Wide enough for the longest single word we ship ("Maahanmuuttaja-
       taustaiset") so wrapping happens at spaces, not mid-word. */
  }
}

/* Multi-line chart (age-profile lines) legend + unit. */
.kb-line-unit {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin: 0.1rem 0 0.3rem;
}
.kb-line-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  margin-top: 0.35rem;
  /* the hint bubble is absolutely positioned above a key and must not be clipped */
  overflow: visible;
}
.kb-line-key {
  font-size: var(--fs-xs);
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  gap: 6px;
}
/* A legend key that carries a group explainer. The dotted underline is the
   only thing telling the reader there is something to hover; without it the
   tooltip is undiscoverable.

   The hint used to be a native `title`, which the browser renders after about a
   second, unstyled, never on touch, and not at all if the pointer drifts — so
   the `help` cursor promised a box that usually failed to arrive. It is now a
   data-hint bubble drawn here, on hover and on keyboard focus. */
.kb-line-key.has-hint {
  cursor: help;
  position: relative;
  text-decoration: underline dotted rgba(var(--accent-rgb), 0.5);
  text-underline-offset: 3px;
}
.kb-line-key.has-hint:hover {
  color: var(--text);
}

.kb-line-key.has-hint::after {
  content: attr(data-hint);
  position: absolute;
  bottom: calc(100% + 8px);
  left: 0;
  z-index: 40;
  width: max-content;
  max-width: min(320px, 70vw);
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-strong, rgba(10, 14, 24, 0.98));
  box-shadow: var(--shadow-md);
  color: var(--text);
  font-size: var(--fs-xs);
  line-height: var(--lh-ui);
  letter-spacing: 0;
  text-transform: none;
  text-decoration: none;
  white-space: normal;
  text-align: left;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s ease;
  pointer-events: none;
}

.kb-line-key.has-hint:hover::after,
.kb-line-key.has-hint:focus-visible::after {
  opacity: 1;
  visibility: visible;
}

/* Keys past the middle of the row would push the bubble off the right edge. */
.kb-line-key.has-hint.hint-right::after {
  left: auto;
  right: 0;
}
.kb-line-swatch {
  width: 14px;
  height: 3px;
  border-radius: var(--radius-xs);
  display: inline-block;
}

/* Prominent methodology-limitations box for charts that are a modeled
   scenario rather than a straightforward plot of measured data. Deliberately
   louder than .kb-chart-caption — it's meant to be read, not skimmed past. */
.kb-chart-caveats {
  margin-top: 0.6rem;
  padding: 0.7rem 0.85rem;
  border-left: 3px solid var(--muted);
  background: color-mix(in srgb, var(--muted) 10%, transparent);
  border-radius: var(--radius-xs);
}
/* The methodology box is a <details>, collapsed by default, matching the
   panels on the map and charts pages. Two of the three charts carrying one are
   projections whose box runs to nine bullets; left open they push the next
   section off the screen. Caveats that change how a chart must be read live in
   the source caption below the chart, not in here, so folding this away does
   not hide them. */
.kb-chart-caveats-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
  margin: 0;
  letter-spacing: 0.03em;
  cursor: pointer;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-height: 24px;
}
.kb-chart-caveats-title::-webkit-details-marker {
  display: none;
}
/* Own marker so the affordance survives list-style:none in every engine. */
.kb-chart-caveats-title::before {
  content: "";
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transition: transform 0.15s ease;
  flex: none;
}
.kb-chart-caveats[open] > .kb-chart-caveats-title {
  margin-bottom: 0.5rem;
}
.kb-chart-caveats[open] > .kb-chart-caveats-title::before {
  transform: rotate(90deg);
}
.kb-chart-caveats-title:hover {
  color: var(--accent);
}
.kb-chart-caveats-title:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--radius-xs);
}
.kb-chart-caveats-list {
  margin: 0;
  padding-left: 1.1rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}
.kb-chart-caveats-list li {
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: var(--lh-prose);
  opacity: 0.92;
}

.kb-migmap-card {
  padding: 0.75rem 0.75rem 0.55rem;
}

.kb-migmap-header {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: flex-start;
  gap: 1rem;
  margin-bottom: 0.45rem;
}

.kb-migmap-title {
  margin-bottom: 0;
  min-width: 0;
}

.kb-migmap-year {
  color: var(--accent);
  font-size: 2.2rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

.kb-migmap-canvas {
  display: block;
  width: 100%;
  border-radius: var(--radius-sm);
  background: var(--bg);
  box-shadow: inset 0 0 0 1px rgba(255,255,255,0.05);
}

.kb-migmap-controls {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.55rem;
  margin-top: 0.5rem;
}

.kb-migmap-playbtn {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--control-bg);
  color: var(--text);
  cursor: pointer;
  flex-shrink: 0;
  font-size: var(--fs-xs);
  line-height: 1.2;
  margin: 0;
  padding: 4px 10px;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}

.kb-migmap-playbtn:hover {
  border-color: var(--accent-dim);
  color: var(--accent);
}

.kb-migmap-slider {
  --progress: 0%;
  appearance: none;
  width: 100%;
  min-width: 0;
  height: 16px;
  margin: 0;
  border-radius: var(--radius-pill);
  background:
    linear-gradient(90deg, var(--accent) 0 var(--progress), var(--line) var(--progress) 100%);
  cursor: pointer;
}

.kb-migmap-slider::-webkit-slider-thumb {
  appearance: none;
  width: 16px;
  height: 16px;
  border: 2px solid var(--bg);
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 1px var(--border);
}

.kb-migmap-slider::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border: 2px solid var(--bg);
  border-radius: 50%;
  background: var(--accent);
  box-shadow: 0 0 0 1px var(--border);
}

.kb-migmap-total {
  color: var(--accent);
  font-size: var(--fs-xs);
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.kb-migmap-origins {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px 14px;
  margin: 0.5rem 0 0;
  padding: 0;
  color: var(--muted);
  font-size: var(--fs-xs);
  list-style: none;
}

.kb-migmap-origins li {
  display: grid;
  grid-template-columns: 2.8rem minmax(0, 1fr) 2.5rem;
  align-items: center;
  gap: 6px;
}

.kb-migmap-origins b {
  color: var(--text);
}

.kb-migmap-origins i {
  display: block;
  height: 4px;
  border-radius: var(--radius-pill);
  background:
    linear-gradient(90deg, rgba(79,195,247,0.75) 0 var(--origin-share), rgba(255,255,255,0.10) var(--origin-share) 100%);
}

.kb-migmap-origins em {
  font-style: normal;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

.kb-migmap-caption {
  margin-top: 0.35rem;
}

/* The two rank panels used to stand in a 210px column beside the map, which
   left the map half the card's width. They are a pair that reads left to
   right, so they sit under the map instead and the map takes the full width.
   The narrow-width rules below only tune the spacing now, because this is
   already the layout they used to switch to. */
.kb-migmap-body {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 14px;
  margin-top: 0.6rem;
}

.kb-migmap-main {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.kb-migmap-lists {
  display: flex;
  flex-direction: row;
  gap: 12px;
}

.kb-migmap-rank {
  flex: 1 1 0;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-soft);
  padding: 10px 12px;
}

.kb-migmap-rank-title {
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 650;
}

.kb-migmap-rank-unit {
  font-size: var(--fs-xs);
  color: var(--muted-soft);
  margin: 0.1rem 0 6px;
}

.kb-migmap-rank-list {
  display: grid;
  gap: 4px;
  margin: 0;
  padding-left: 18px;
  color: var(--text);
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
}

.kb-migmap-rank-list li {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 5px 0;
  border-bottom: 1px solid var(--line);
}

.kb-migmap-rank-list li:last-child {
  border-bottom: none;
}

.kb-migmap-rank-name {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.kb-migmap-rank-code {
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 600;
  background: var(--panel-soft);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  padding: 1px 5px;
  text-transform: uppercase;
  flex-shrink: 0;
  letter-spacing: 0.02em;
}

.kb-migmap-rank-value {
  color: var(--accent);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  flex-shrink: 0;
}

@media (max-width: 620px) {
  .kb-chart-head {
    align-items: stretch;
    flex-direction: column;
  }

  .kb-pew-select {
    width: 100%;
  }

  /* The static page's map sets its country % labels in viewBox units, so
     shrinking the whole map to fit a narrow screen shrinks the text past
     legibility: that one keeps a readable size and scrolls. The island's map
     knows the width it is drawn at (data-chart-width) and sizes its labels
     for it, so it fits the screen instead. */
  .kb-pew-svg-wrap {
    overflow-x: auto;
  }

  .kb-pew-svg-wrap svg:not([data-chart-width]) {
    width: 640px !important;
    min-width: 640px;
  }

  .kb-migmap-header {
    align-items: start;
    gap: 0.55rem;
  }

  .kb-migmap-year {
    font-size: 1.65rem;
  }

  .kb-migmap-title {
    font-size: var(--fs-xs);
    line-height: 1.3;
  }

  .kb-migmap-controls {
    grid-template-columns: auto minmax(0, 1fr);
  }

  .kb-migmap-total {
    grid-column: 1 / -1;
    justify-self: end;
  }

  .kb-migmap-lists .kb-migmap-rank {
    /* Side by side, each panel gets ~143px of content box. The desktop
       spacing does not fit in that: the ordered-list indent alone is 18px,
       and the row needs name + country chip + a 6-digit tabular value. The
       result was the value spilling out past the panel border and over the
       neighbouring panel. Reclaim the space rather than truncate, so no
       country name or figure is ever cut. */
    padding: 10px 8px;
  }

  .kb-migmap-lists .kb-migmap-rank-list {
    padding-left: 0;
    list-style: none;
  }

  .kb-migmap-lists .kb-migmap-rank-list li {
    gap: 6px;
  }

  .kb-migmap-lists .kb-migmap-rank-name {
    gap: 4px;
    /* Long names (Afganistan, Britannia) shrink here instead of pushing the
       value out of the box. */
    overflow: hidden;
  }

  .kb-migmap-lists .kb-migmap-rank-code {
    padding: 1px 4px;
  }

  .kb-migmap-origins {
    grid-template-columns: 1fr;
  }
}

/* Below ~380px the two rank panels cannot sit side by side without either
   truncating a country name or clipping a figure, whatever the spacing. Stack
   them instead: full width each, nothing lost. */
@media (max-width: 380px) {
  .kb-migmap-lists {
    flex-direction: column;
  }
}

/* The multi-line chart's hover tooltip tracks the pointer via mousemove.
   There's no such thing as "hover" with a finger, so on a touch-primary
   portrait phone it just leaves a tooltip stuck wherever the last touch
   landed, with no precise way to move or dismiss it. Turn the tracking off
   there entirely rather than ship an interaction nobody can use cleanly. */
@media (max-width: 620px) and (orientation: portrait) {
  .kb-line-chart svg {
    pointer-events: none;
  }

  /* Stacked-bar and multi-line charts are drawn at a ~660px-wide SVG
     viewBox, sized for desktop. In portrait that no longer fits the column
     and either gets clipped or squeezed illegibly small depending on the
     parent's overflow. Keep the SVG near its natural size and let just that
     region scroll horizontally, directly under the chart — not the title,
     legend, or caption around it. Landscape/desktop have enough width that
     this isn't needed, and forcing a fixed min-width there would just make
     things worse. (Pew/migration maps have their own analogous wrapper
     already: .kb-pew-svg-wrap / .kb-migmap-svg-wrap.) */
  .kb-chart-svg-wrap {
    overflow-x: auto;
  }

  /* The static page's scaled charts only: the island draws at the card's
     real width (.kb-svg-px), so it neither shrinks nor needs the scroll. */
  .kb-chart-svg-wrap svg:not(.kb-svg-px) {
    width: 600px !important;
    min-width: 600px;
  }
}

.kb-country-name {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text);
  margin: 0 0 12px;
  display: flex;
  align-items: center;
  gap: 8px;
}

.kb-country-name::before {
  content: "";
  display: inline-block;
  width: 3px;
  height: 14px;
  background: var(--accent);
  border-radius: var(--radius-xs);
  flex-shrink: 0;
}

.kb-country-lead {
  max-width: 74ch;
  font-size: var(--fs-md);
  color: var(--text);
  line-height: var(--lh-prose);
  margin: 0 0 16px;
}

.kb-surveys {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* A country block with no survey entries still renders this flex box, and an
   empty box in the flow stops the last chart card's bottom margin from
   collapsing into the block separator: the gap under a block-final chart came
   out at 52px against the 20px above it. */
.kb-surveys:empty {
  display: none;
}

.kb-entry {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 16px;
  min-width: 0;
  box-shadow: var(--shadow-sm);
}

.kb-entry-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
  min-width: 0;
  flex-wrap: wrap;
}

.kb-entry-title {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text);
  flex: 1 1 55%;
  min-width: 0;
  overflow-wrap: anywhere;
}

.kb-entry-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 1 auto;
  min-width: 0;
  flex-wrap: wrap;
  justify-content: flex-end;
}

.kb-entry-year,
.kb-entry-n {
  font-size: var(--fs-xs);
  color: var(--muted);
  overflow-wrap: anywhere;
}

.kb-entry-year {
  white-space: nowrap;
}

.kb-entry-note {
  margin: 0 0 6px;
  font-size: var(--fs-xs);
  line-height: var(--lh-prose);
  color: var(--muted);
  overflow-wrap: anywhere;
}

.kb-entry-source {
  display: block;
  font-size: var(--fs-xs);
  color: var(--accent-dim);
  text-decoration: none;
  margin-bottom: 1px;
  padding-block: 5px;
  word-break: break-all;
}

.kb-entry-source:hover {
  color: var(--accent);
  text-decoration: underline;
}

.kb-entry-findings {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.kb-entry-findings li {
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: var(--lh-prose);
  padding-left: 16px;
  position: relative;
  overflow-wrap: anywhere;
}

.kb-entry-findings li::before {
  content: "–";
  position: absolute;
  left: 0;
  color: var(--muted);
}

.kb-entry-findings li strong {
  color: var(--accent);
}

.kb-entry-findings li a {
  color: var(--accent-dim);
  text-decoration: none;
}
.kb-entry-findings li a:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* Articles */
.kb-articles {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.kb-article {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 16px;
  box-shadow: var(--shadow-sm);
}

.kb-article-header {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-bottom: 6px;
}

.kb-article-title {
  font-size: var(--fs-md);
  font-weight: 600;
  color: var(--text);
  text-decoration: none;
  line-height: var(--lh-ui);
}

a.kb-article-title:hover {
  color: var(--accent);
  text-decoration: underline;
}

.kb-article-original {
  margin-top: 2px;
  margin-bottom: 2px;
}

.kb-article-original-link {
  font-size: var(--fs-xs);
  opacity: 0.45;
  color: inherit;
  text-decoration: none;
  font-style: italic;
}

.kb-article-original-link:hover {
  opacity: 0.7;
  text-decoration: underline;
}

.kb-article-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px;
}

.kb-article-evidence {
  font-size: var(--fs-xs);
  font-weight: 650;
  color: var(--accent);
  background: rgba(112, 195, 226, 0.08);
  border: 1px solid rgba(112, 195, 226, 0.22);
  border-radius: var(--radius-xs);
  padding: 2px 7px;
}

.kb-article-evidence[data-evidence="opinion"] {
  color: #f3c96b;
  background: rgba(243, 201, 107, 0.08);
  border-color: rgba(243, 201, 107, 0.25);
}

.kb-article-evidence[data-evidence="research"] {
  color: #a7e3bd;
  background: rgba(167, 227, 189, 0.08);
  border-color: rgba(167, 227, 189, 0.24);
}

.kb-article-author {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
}

.kb-article-pub {
  font-size: var(--fs-sm);
  color: var(--muted);
}

.kb-article-author + .kb-article-pub::before {
  content: "·";
  margin-right: 6px;
  color: var(--muted);
}

.kb-article-date {
  font-size: var(--fs-xs);
  color: var(--muted);
  background: rgba(112, 195, 226, 0.07);
  border: 1px solid var(--border);
  border-radius: var(--radius-xs);
  padding: 2px 7px;
}

/* A source's finding stays on the page and its detail opens on click. The
   marker is the word, not a triangle: the row is a paragraph of prose, and a
   disclosure arrow beside running text reads as a bullet. */
/* The bare `details` in this sheet is a panel: a bordered, rounded, filled box,
   which is right for a footnote toggle inside a map panel. Here it wraps a
   sentence of prose inside a card that already has its own border, so a source
   whose summary runs to a second paragraph was drawn as a box within a box —
   and one whose lead was a single line came out looking like an empty input
   field. The row has to read as the paragraph it replaced, which is what the
   sources without a second paragraph already do. */
.kb-article-more {
  margin: 0;
  border: 0;
  border-radius: 0;
  background: none;
  padding: 0;
  color: var(--text);
  /* The summary row takes `font-size: inherit` to escape the small-caps control
     styling, so the container has to carry the paragraph's own size: inheriting
     the card's 16px made an expandable summary larger than a plain one. */
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
}

/* A control, not a sentence: the summary row used to be the card's first
   sentence and is now just the word that opens it. Small and quiet, so a list
   of cards reads as titles rather than as a wall of prose. */
.kb-article-more > summary {
  display: inline-block;
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: var(--lh-ui);
  letter-spacing: 0.02em;
  text-transform: none;
  color: var(--accent);
  margin: 2px 0 0;
  padding: 0;
}

.kb-article-more > summary::after {
  content: " ›";
}

.kb-article-more[open] > summary::after {
  content: " ‹";
}

.kb-article-more > summary:hover {
  text-decoration: underline;
}

.kb-article-more > summary::-webkit-details-marker {
  display: none;
}

.kb-article-more-hint {
  margin-left: 6px;
  color: var(--accent);
  font-size: var(--fs-sm);
  white-space: nowrap;
}

.kb-article-more-hint::after {
  content: " ›";
}

.kb-article-more[open] .kb-article-more-hint {
  display: none;
}

.kb-article-summary--rest {
  display: block;
  margin-top: 6px;
}

/* Making both children inline is not enough: everything after `summary` sits in
   the browser's own block wrapper, so the rest started a new line however it
   was styled. Browsers that expose that wrapper let it go inline and the
   sentence runs on; the rest fall back to the paragraph beneath, which is
   readable, just not as tidy. Only when open: the closed state's `display:
   none` lives on the same wrapper, so an unscoped rule shows the hidden half
   with the "lisää" hint stranded in the middle of it. */
.kb-article-more[open]::details-content {
  display: block;
}

.kb-article-summary {
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: var(--lh-prose);
  margin: 0;
}

.kb-article-caveat {
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: var(--lh-prose);
  margin: 6px 0 0;
}

/* X-posts – collapsible toggle */
.kb-xposts-toggle {
  margin-top: 8px;
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  overflow: hidden;
}

.kb-xposts-summary {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 9px 14px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  list-style: none;
  background: var(--panel);
  transition: color 0.15s, background 0.15s;
}

.kb-xposts-summary::-webkit-details-marker { display: none; }

.kb-xposts-summary::before {
  content: "▸";
  font-size: var(--fs-xs);
  transition: transform 0.15s;
  flex-shrink: 0;
}

.kb-xposts-toggle[open] > .kb-xposts-summary::before {
  transform: rotate(90deg);
}

.kb-xposts-summary:hover {
  color: var(--text);
  background: var(--panel-soft);
}

.kb-xposts-count {
  font-weight: 400;
  color: var(--muted);
}

.kb-xposts-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--border);
}

.kb-xpost-row {
  border-bottom: 1px solid var(--border);
}

.kb-xpost-row:last-child {
  border-bottom: none;
}

.kb-xpost-row-link {
  display: block;
  padding: 8px 14px;
  font-size: var(--fs-sm);
  color: var(--text);
  text-decoration: none;
  line-height: var(--lh-ui);
  transition: background 0.12s, color 0.12s;
}

.kb-xpost-row-link:hover {
  background: var(--panel-soft);
  color: var(--accent);
}

.kb-xpost-row-label {
  display: block;
  padding: 8px 14px;
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: var(--lh-ui);
}

@media (max-width: 700px) {
  .knowledge-stage {
    overflow-x: hidden;
  }

  .kb-shell {
    flex-direction: column;
    max-width: 100%;
    padding: 16px 12px 40px;
  }

  .kb-toc {
    width: 100%;
    max-height: min(34dvh, 260px);
    overflow-y: auto;
    position: static;
    padding: 10px 0;
    margin-right: 0;
    margin-bottom: 18px;
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    background: var(--panel);
  }

  .kb-toc-title {
    margin-left: 12px;
  }

  .kb-content,
  .kb-comparisons,
  .kb-entry,
  .kb-article,
  .kb-article-group,
  .kb-chart,
  .kb-migmap-card {
    max-width: 100%;
  }

  .kb-comparisons-grid {
    column-count: 1;
  }

  .kb-entry-source,
  .kb-article-title,
  .kb-article-summary,
  .kb-section-lead,
  .kb-group-lead,
  .kb-country-lead,
  .kb-intro,
  .kb-comparison-desc {
    overflow-wrap: anywhere;
  }
}

/* ── Chart stage top padding ────────────────────────────── */
.chart-stage-col {
  padding-top: 20px;
}

/* ── System prose/body text ────────────────────────────── */
.kb-section-lead,
.kb-group-lead,
.kb-section-note,
.kb-intro,
.kb-comparison-desc,
.home-hero-tagline {
  font-family: var(--font-data);
}

/* ── Home / landing stage ───────────────────────────────── */
.home-stage {
  /* NOTE: the top is not set here. `body[data-view="home"] #homeStage` wins on
     specificity and supplies padding-top; edit that one. */
  padding: 100px 24px 64px;
  gap: 40px;
  position: relative;
  background-image:
    url("../images/dark_gradient.svg?v=2"),
    url("../images/helsinki_dark_bg.svg?v=2");
  background-attachment: local, local;
  background-size: 100% calc(50vh + 80px), auto calc(50vh + 80px);
  background-position: center -80px, center -80px;
  background-repeat: no-repeat, no-repeat;
  background-color: #000000;
}

body.light-theme .home-stage {
  background-image:
    url("../images/light_gradient.svg?v=2"),
    url("../images/helsinki_light_bg.svg?v=2");
  background-attachment: local, local;
  background-size: 100% calc(50vh + 80px), auto calc(50vh + 80px);
  background-position: center -80px, center -80px;
  background-repeat: no-repeat, no-repeat;
  background-color: #f4f7f9;
}

@media (max-width: 920px) {
  .home-stage {
    padding-top: calc(var(--safe-top) + var(--top-bar-height) + 60px);
  }
}

.home-elm-shell {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  /* On a wide screen the content column is narrow against the viewport, and at
     40px the cards read as one dense block rather than as sections. */
  gap: 56px;
}

.home-hero {
  text-align: center;
  /* Sized for the TITLE, which has to stay on one line -- the tagline is
     narrowed on its own rule below rather than by squeezing this. */
  max-width: 740px;
  width: 100%;
}

/* Spacing per step rather than one flat gap. The flat gap was even in the code
   and uneven on screen, because .area-search carries its own 22/26px margins
   for the area page (where nothing else supplies them): the search box sat
   further from the buttons than the buttons sat from the hero.

   The offsets are relative to the shell's gap, so the shell's gap sets how open
   the page breathes and these set the rhythm within it. Only the three blocks
   above the fold are tightened -- the buttons belong to the search box under
   them, and the hero belongs to both. Everything below keeps the full gap.
   Scoped to the home shell so the area page keeps its own margins. */
/* -8, not -22: the hero is the one block the buttons do NOT belong to, and at
   -22 they sat 34px below the tagline -- closer to it than to the search box
   they act on. The rest of the rhythm below is unchanged. */
.home-elm-shell > .home-cta-row {
  margin-top: -8px;
}

.home-elm-shell > .area-search {
  margin-top: -34px;
  margin-bottom: 0;
}

.home-elm-shell > .home-quick {
  margin-top: -12px;
}

/* The whole title used to be accent-coloured, which left the accent meaning
   "this is a heading" rather than pointing at anything. It points at the one
   word the site is about instead, and the rest is the page's own text. */
.home-hero-title {
  margin: 0 0 16px;
  font-size: clamp(1.75rem, 4.6vw, 3.1rem);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: -0.02em;
  color: var(--text);
  /* Wraps where the width says, with the lines evened out, instead of at a
     break placed by hand for one viewport. */
  text-wrap: balance;
}

.home-hero-title-accent {
  color: var(--accent);
}

/* The caveats moved off the hero paragraph onto the info page, so the last
   sentence ends in a link there. It runs on inside the paragraph rather than
   sitting on a line of its own, where it read as a fourth button above the
   three real ones.

   display:inline rather than the button default of inline-block, so the four
   words break across lines with the rest of the sentence instead of jumping to
   the next line as a block. The paragraph is already var(--muted), so the
   underline is the only thing marking it as a link, and it is drawn at 70%
   rather than the 45% it used when a line of its own did that work. */
.home-hero-note-link {
  display: inline;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--muted) 70%, transparent);
  cursor: pointer;
}

.home-hero-note-link:hover,
.home-hero-note-link:focus-visible {
  color: var(--accent);
  text-decoration-color: currentColor;
}

.home-hero-tagline {
  font-size: var(--fs-md);
  color: var(--muted);
  line-height: var(--lh-prose);
  margin: 0;
  font-weight: 400;
  /* Narrower than the hero, and centred in it: a centred paragraph this long
     reads better in a shorter measure, and it should not out-run the headline
     above it.

     640 rather than 700 -- anywhere in 640-700 it sets to the same number of
     lines (six in Finnish, five in English), so within that band the narrowest
     costs nothing. Below 640 it takes a seventh. It stays off .home-hero
     because the title needs the full 740 to hold one line. */
  max-width: 640px;
  margin-inline: auto;
}

.home-cta-row {
  display: flex;
  flex-direction: row;
  gap: 12px;
  justify-content: center;
  flex-wrap: wrap;
  width: 100%;
}

.home-cta-btn {
  /* Fixed equal width so the row reads as a unit regardless of label length.
     The width used to be set by the longest label -- "Explore the background"
     needed almost every pixel of it and left 13px of slack against the other
     two buttons' 84 and 116. That label is shorter now, so 200px is a round
     number with room in it rather than a measurement of one string. */
  width: 200px;
  padding: 12px 4px;
  text-align: center;
  border-radius: var(--radius-md);
  font-family: inherit;
  font-size: var(--fs-md);
  font-weight: 600;
  cursor: pointer;
  transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease, box-shadow 0.18s ease;
  letter-spacing: 0.01em;
}

/* Finnish labels are shorter ("Tutustu taustoihin" ~146px), so the row can
   tighten further when the page is in Finnish. */
:lang(fi) .home-cta-btn {
  width: 180px;
}

.home-cta-btn--primary {
  background: var(--accent);
  color: #0a1a24;
  border: 1px solid var(--accent);
}

.home-cta-btn--primary:hover {
  background: #b8f0ff;
  border-color: #b8f0ff;
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(112, 195, 226, 0.3);
}

.home-cta-btn--secondary {
  background: transparent;
  color: var(--accent);
  border: 1px solid rgba(112, 195, 226, 0.4);
}

.home-cta-btn--secondary:hover {
  background: rgba(112, 195, 226, 0.08);
  border-color: var(--accent);
  transform: translateY(-2px);
  box-shadow: 0 6px 20px rgba(112, 195, 226, 0.12);
}

/* The blanket `body.light-theme button` rule above paints every button white,
   and it outranks a single class: one class plus two elements beats one class.
   So the front page's primary call to action lost its accent in the light theme
   and became a white button among white buttons -- the one thing on the page
   that is supposed to be the obvious next step read as the least of the three.
   These put it back, at the specificity the blanket rule set.

   The colours are the light palette's own, not the dark one's: #0369a1 on white
   is 5.9:1, where the dark theme's #70c3e2 would be 1.8:1 and unreadable. */
body.light-theme .home-cta-btn--primary {
  background: var(--accent);
  color: #ffffff;
  border-color: var(--accent);
}

body.light-theme .home-cta-btn--primary:hover {
  background: #075985;
  border-color: #075985;
  box-shadow: 0 6px 20px rgba(3, 105, 161, 0.25);
}

body.light-theme .home-cta-btn--secondary {
  background: transparent;
  color: var(--accent);
  border-color: rgba(3, 105, 161, 0.35);
}

body.light-theme .home-cta-btn--secondary:hover {
  background: rgba(3, 105, 161, 0.07);
  border-color: var(--accent);
}

/* Same rule, same victim: an unselected theme chip came out as a plain white
   pill rather than the quiet one the dark theme gets. */
body.light-theme .kb-theme-chip {
  background: var(--panel-soft);
  color: var(--muted);
  border-color: var(--border);
}

body.light-theme .kb-theme-chip:hover {
  color: var(--text);
  border-color: var(--accent);
}
/* Buttons that declare themselves transparent and mean it. The blanket
   `body.light-theme button { background: #ffffff }` outranks a single class, so
   a panel heading that is a button for navigation came out as a white box
   behind its own text, and the share and disclosure icons came out as white
   squares in the middle of a card.
   Only the four that ask for `background: none` or `transparent` are listed.
   The others the blanket rule repaints are repainted on purpose or are rescued
   by it: --control-bg is defined dark-only, so .kb-rank-toggle-btn and
   .layer-button would go near-black on a light page without it. Removing the
   blanket rule is therefore not the fix it looks like. */
body.light-theme .kb-share-btn,
body.light-theme .kb-chart-image-btn,
body.light-theme .kb-comparison-toggle,
body.light-theme .migri-view-btn {
  background: none;
}

/* The selected tab keeps its tint, which the rule above would otherwise strip
   the way it stripped the theme chips. */
body.light-theme .migri-view-btn.is-active {
  background: rgba(3, 105, 161, 0.1);
}

/* And the selected one keeps its fill. The rule above is one class heavier than
   `.kb-theme-chip.is-active`, so restoring the quiet chip silently flattened the
   selected one and the filter stopped showing which chips were on. */
body.light-theme .kb-theme-chip.is-active {
  background: var(--accent);
  color: #ffffff;
  border-color: var(--accent);
}

/* Shortcuts to the most-requested datasets, between the three call-to-action
   buttons and the stat tiles. Pills, not blocks: prominent enough to be the
   next thing you notice, but clearly a different kind of control from the
   three primary buttons above, which are fixed-width rectangles. */
.home-quick {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* The eyebrow needs more room over the buttons than the buttons need between
     themselves: at 8px it read as part of the first pill rather than as a label
     over the row. */
  gap: 14px;
  width: 100%;
  /* wider than the 820px stat grid below so all five fit on one line on
     desktop; they wrap on their own once the viewport cannot hold them */
  max-width: 1000px;
}

.home-quick-label {
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  color: var(--muted);
}

.home-quick-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
  width: 100%;
}

.home-quick-btn {
  display: inline-flex;
  align-items: center;
  padding: 7px 15px;
  border: 1px solid rgba(var(--accent-rgb), 0.28);
  border-radius: var(--radius-pill);
  background: rgba(var(--accent-rgb), 0.06);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 500;
  line-height: 1.2;
  text-decoration: none;
  white-space: nowrap;
  transition: background 0.16s ease, border-color 0.16s ease, color 0.16s ease;
}

.home-quick-btn:hover,
.home-quick-btn:focus-visible {
  background: rgba(var(--accent-rgb), 0.16);
  border-color: var(--accent);
  color: var(--accent);
}

/* The nationality label is long; let it wrap rather than force a scrollbar. */
@media (max-width: 620px) {
  .home-quick-btn {
    white-space: normal;
    text-align: center;
  }
}

/* ── Home boxes ─────────────────────────────────────────────
   One frame per kind of data: six single figures, then areas and trends, then
   group comparisons, the fact of the visit and the claims. A frame holding two
   kinds of thing says nothing about either.

   The cells are transparent. The rules between them are borders the cells draw
   themselves rather than a background showing through a gap, because the stage
   carries the background map and a painted cell would cover it. */
.home-box {
  width: 100%;
  max-width: 900px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.home-box-title {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: center;
}

.home-box-grid {
  display: grid;
  border: 1px solid var(--home-line);
  border-radius: var(--radius-md);
  overflow: hidden;
}

/* Every cell carries the rule above it and the rule to its left; the first row
   and the first column drop theirs against the frame. Set as widths on a
   border that is already declared, so a cell never has to restate it. */
.home-box-grid > * {
  border: 0 solid var(--home-line-soft);
  border-top-width: 1px;
  border-left-width: 1px;
  min-width: 0;
}

.home-box-grid--stats,
.home-box-grid--compare {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.home-box-grid--stats > :nth-child(-n + 3),
.home-box-grid--compare > :nth-child(-n + 3) {
  border-top-width: 0;
}

.home-box-grid--stats > :nth-child(3n + 1),
.home-box-grid--compare > :nth-child(3n + 1) {
  border-left-width: 0;
}

.home-box-grid--data {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.home-box-grid--data > :nth-child(-n + 2) {
  border-top-width: 0;
}

.home-box-grid--data > :nth-child(2n + 1) {
  border-left-width: 0;
}

.home-box-grid--fact > * {
  border-top-width: 0;
  border-left-width: 0;
}

/* A cell in any of the boxes: what it measures, the period it covers, the
   figure, and a note under it. Every box uses these four, because a heading
   that is accent-coloured in one box, white in the next and muted in a third
   says nothing by being different. */
.home-cell-title {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  line-height: var(--lh-ui);
  /* No automatic hyphenation: English broke "background" across lines as
     "back-ground" inside a word that already carries a hyphen. A word that
     genuinely cannot fit still breaks, without one. */
  overflow-wrap: break-word;
}

/* The lists' heading is the panel's own navigation, so it is a button and has
   to look like the heading it replaced. */
.home-cell-title--nav {
  display: block;
  width: 100%;
  margin: 0;
  padding: 0;
  border: 0;
  background: none;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}

.home-cell-period {
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--muted);
  opacity: 0.8;
}

.home-cell-figure {
  margin-top: 6px;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: clamp(1.6rem, 3.2vw, 2.25rem);
  font-weight: 700;
  color: var(--text);
  letter-spacing: -0.015em;
  line-height: 1.05;
}

.home-cell-figure--long {
  font-size: clamp(1.3rem, 2.5vw, 1.75rem);
  letter-spacing: -0.005em;
}

/* A trend cell has one figure and room for it. */
.home-cell-figure--large {
  font-size: clamp(1.9rem, 4vw, 2.6rem);
  letter-spacing: -0.02em;
}

.home-cell-note {
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  color: var(--muted);
  opacity: 0.85;
  line-height: var(--lh-ui);
}

/* Any measured number inside a cell's rows. White like every other figure:
   the accent is kept for what is marked rather than what is measured, which
   is why the bar beside it is accent-coloured and the number is not. */
.home-cell-value {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  text-align: right;
  white-space: nowrap;
}

/* The key figure's own line: the period on the left, the multiple on the
   right. */
.home-cell-meta {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px;
  margin-top: 3px;
}

/* The multiple is the thing a reader otherwise works out in their head. */
.home-stat-badge {
  flex: none;
  padding-top: 1px;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--accent);
  text-align: right;
  line-height: 1.3;
}

/* The site's own calculation is marked by the word, not by a colour of its
   own: a second hue on one cell out of six reads as an error state. */
.home-stat-badge--tag {
  font-family: inherit;
  font-size: var(--fs-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  border: 1px solid var(--home-line);
  border-radius: var(--radius-pill);
  padding: 2px 8px;
}

.home-stat {
  display: flex;
  flex-direction: column;
  padding: 16px 18px 18px;
  background: none;
  border-radius: 0;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: inherit;
  transition: background 0.2s ease;
}

.home-stat:hover {
  background: rgba(var(--accent-rgb), 0.05);
}

/* Only the key figures reserve two lines of title. They sit three to a row and
   their figures have to land on one baseline; a list or a trend cell is alone
   in its column, and the reserved line read as a hole under a short title. */
.home-stat .home-cell-title {
  min-height: 2.2rem;
}

/* ── The key figure's scale ─────────────────────────────────
   Both values on one line. Nothing is drawn from zero, because the figure's
   meaning is the distance between the marks and not the length of a bar. */
.home-scale {
  position: relative;
  height: 11px;
  margin: 11px 0 8px;
}

.home-scale::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 5px;
  height: 1px;
  background: var(--home-line-soft);
}

.home-scale-dot {
  position: absolute;
  border-radius: 50%;
}

.home-scale-dot--figure {
  top: 0;
  width: 11px;
  height: 11px;
  margin-left: -5.5px;
  background: var(--accent);
  box-shadow: 0 0 0 3px rgba(var(--accent-rgb), 0.15);
}

.home-scale-dot--comparison {
  top: 1px;
  width: 9px;
  height: 9px;
  margin-left: -4.5px;
  background: var(--bg);
  border: 1.5px solid var(--muted);
}

.home-scale-dot--baseline {
  top: 3.5px;
  width: 4px;
  height: 4px;
  margin-left: -2px;
  background: var(--muted);
}

/* A range is a value with an uncertainty around it, so the mid-point carries
   the same dot as any other figure and the range is a whisker through it. */
.home-scale-range {
  position: absolute;
  top: 5px;
  height: 1px;
}

.home-scale-range::before,
.home-scale-range::after {
  content: '';
  position: absolute;
  top: -3px;
  width: 1px;
  height: 7px;
  background: inherit;
}

.home-scale-range::before {
  left: 0;
}

.home-scale-range::after {
  right: 0;
}

.home-scale-range--figure {
  background: var(--accent);
}

.home-scale-range--comparison {
  background: var(--muted);
}

/* The live estimate is the page's headline number, not a seventh figure. It
   stands on its own between the search and the first box, with no frame:
   centred there it introduces the boxes rather than sitting inside one. It
   used to borrow the key figure's class names, which made every rule below
   have to say which of the two it meant. */
.home-live {
  width: 100%;
  max-width: 900px;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 10px;
  padding: 2px 0 0;
  background: none;
  border: 0;
  border-radius: 0;
  cursor: pointer;
  font-family: inherit;
  color: inherit;
}

.home-live-number {
  display: inline-flex;
  align-items: baseline;
  justify-content: center;
  gap: 12px;
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: clamp(2.6rem, 8vw, 4.2rem);
  font-weight: 700;
  color: var(--accent);
  letter-spacing: -0.02em;
  line-height: 1;
}

.home-live-label {
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--text);
  line-height: var(--lh-ui);
}

.home-live:hover .home-live-label {
  color: var(--accent);
}

/* The footnote is three lines of qualification. Full width would set it in one
   long line that nobody finishes reading. */
.home-live-note {
  max-width: 68ch;
  margin-inline: auto;
  font-family: var(--font-data);
  font-size: var(--fs-xs);
  color: var(--muted);
  opacity: 0.85;
  line-height: var(--lh-ui);
}

.home-live-dot {
  display: inline-block;
  /* Scales with the number beside it, which is twice the size. */
  width: 11px;
  height: 11px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--accent);
  align-self: center;
  animation: home-live-pulse 2s ease-in-out infinite;
}

@keyframes home-live-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(var(--accent-rgb), 0.45); }
  50% { opacity: 0.6; box-shadow: 0 0 0 4px rgba(var(--accent-rgb), 0); }
}

@keyframes home-stat-live-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 0 0 rgba(112, 195, 226, 0.45); }
  50% { opacity: 0.6; box-shadow: 0 0 0 4px rgba(112, 195, 226, 0); }
}

/* ── Home data panels (municipality chart + trend) ──────── */
.home-panel {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 14px;
  padding: 18px;
  background: none;
  border-radius: 0;
  cursor: pointer;
  text-align: left;
  font-family: inherit;
  color: inherit;
  transition: background 0.2s ease;
}

.home-panel:hover {
  background: rgba(var(--accent-rgb), 0.05);
}

.home-cell-title--nav:hover,
.home-cell-title--nav:focus-visible {
  color: var(--text);
}

/* Underlined on hover and focus only: a column of nine underlined names reads
   as a list of links before it reads as a ranking. */
.home-area-link {
  color: inherit;
  text-decoration: none;
}

.home-area-link:hover,
.home-area-link:focus-visible {
  color: var(--accent);
  text-decoration: underline;
}

.home-muni-list {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

/* The name column takes a share of the cell rather than a fixed 108px, which
   clipped "Koivukylä-Havukoski" to "Koivukylä-Havuk..." once the cells grew. */
.home-muni-row {
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(50px, 1fr) auto;
  align-items: center;
  gap: 10px;
  font-size: var(--fs-xs);
}

.home-muni-name {
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-weight: 400;
}

.home-muni-bar-wrap {
  background: rgba(255, 255, 255, 0.04);
  border-radius: var(--radius-xs);
  height: 6px;
  overflow: hidden;
}

.home-muni-bar {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-xs);
  opacity: 0.7;
}

.home-postal-row {
  grid-template-columns: minmax(0, 1.7fr) minmax(40px, 1fr) auto;
}

.home-postal-code {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  margin-right: 4px;
  font-size: var(--fs-xs);
}

/* ── Trend panel ────────────────────────────────────────── */
.home-panel--trend {
  justify-content: center;
}

.home-trend-panel {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
}

.home-trend-panel .home-cell-note {
  margin-bottom: 12px;
}

.home-sparkline {
  width: 100%;
  height: 52px;
  display: block;
}

.home-trend-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 6px;
}

.home-trend-axis-label {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  font-size: var(--fs-xs);
  color: var(--muted);
  opacity: 0.85;
}

@media (max-width: 700px) {
  .home-box-grid--data {
    grid-template-columns: 1fr;
  }

  .home-box-grid--data > * {
    border-left-width: 0;
    border-top-width: 1px;
  }

  .home-box-grid--data > :first-child {
    border-top-width: 0;
  }
}

@media (max-width: 640px) {
  .home-stage {
    padding-top: calc(var(--safe-top) + var(--top-bar-height) + 28px);
    padding-left: 16px;
    padding-right: 16px;
    padding-bottom: 48px;
    gap: 28px;
  }
  .home-elm-shell {
    gap: 36px;
  }
  /* The desktop offsets are absolute pixels against a wider gap; against this
     one they would close the search block up against the buttons. */
  .home-elm-shell > .home-cta-row {
    margin-top: -12px;
  }
  .home-elm-shell > .area-search {
    margin-top: -18px;
  }
  .home-elm-shell > .home-quick {
    margin-top: -4px;
  }
  .home-box-grid--stats {
    grid-template-columns: 1fr;
  }

  .home-box-grid--stats > * {
    border-left-width: 0;
    border-top-width: 1px;
  }

  .home-box-grid--stats > :first-child {
    border-top-width: 0;
  }

  .home-stat .home-cell-title {
    min-height: 0;
  }
}

/* ── Home comparison section ──────────────────────────── */
.home-claims {
  width: 100%;
  max-width: 900px;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.home-claims-lead {
  margin: 0;
  font-size: var(--fs-sm);
  color: var(--muted);
  text-align: center;
}

.home-compare-card {
  /* It is an anchor, so it has to be told to behave like the cell it looks
     like: no underline, the page's own text colour, left-aligned. */
  text-decoration: none;
  color: inherit;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 16px 18px 18px;
  background: none;
  border-radius: 0;
  cursor: pointer;
  transition: background 0.2s ease;
}

.home-compare-card:hover {
  background: rgba(var(--accent-rgb), 0.05);
}

/* Same split as the ranked lists, and for the same reason: at a fixed 112px
   "Suomalaiset kansalaiset" was clipped to "Suomalaiset kans...". */
.home-compare-row {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(36px, 1fr) auto;
  align-items: center;
  gap: 10px;
}

/* Three cards to a row leave a name about 140px, which cut five of the nine
   group names short. A name wraps rather than disappearing behind an
   ellipsis. */
.home-compare-group {
  font-size: var(--fs-xs);
  color: var(--text);
  line-height: var(--lh-ui);
  /* "Suojeluperusteiset" is one word and cannot wrap, so it breaks rather than
     running under the bar beside it. */
  overflow-wrap: break-word;
}

.home-compare-bar-wrap {
  background: rgba(255, 255, 255, 0.05);
  border-radius: var(--radius-xs);
  height: 7px;
}

.home-compare-bar {
  height: 100%;
  background: var(--accent);
  border-radius: var(--radius-xs);
  opacity: 0.55;
}

.home-compare-bar--warm {
  background: var(--accent-warm);
  opacity: 0.8;
}

.home-compare-bar--hot {
  background: var(--accent-hot);
  opacity: 0.9;
}

/* Three figures of three rows each stop fitting side by side well before the
   six single figures do. */
@media (max-width: 860px) {
  .home-box-grid--compare {
    grid-template-columns: 1fr;
  }

  .home-box-grid--compare > * {
    border-left-width: 0;
    border-top-width: 1px;
  }

  .home-box-grid--compare > :first-child {
    border-top-width: 0;
  }
}

/* Two lines of label plus a year stop fitting in a third of the width. The
   lower bound keeps this off phone widths, where the earlier rule has already
   put every figure on its own row. */
@media (max-width: 780px) and (min-width: 641px) {
  .home-box-grid--stats {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .home-box-grid--stats > :nth-child(3) {
    border-top-width: 1px;
  }

  .home-box-grid--stats > :nth-child(4) {
    border-left-width: 1px;
  }

  .home-box-grid--stats > :nth-child(2n + 1) {
    border-left-width: 0;
  }

  .home-box-grid--stats > :nth-child(-n + 2) {
    border-top-width: 0;
  }
}

/* ── Home tab icon ──────────────────────────────────────── */
.top-bar-tab--home {
  padding: 4px 10px;
  display: flex;
  align-items: center;
}

.top-bar-home-icon {
  width: 18px;
  height: 18px;
  display: block;
}


/* ── Claims & Facts chart ───────────────────────────────── */
/* Two columns with a flow each rather than a multicol block. Multicol evens
   out the column boxes but cannot split a card, so four tall cards left the
   shorter column ending 48px above the taller one, and the teaser row that sat
   under both of them inherited that dead space. */
/* The columns stretch to the taller one's height so their teasers can end at
   the same line. With align-items:start each column stopped where its own
   content did, and the divider between them stopped there too. */
.claims-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
}

.claims-col {
  display: flex;
  flex-direction: column;
  min-width: 0;
}

.claims-col + .claims-col {
  border-left: 1px solid var(--home-line-soft);
}

/* A cell in the claims box, not a card of its own. The rule sits on top of
   each card so the last one in a column never doubles up against the frame,
   whatever height that column happens to end at. */
.claim-card {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 18px;
  background: none;
  border: 0;
  border-top: 1px solid var(--home-line-soft);
  border-radius: 0;
}

.claims-col > .claim-card:first-child {
  border-top: 0;
}


/* Four cards show by default. The next two render clipped under a gradient
   that dissolves into the page background -- the tease -- and a bare round
   plus reveals more. The gradient endpoints must match .home-stage's
   background-color per theme, or the fade reads as a grey smudge. */
.claims-grid-wrap {
  position: relative;
  border: 1px solid var(--home-line);
  border-radius: var(--radius-md);
  overflow: hidden;
}

/* The tease: the next card clipped under the fade, at the foot of the column
   it will belong to once revealed. */
/* The fade belongs to the card, not to the foot of the grid. It used to be one
   180px band across the bottom of the wrapper, which worked only while both
   teasers sat at the same height -- true when they shared one row, false since
   each column got its own flow. Measured at 1295px: after one press of the plus
   the columns differ by 172px and the shorter column's teaser rises 116px above
   the band, so it ended as a hard cut through a sentence. A mask travels with
   the card, whatever height it lands at, and needs no colour of its own: it
   fades the card's own pixels out, so it is right in both themes by
   construction. */
.claim-card--teaser {
  /* 108px is the base, not the cap. The taller column has no free space and
     its teaser stays at the base; the shorter column's teaser grows into the
     difference, so the two fades end on the same line instead of one of them
     floating with dead space under it. Measured at 1400px: the columns drifted
     12px apart at first, then 172, 272 and 289 as the plus revealed more. */
  flex: 1 1 108px;
  min-height: 108px;
  overflow: hidden;
  /* A fixed band rather than a share of the height, so a teaser that grew to
     three times the base does not spend two thirds of itself fading. */
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 84px), transparent 100%);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 84px), transparent 100%);
}

/* Newly mounted cards cascade in. Animation runs once per DOM node, so cards
   already on screen do not replay when more are revealed below them. */
.claims-grid .claim-card {
  animation: claim-reveal 0.4s ease both;
}

@keyframes claim-reveal {
  from {
    opacity: 0;
    transform: translateY(14px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .claims-grid .claim-card {
    animation: none;
  }
}

.claims-more-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 46px;
  height: 46px;
  /* It used to be pulled up onto the fade, which read as "there is more under
     here" while the fade was the only edge. The box has a frame now, and a
     button straddling that frame reads as a mistake. The column's own gap
     places it. */
  margin: 0 auto;
  padding: 0;
  background: rgba(112, 195, 226, 0.07);
  border: 1px solid rgba(112, 195, 226, 0.25);
  border-radius: 50%;
  color: var(--accent-dim);
  font: inherit;
  font-size: 1.5rem;
  font-weight: 300;
  line-height: 1;
  cursor: pointer;
}

.claims-more-btn:hover {
  color: var(--accent);
  border-color: var(--accent);
  background: rgba(112, 195, 226, 0.12);
}

.claim-row {
  display: flex;
  align-items: flex-start;
  gap: 10px;
}

.claim-row--fact {
  padding-top: 10px;
  border-top: 1px solid var(--border);
}

.claim-badge {
  flex-shrink: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  background: rgba(112, 195, 226, 0.07);
  color: var(--accent-dim);
  border: 1px solid rgba(112, 195, 226, 0.11);
  border-radius: var(--radius-sm);
  padding: 2px 7px;
  margin-top: 2px;
  white-space: nowrap;
}

.claim-badge--fact {
  background: rgba(100, 220, 120, 0.07);
  color: #4db060;
  border-color: rgba(100, 220, 120, 0.12);
}

.claim-text {
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: var(--lh-ui);
  margin: 0;
  font-style: italic;
}

.claim-fact {
  font-size: var(--fs-sm);
  color: var(--text);
  line-height: var(--lh-prose);
  margin: 0;
}

.claim-fact strong {
  color: var(--accent);
  font-weight: 700;
}

.claim-card--hot .claim-fact strong {
  color: var(--accent);
}

.claim-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  padding-top: 8px;
  border-top: 1px solid rgba(255, 255, 255, 0.06);
}

.claim-sources {
  display: flex;
  flex: 1 1 240px;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.claim-sources-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
}

.claim-source-link,
.claim-detail-link {
  display: inline-flex;
  align-items: center;
  min-height: 24px;
  max-width: 100%;
  border: 1px solid rgba(112, 195, 226, 0.18);
  border-radius: var(--radius-sm);
  padding: 3px 7px;
  background: rgba(112, 195, 226, 0.05);
  color: var(--accent);
  font-size: var(--fs-xs);
  line-height: 1.25;
  text-decoration: none;
  overflow-wrap: anywhere;
}

.claim-source-link:hover,
.claim-detail-link:hover {
  border-color: rgba(112, 195, 226, 0.36);
  background: rgba(112, 195, 226, 0.1);
  text-decoration: none;
}

.claim-detail-link {
  flex: 0 0 auto;
  margin-left: auto;
  font-weight: 700;
}

@media (max-width: 700px) {
  /* One column, and the cards have to come back in their real order: the two
     columns split them by index, so stacking the columns would show every even
     card and then every odd one. display:contents makes the cards themselves
     the flex items, and the order each one carries puts the sequence back. */
  .claims-grid {
    display: flex;
    flex-direction: column;
    gap: 14px;
  }

  .claims-col {
    display: contents;
  }

  .claims-col + .claims-col {
    border-left: 0;
  }

  .claims-col:last-child > .claim-card:first-child {
    border-top: 1px solid var(--home-line-soft);
  }
}

/* ── Claim card hover accent bar ────────────────────────── */
.claim-card {
  position: relative;
  overflow: hidden;
  transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}

.claim-card::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}

.claim-card:hover {
  background: rgba(112, 195, 226, 0.09);
  border-color: rgba(112, 195, 226, 0.3);
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
}

.claim-card:hover::before {
  transform: scaleX(1);
}

/* Keep these after the base chart TOC rules so the desktop sidebar does not win on mobile. */
@media (max-width: 768px) {
  body[data-view="chart"] #chartStage,
  html:not(.js) body:has(#chartStage:target) #chartStage {
    padding: calc(var(--safe-top) + var(--top-bar-height) + 10px) 10px 0;
  }

  .chart-stage {
    gap: 12px;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    overflow-x: hidden;
  }

  .chart-stage-col {
    flex-basis: 100%;
    max-width: 100%;
    padding-top: 0;
    padding-left: 0;
    transition: transform var(--chart-toc-transition);
  }

  /* The drawer itself is defined once, further down in this file, where the
     rules carry enough specificity to win on their own. */

  .chart-stage.is-toc-open .chart-stage-col {
    padding-left: 0;
    transform: translateX(100%);
  }

  body[data-view="chart"] .map-share-btn,
  html:not(.js) body:has(#chartStage:target) .map-share-btn {
    display: none;
  }

  .chart-toc-toggle-btn {
    left: 10px;
    top: calc(var(--safe-top) + var(--top-bar-height) + 8px);
  }
}

@media (max-width: 480px) {
  body[data-view="chart"] #chartStage,
  html:not(.js) body:has(#chartStage:target) #chartStage {
    padding: calc(var(--safe-top) + var(--top-bar-height) + 8px) 8px 0;
  }

  .chart-stage {
    gap: 10px;
  }

  .chart-toc-toggle-btn {
    left: 8px;
    top: calc(var(--safe-top) + var(--top-bar-height) + 6px);
  }
}

/* ──────────────────────────────────────────────────────────────────────────
   MOBILE USABILITY IMPROVEMENTS (OVERLAYS, BOTTOM SHEETS, SCROLLABLE CHARTS)
   ────────────────────────────────────────────────────────────────────────── */

/* 1. Header TOC Toggles */
/* Back to the top of the knowledge base. The view is the site's longest and it
   scrolls its own container rather than the document, so neither the browser's
   own gesture nor a plain anchor gets a reader back up. The button is fixed to
   the viewport and only exists while the knowledge view is showing; JS reveals
   it once the reader is a screenful down and hides it again at the top, so it
   never covers content the reader has not scrolled past. */
.kb-to-top-btn {
  position: fixed;
  right: calc(var(--safe-right, 0px) + 18px);
  bottom: calc(var(--safe-bottom, 0px) + 18px);
  z-index: 60;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  padding: 0;
  border: 1px solid var(--panel-border);
  border-radius: var(--radius-pill);
  background: var(--panel-bg);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  color: var(--accent);
  cursor: pointer;
  opacity: 0;
  transform: translateY(6px);
  transition: opacity 0.18s ease, transform 0.18s ease, border-color 0.18s ease;
}

.kb-to-top-btn[hidden] {
  display: none;
}

.kb-to-top-btn.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.kb-to-top-btn:hover {
  border-color: var(--accent);
}

.kb-to-top-btn svg {
  width: 18px;
  height: 18px;
  display: block;
}

/* The card the button floats over is nearly opaque, so on a phone a solid fill
   reads better than the blurred panel token. */
@media (max-width: 700px) {
  .kb-to-top-btn {
    right: calc(var(--safe-right, 0px) + 14px);
    bottom: calc(var(--safe-bottom, 0px) + 14px);
  }
}

@media (prefers-reduced-motion: reduce) {
  .kb-to-top-btn {
    transition: opacity 0.18s ease;
    transform: none;
  }

  .kb-to-top-btn.is-visible {
    transform: none;
  }
}

body[data-view] #kbTocToggleBtn {
  display: none;
}

/* Show the KB TOC toggle on desktop too (like the map menu). The history view
   has a contents column of its own and shares the button. */
body[data-view="knowledge"] #kbTocToggleBtn,
body[data-view="history"] #kbTocToggleBtn {
  display: flex;
}

@media (max-width: 700px) {
  body[data-view="knowledge"] #kbTocToggleBtn,
  body[data-view="history"] #kbTocToggleBtn {
    display: flex;
  }
}

/* 2. Knowledge Base TOC — full-screen drawer sliding in from the left on mobile */
@media (max-width: 700px) {
  .kb-shell .kb-toc {
    position: fixed;
    top: var(--top-bar-height);
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    max-height: none;
    margin: 0;
    z-index: 1010;
    background: var(--surface-panel);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: none;
    border-top: 1px solid var(--accent-dim);
    border-radius: 0;
    box-shadow: var(--shadow);
    padding: 16px 16px max(16px, var(--safe-bottom));
    overflow-y: auto;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear 0.28s;
  }

  /* Push the page content out of the way (instead of just overlaying it) as
     the drawer slides in — .kb-toc and .kb-content are direct siblings. */
  .kb-content {
    transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1);
  }

  .kb-shell .kb-toc.is-open ~ .kb-content {
    transform: translateX(100%);
  }

  .kb-shell .kb-toc.is-open {
    transform: translateX(0);
    visibility: visible;
    transition: transform 0.28s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear 0s;
  }
}

/* 3. Collapsible Map Filters Bottom Sheet (#utilityPanel) */
@media (max-width: 920px) {
  .utility-panel {
    position: fixed;
    top: auto;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    max-width: none;
    height: auto;
    max-height: 75dvh;
    border-radius: 18px 18px 0 0;
    border: 1px solid var(--border);
    border-bottom: none;
    transform: translateY(100%);
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease;
    /* Keep the sheet on its own compositing layer at all times so mobile
       browsers animate the slide-out on close instead of snapping it shut
       (same reason as .chart-sidebar; combined with position:fixed here). */
    will-change: transform;
    opacity: 0;
    pointer-events: none;
    visibility: visible;
    /* Clear the drag handle by insetting the inner scroller, not the outer
       sheet (padding now lives on .utility-panel-scroll). */
    --panel-pad-top: 28px;
    z-index: 999;
  }

  .utility-panel.is-open {
    transform: translateY(0) scale(1);
    opacity: 1;
    pointer-events: auto;
  }

  /* Bottom sheet drag handle indicator */
  .utility-panel::before {
    content: "";
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    width: 40px;
    height: 4px;
    border-radius: 2px;
    background: rgba(112, 195, 226, 0.3);
    display: block;
    opacity: 1;
    pointer-events: none;
  }
}

/* 4. Collapsible Chart Filters Bottom Sheets (.chart-sidebar) */
.chart-filters-toggle-btn {
  display: none;
}

/* The pill that presses the top bar's spectacles from a chart's own row. A
   button inherits the pill look from .chart-xlink and only needs the UA
   button styling cleared. Pressed, it reads as the state it will undo. On a
   phone the row is full and the top-bar icon is always in view. */
.chart-advanced-btn {
  font: inherit;
  font-size: var(--fs-xs);
  line-height: 1;
}

/* Icon only at every width: the row's other pills spell their text out above
   1280 px, and a fourth spelled-out pill pushed the chart's title into an
   ellipsis. The tooltip and the aria-label carry the words. */
.chart-advanced-btn .chart-advanced-btn-label {
  display: none;
}

.chart-advanced-btn[aria-pressed="true"] {
  background: rgba(var(--accent-rgb), 0.16);
  border-color: var(--accent);
  opacity: 1;
}

@media (max-width: 700px) {
  .chart-advanced-btn {
    display: none;
  }
}

@media (max-width: 768px) {
  .chart-container-shell {
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    content-visibility: visible;
  }

  /* Icon only, like the three controls beside it: at 390px the word
     "Valinnat" was 106px of a 348px row and pushed the share button out of the
     card. The label lives on in the tooltip and the aria-label. */
  .chart-filters-toggle-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    height: 32px;
    min-width: 32px;
    padding: 0 8px;
    border-radius: var(--radius-pill);
    border: 1px solid var(--border);
    background: rgba(10, 14, 22, 0.9);
    cursor: pointer;
    margin-right: 8px;
    color: var(--text);
  }

  .chart-filters-toggle-btn span {
    display: none;
  }

  .chart-filters-toggle-btn svg {
    width: 15px;
    height: 15px;
  }

  .chart-filters-toggle-btn.is-open {
    border-color: var(--accent);
    color: var(--accent);
  }

  .chart-sidebar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    width: 100%;
    max-height: 70dvh;
    background: rgba(8, 12, 18, 0.96);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border-top: 1px solid var(--border);
    border-radius: 0;
    z-index: 1100;
    padding: 28px 16px 16px;
    box-shadow: 0 -10px 32px rgba(0, 0, 0, 0.5);
    transform: translateY(100%);
    transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    /* Hints the browser to keep this on its own compositing layer at all
       times, not just while transitioning — without it, some mobile
       browsers drop the transform transition (snap instead of slide),
       particularly combined with backdrop-filter + position:fixed here. */
    will-change: transform;
    pointer-events: none;
    opacity: 0;
    overflow-y: auto;
  }

  .chart-sidebar.is-open {
    transform: translateY(0);
    pointer-events: auto;
    opacity: 1;
  }

  /* Bottom sheet drag handle indicator — tapping it closes the sheet (see
     the ".chart-sidebar.is-open" tap-position check in app_share.js). */
  .chart-sidebar::before {
    content: "";
    position: absolute;
    top: 10px;
    left: 50%;
    transform: translateX(-50%);
    width: 40px;
    height: 4px;
    border-radius: var(--radius-xs);
    background: rgba(112, 195, 226, 0.3);
    cursor: pointer;
  }

  /* Chart TOC drawer — to win over the legacy persistent-sidebar
     (desktop) and older collapsible-block (mobile) rules for the same
     selectors elsewhere in this stylesheet. */
  /* Full-screen drawer sliding in from the left, matching the Knowledge Base
     TOC — pushes .chart-stage-col out of the way instead of overlaying it.

     Written as two state selectors rather than a bare .chart-toc-wrap so the
     specificity matches the desktop rules it has to beat
     (.chart-stage:not(.is-toc-open) .chart-toc-wrap sets width: 0). At equal
     specificity the later rule wins, which is this one. That is what removed
     the !important these declarations used to carry. */
  .chart-stage:not(.is-toc-open) .chart-toc-wrap,
  .chart-stage.is-toc-open .chart-toc-wrap {
    position: fixed;
    top: var(--top-bar-height);
    left: 0;
    right: 0;
    bottom: 0;
    width: 100%;
    height: calc(100dvh - var(--top-bar-height));
    max-height: none;
    margin: 0;
    z-index: 1010;
    background: var(--surface-panel);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    padding: 16px 16px max(16px, var(--safe-bottom));
    overflow-y: auto;
  }

  .chart-stage:not(.is-toc-open) .chart-toc-wrap {
    /* opacity, pointer-events and padding-top used to come from the desktop
       collapse rule. Stated here so the closed state does not depend on a
       rule written for another breakpoint. */
    opacity: 0;
    pointer-events: none;
    padding-top: 0;
    transform: translateX(-100%);
    visibility: hidden;
    transition: transform var(--chart-toc-transition), visibility 0s linear 260ms;
  }

  .chart-stage.is-toc-open .chart-toc-wrap {
    opacity: 1;
    pointer-events: auto;
    padding-top: 16px;
    transform: translateX(0);
    visibility: visible;
    transition: transform var(--chart-toc-transition), visibility 0s linear 0s;
  }

  .chart-stage-col {
    transition: transform var(--chart-toc-transition);
  }

  .chart-stage.is-toc-open .chart-stage-col {
    padding-left: 0;
    transform: translateX(100%);
  }
}

@media (max-width: 768px) {
  body.light-theme .chart-sidebar {
    background: #ffffff;
    border-top-color: rgba(0, 0, 0, 0.1);
    box-shadow: 0 -10px 32px rgba(0, 0, 0, 0.12);
  }

  body.light-theme .chart-sidebar::before {
    background: rgba(0, 0, 0, 0.18);
  }

  body.light-theme .chart-filters-toggle-btn {
    background: #ffffff;
    border-color: rgba(82, 99, 115, 0.2);
    color: var(--text);
  }

  body.light-theme .chart-filters-toggle-btn.is-open {
    border-color: var(--accent);
    color: var(--accent);
  }
}

/* 5. Scrollable Bar Charts on Mobile */
.chart-scroll-wrapper {
  width: 100%;
  height: 100%;
  position: relative;
}

/* The grouped bar charts send a mobile-only minWidth computed from their
   group count (mobileGroupedChartMinWidth in app_charts_core.js), so on narrow
   screens their SVG renders wider than the viewport and this wrapper scrolls.
   The single-series bar charts send one too, but ChartView.elm lays their bars
   down in rows instead (viewBarRows), so they never exceed this box. This
   wrapper is what the host measures for that width (its own box stays clipped
   to the viewport; only its *content*, the wider SVG, needs to scroll), so it
   must not get a fixed width of its own — that was the 2026-07-15 attempt
   (flat 640px) and it left a dead scrollbar on charts with too few bars to
   fill 640px, e.g. migriWrap's default 2-country view. Charts that don't
   send minWidth (lines) never exceed this box, so overflow-x here is a no-op
   for them. */
@media (max-width: 768px) {
  .chart-scroll-wrapper {
    overflow-x: auto;
  }

  /* A cut-off bar with no scrollbar reads as a broken chart rather than a
     scrollable one: on a phone the last category is sliced down its middle with
     nothing to say the rest is a swipe away.

     The cue is a mask on the chart itself, not a painted layer. The two-layer
     "scroll shadow" this replaces put an opaque cover over the box's right edge
     from the very start of the scroll -- `local` anchors to the padding box plus
     the scroll offset, not to the far edge of the content -- so it darkened the
     plot exactly where it meant to hint. A mask needs no colour to match, works
     the same in both themes, and fades the drawing rather than covering it.

     JS keeps the two classes honest: .is-scrollable while the content overflows,
     .is-at-end once the reader has reached the far side, where a cue would be a
     lie. */
  .chart-scroll-wrapper.is-scrollable:not(.is-at-end) {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 34px), transparent);
  }
}

/* 6. Anchored Tooltips for Mobile Charts */
@media (max-width: 768px) {
  .chart-elm-tooltip {
    position: absolute;
    left: 8px;
    top: 8px;
    transform: none;
    box-shadow: none;
    background: rgba(10, 14, 22, 0.9);
    border-color: var(--accent-dim);
    z-index: 30;
    pointer-events: none;
  }
}

/* 8. Mobile Landscape / Horizontal View Adjustments */
/* max-width covers common large-phone landscape widths too (e.g. iPhone 14/15
   Pro Max at 926px) — without this floor their .chart-main sits at flex:1 1 0
   with no minimum, and can be squeezed to 0 height by the row-layout sidebar
   next to it. */
@media (max-width: 960px) and (max-height: 480px) {
  .chart-main,
  .nat-bar-chart-main,
  .subsidy-bar-chart-main,
  .lang-speakers-chart-main {
    height: 140px !important;
    min-height: 140px !important;
    max-height: 140px !important;
  }
}

/* Article disclosure — articles are the weakest evidence in a section, so where
   the section also has charts or studies they collapse behind this. Sections
   with nothing but articles render them open (see sectionHasEvidence). */
.kb-articles-disclosure {
  display: block;
  margin-top: 18px;
  border-top: 1px solid var(--panel-rule);
  padding-top: 6px;
}

.kb-articles-disclosure-header {
  cursor: pointer;
  user-select: none;
  outline: none;
  list-style: none;
  padding: 6px 2px;
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  color: var(--muted);
  transition: color 0.2s;
}

.kb-articles-disclosure-header::before {
  content: "▶";
  font-size: var(--fs-xs);
  opacity: 0.7;
  transition: transform 0.15s;
  transform-origin: center;
  flex-shrink: 0;
}

.kb-articles-disclosure[open] .kb-articles-disclosure-header::before {
  transform: rotate(90deg);
}

.kb-articles-disclosure-header::-webkit-details-marker {
  display: none;
}

.kb-articles-disclosure-header:hover {
  color: var(--text);
}

/* The generic `details` rule near the top of this file gives every disclosure a
   card box (border, radius, panel background). This one inherits it, so it needs
   side padding of its own — without it the article cards sit flush against the
   panel edge, 1px of border apart, and read as boxes bursting out of their
   container. 10px inset also lines the header text up with the card titles
   (10 + marker 8 + gap 6 = 24; card text 10 + border + 12 = 23).
   Vertical padding lives on the header, not the panel, so the label is centred
   in the collapsed state instead of riding high. */
.kb-articles-disclosure {
  padding: 0 10px;
}

.kb-articles-disclosure[open] {
  padding-bottom: 12px;
}

.kb-articles-disclosure-header {
  padding: 10px 0;
}

/* Folded journalism is the section's least important material, so its cards sit
   tighter than the open lists above them: less padding, less air between them,
   and a smaller radius to match. */

.kb-articles-disclosure .kb-articles {
  gap: 6px;
  margin-top: 10px;
}

.kb-articles-disclosure .kb-article {
  padding: 9px 12px;
  border-radius: var(--radius-sm);
  box-shadow: none;
}

.kb-articles-disclosure .kb-article-header {
  gap: 3px;
  margin-bottom: 3px;
}

.kb-articles-disclosure .kb-article-summary {
  margin-top: 4px;
}

.kb-articles-disclosure-header:focus-visible {
  outline: 2px solid var(--accent, #4aa3ff);
  outline-offset: 2px;
}

/* A closed disclosure carries no bottom margin, so a country block that
   follows it (the per-country survey layout, after a section's own folded
   articles) would otherwise sit flush under the "Artikkeleita (N)" row
   instead of getting the usual gap between countries. */
.kb-articles-disclosure + .kb-country {
  margin-top: 32px;
}

/* ── "Tiesitkö?" fact card on the home view ──────────────────────────────────
   One random fact from the site's data with a deep link; sits between the
   quick links and the stats row. Follows the .claim-card visual idiom. */
.home-fact-card {
  display: flex;
  flex-direction: column;
  gap: 6px;
  width: 100%;
  padding: 16px 18px 18px;
  background: none;
  border-radius: 0;
  /* The whole cell is an anchor to the fact's chart or section. */
  cursor: pointer;
  text-decoration: none;
  color: inherit;
  transition: background 0.2s ease;
}

.home-fact-card:hover {
  background: rgba(var(--accent-rgb), 0.05);
}

/* Shuffling swaps the keyed fact body, so each new fact replays this. */
@keyframes home-fact-swap {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.home-fact-text {
  animation: home-fact-swap 0.35s ease both;
}

.home-fact-card:hover .home-fact-link {
  text-decoration: underline;
}

/* Only the shuffle control lives here now, so it sits at the end of the row
   rather than at whichever end a second item left free. */
.home-fact-head {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 10px;
}

/* The page's other "give me another" control, the plus under the claims, is a
   circle. This was a small rounded square. */
.home-fact-shuffle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  background: none;
  border: 1px solid var(--home-line);
  border-radius: 50%;
  color: var(--accent);
  font-size: var(--fs-sm);
  line-height: 1;
  cursor: pointer;
}

.home-fact-shuffle:hover {
  background: rgba(112, 195, 226, 0.1);
}

/* The fact pool runs from 39 to 320 characters, so shuffling used to resize the
   card and shove the whole page below it up or down. The body reserves the room
   the LONGEST fact needs at this width and centres whatever it gets, so the card
   is the same height whichever fact is showing.

   --fact-lines is measured, not guessed: 4 lines covers every fact in both
   languages at full width, so the card there is the same height whichever fact
   is showing. One line is 1.45 (the text's line-height) x 0.92rem (its
   font-size), so those two numbers move together with the line-height below.

   Below 560px the reserve is short of a 9-line worst case on purpose: covering
   that would add ~45px of empty card on the narrowest screens. Rather than pay
   that, or let the rare long fact shove the page around, the POOL is kept
   inside the reserve -- at 390px the longest fact in either language lands
   exactly on it. Keep it that way: a fact over ~250 characters will nudge the
   page on a phone. `node repo_support/validate_site_facts.mjs` does not catch
   this; it is a length budget, not a data error. */
.home-fact-body {
  --fact-lines: 4;
  min-height: calc(var(--fact-lines) * 1.45 * 0.92rem);
  display: flex;
  flex-direction: column;
  justify-content: center;
}

.home-fact-text {
  margin: 0;
  font-size: var(--fs-md);
  line-height: var(--lh-ui);
  color: var(--text);
}

.home-fact-text strong {
  color: var(--accent);
  font-weight: 700;
}

.home-fact-link {
  align-self: flex-end;
  font-size: var(--fs-sm);
  color: var(--accent);
  text-decoration: none;
}

.home-fact-link:hover {
  text-decoration: underline;
}

/* Below ~700px the card is no longer at its 720px cap and the longest fact
   wraps to five lines. */
@media (max-width: 700px) {
  .home-fact-body { --fact-lines: 5; }
}

/* On a phone the card is ~316px wide and the four longest facts run to eight or
   nine lines. Reserving all nine would leave five empty lines under the typical
   three- or four-line fact, which is a worse card than a moving one. Seven holds
   95 of the 99 facts still; the last four keep nudging the page, which is the
   deliberate trade -- see FEATURES.md. */
@media (max-width: 430px) {
  .home-fact-body { --fact-lines: 7; }
}

body.light-theme .home-fact-card:hover {
  background: rgba(var(--accent-rgb), 0.06);
}

/* ── Info view ──────────────────────────────────────────────────────────────
   About-the-site cards plus the feature changelog. Everything is built from
   the theme tokens, so light mode follows without a second palette. */
.info-stage {
  flex-direction: column;
  align-items: stretch;
  overflow-y: auto;
  padding: 0;
  min-height: 0;
}

.site-info-page {
  width: 100%;
  padding: 28px 20px 72px;
}

.site-info-inner {
  max-width: 860px;
  margin: 0 auto;
}

.site-info-title {
  margin: 0 0 10px;
  font-size: clamp(1.625rem, 3.4vw, 2.25rem);
  font-weight: 650;
  letter-spacing: -0.02em;
  color: var(--text);
}

.site-info-lead {
  margin: 0 0 34px;
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
  color: var(--muted);
  max-width: 62ch;
}

.site-info-section {
  margin-bottom: 40px;
}

.site-info-section-title {
  margin: 0 0 6px;
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--accent);
}

.site-info-section-lead {
  margin: 0 0 18px;
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
  color: var(--muted);
  max-width: 62ch;
}

/* The API catalog address, styled as one clear action row rather than a link
   buried in prose: label on the left, the path in mono. */
.site-info-api-catalog {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  margin: 2px 0 16px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--panel-soft);
  text-decoration: none;
  max-width: 100%;
}

.site-info-api-catalog:hover {
  border-color: var(--accent);
}

.site-info-api-catalog-label {
  font-size: var(--fs-sm);
  font-weight: 620;
  color: var(--text);
  white-space: nowrap;
}

.site-info-api-catalog-path {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--fs-sm);
  color: var(--accent);
  overflow-wrap: anywhere;
}

.site-info-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  /* The cards carry no box, so the gap is what separates them. It has to be
     wide enough that two columns of short prose cannot read as one flowing
     column, which 12px was not. */
  gap: 26px 40px;
  margin-top: 20px;
}

/* Four cards read as a 2 x 2 block rather than three and a straggler. */
.site-info-cards--pairs {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (max-width: 720px) {
  .site-info-cards--pairs {
    grid-template-columns: 1fr;
  }
}

/* A heading and a paragraph in a column need no box. These are not controls
   and nothing here is clickable, so the border and fill only repeated what the
   heading and the gap already say. The disclosures below keep theirs, because
   a box there means "this opens". */
.site-info-card {
  padding: 0;
}

.site-info-card-title {
  margin: 0 0 7px;
  font-size: var(--fs-md);
  font-weight: 620;
  color: var(--text);
}

.site-info-card-body {
  margin: 0;
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
  color: var(--muted);
}

/* Sources registry: one block per publisher, each a list of datasets. The rows
   carry a lot of small print, so the type scale steps down rather than the rows
   getting denser — this is a page people read when they are checking something,
   not skimming. */
.site-info-sources {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* The method notes are six short, unrelated cards, so they read better across
   than stacked: a single column made the section look longer than it is and
   pushed the source list below the fold.

   Height is two cases, not one. Closed, the cards should match: a three-line
   title next to two-line ones made the row look broken, which is what
   `align-items: start` did on its own. Open, they must NOT match, or expanding
   one would stretch its row-mates into tall empty boxes. So the grid stretches
   only while every card in it is closed. */
.site-info-methods {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: start;
  gap: 12px;
  margin-top: 16px;
}

.site-info-methods:not(:has(> .site-info-method[open])) {
  align-items: stretch;
  /* Rows match each other too, not just the cards within a row: a two-line row
     under a three-line one is the same unevenness one step up. */
  grid-auto-rows: 1fr;
}

@media (max-width: 1080px) {
  .site-info-methods {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 700px) {
  .site-info-methods {
    grid-template-columns: minmax(0, 1fr);
  }
}

.site-info-sources {
  margin-top: 26px;
}

.site-info-method,
.site-info-source-group {
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  background: var(--panel-soft);
  padding: 12px 16px 4px;
}

.site-info-method-summary,
.site-info-source-group-summary {
  display: flex;
  align-items: baseline;
  gap: 10px;
  cursor: pointer;
  list-style: none;
  padding-bottom: 8px;
}

.site-info-method-summary::-webkit-details-marker,
.site-info-source-group-summary::-webkit-details-marker {
  display: none;
}

/* Own marker, so it sits before the title and turns with the open state. */
.site-info-method-summary::before,
.site-info-source-group-summary::before {
  content: "";
  flex: none;
  width: 0;
  height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  color: var(--accent-dim);
  transform: translateY(-1px);
  transition: transform 120ms ease;
}

.site-info-method[open] > .site-info-method-summary::before,
.site-info-source-group[open] > .site-info-source-group-summary::before {
  transform: translateY(-1px) rotate(90deg);
}

.site-info-source-count {
  margin-left: auto;
  font-size: var(--fs-sm);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  /* Let the group title take the wrap instead: the count is two words and
     broke across two lines next to the longer publisher names on a phone. */
  white-space: nowrap;
}

.site-info-method-title,
.site-info-source-group-title {
  margin: 0;
  display: inline;
  font-size: var(--fs-md);
  font-weight: 640;
  color: var(--text);
}

.site-info-method-body {
  margin: 2px 0 12px;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
  max-width: 74ch;
}

.site-info-source-group-lead {
  margin: 2px 0 12px;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
  max-width: 62ch;
}

.site-info-source-list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--line);
}

.site-info-source {
  padding: 11px 0 12px;
  border-bottom: 1px solid var(--line);
}

.site-info-source-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
}

.site-info-source-title {
  font-size: var(--fs-md);
  font-weight: 560;
  color: var(--text);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  padding-block: 3px;
}

.site-info-source-title:hover,
.site-info-source-title:focus-visible {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

.site-info-source-meta {
  margin-top: 3px;
  font-size: var(--fs-sm);
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
}

.site-info-source-ref {
  font-variant-numeric: tabular-nums;
}

.site-info-source-period::before {
  content: "·";
  margin-right: 10px;
  opacity: 0.6;
}

.site-info-source-note {
  margin: 7px 0 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
  max-width: 74ch;
}

/* The refresh state is the one thing a reader scans for, so it gets a colour
   rather than another line of prose. */
.site-info-refresh {
  flex: none;
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--border);
  color: var(--muted);
  white-space: nowrap;
}

.site-info-refresh--daily {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 42%, transparent);
}

.site-info-refresh--derived {
  color: #e0a33e;
  border-color: color-mix(in srgb, #e0a33e 42%, transparent);
}

@media (max-width: 620px) {
  .site-info-source-head {
    flex-direction: column;
    align-items: flex-start;
    gap: 5px;
  }
}

/* Changelog: a date rail on the left, entries on the right. The rail collapses
   above the entries on narrow screens. */
.site-info-log-day {
  display: grid;
  grid-template-columns: 116px 1fr;
  gap: 18px;
  padding: 16px 0;
  border-top: 1px solid var(--line);
}

.site-info-log-day:first-child {
  border-top: 0;
  padding-top: 4px;
}

.site-info-log-foot {
  margin: 18px 0 0;
  padding-top: 16px;
  border-top: 1px solid var(--line);
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
}

.site-info-log-date {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--accent-dim);
  font-variant-numeric: tabular-nums;
  padding-top: 2px;
}

/* One grid for the whole list rather than a flex row per entry, so the badge
   column is sized by the widest badge across every entry and all explanation
   text starts at the same x. A per-entry flex row let each row size its own
   badge, which left the text ragged: PARANNETTU is wider than UUTTA. The li
   becomes display:contents so its two spans are the grid items. */
.site-info-log-entries {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  grid-template-columns: max-content minmax(0, 1fr);
  column-gap: 10px;
  row-gap: 9px;
  align-items: baseline;
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
}

.site-info-log-entry {
  display: contents;
}

.site-info-log-text {
  color: var(--text);
}

.site-info-tag {
  flex: 0 0 auto;
  /* Wide enough for the longest label in either language (PARANNETTU measures
     95px, Improved 79px), so every badge is the same width and the explanation
     text lines up across days, not just within one day's grid. */
  min-width: 100px;
  text-align: center;
  padding: 2px 8px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-size: var(--fs-xs);
  font-weight: 640;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  white-space: nowrap;
  transform: translateY(-1px);
}

.site-info-tag--new {
  color: var(--accent);
  border-color: rgba(var(--accent-rgb), 0.4);
  background: rgba(var(--accent-rgb), 0.12);
}

.site-info-tag--improved {
  color: var(--accent-dim);
  border-color: var(--border);
  background: var(--panel-soft);
}

.site-info-tag--fixed {
  color: #4ea66e;
  border-color: rgba(78, 166, 110, 0.42);
  background: rgba(78, 166, 110, 0.12);
}

.site-info-tag--removed {
  color: var(--muted);
  border-color: var(--line);
  background: transparent;
}

.site-info-empty {
  margin: 0;
  font-size: var(--fs-md);
  color: var(--muted);
}

@media (max-width: 720px) {
  .site-info-page {
    padding: 20px 16px 64px;
  }

  .site-info-log-day {
    grid-template-columns: 1fr;
    gap: 8px;
  }

  .site-info-log-entry {
    flex-direction: column;
    gap: 4px;
  }

  .site-info-tag {
    align-self: flex-start;
    min-width: 0;
    transform: none;
  }
}

/* The info tab is an icon, so it needs the square footprint the word tabs get
   from their text. */
.top-bar-tab--info {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding-inline: 9px;
}

.top-bar-info-icon {
  width: 17px;
  height: 17px;
  display: block;
}

/* -----------------------------------------------------------------
   Site-wide nav search (SiteSearch.elm island, #siteSearchMount)
   ----------------------------------------------------------------- */

.site-search-icon {
  width: 17px;
  height: 17px;
}

.site-search-overlay {
  position: fixed;
  inset: 0;
  z-index: 3000;
  background: rgba(3, 8, 12, 0.62);
  backdrop-filter: blur(3px);
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 12vh 16px 16px;
}

.site-search-panel {
  width: min(680px, 100%);
  max-height: 72vh;
  display: flex;
  flex-direction: column;
  background: var(--surface-overlay);
  border: 1px solid var(--control-border, rgba(112, 195, 226, 0.22));
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}

.site-search-field {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border, rgba(255, 255, 255, 0.08));
  color: var(--muted);
}

.site-search-input {
  flex: 1;
  min-width: 0;
  background: none;
  border: none;
  outline: none;
  color: var(--text);
  font-size: var(--fs-md);
  font-family: inherit;
}

/* `input:hover, input:focus` draws the site's focus ring with a box-shadow,
   which put a stray rectangle around the query text inside the panel's own
   field. Matching that rule's specificity is enough to switch it off here. */
.site-search-field .site-search-input:hover,
.site-search-field .site-search-input:focus {
  box-shadow: none;
  border-color: transparent;
}

.site-search-close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  padding: 0;
  border: none;
  background: none;
  color: var(--muted);
  cursor: pointer;
  flex-shrink: 0;
}

.site-search-close:hover {
  color: var(--text);
}

.site-search-results {
  list-style: none;
  margin: 0;
  padding: 6px;
  overflow-y: auto;
  flex: 1;
}

.site-search-result-link {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 8px 10px;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
}

.site-search-result.is-selected .site-search-result-link,
.site-search-result-link:hover {
  background: var(--control-hover-bg, rgba(112, 195, 226, 0.1));
}

.site-search-result-group {
  flex-shrink: 0;
  width: 92px;
  font-size: var(--fs-xs);
  color: var(--muted);
  opacity: 0.75;
}

.site-search-result-body {
  min-width: 0;
}

.site-search-result-title {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text);
}

/* The matched run, marked so a hit inside a long source name is findable. */
.site-search-result-title mark {
  background: none;
  color: var(--accent, #70c3e2);
  font-weight: 600;
}

.site-search-result-context {
  display: block;
  margin-top: 1px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.site-search-empty,
.site-search-hint {
  padding: 14px;
  font-size: var(--fs-sm);
  color: var(--muted);
}

.site-search-hint {
  padding: 8px 14px;
  border-top: 1px solid var(--border, rgba(255, 255, 255, 0.08));
  font-size: var(--fs-xs);
  opacity: 0.75;
}

body.site-search-open {
  overflow: hidden;
}

@media (max-width: 640px) {
  .site-search-overlay {
    padding: 0;
  }

  .site-search-panel {
    width: 100%;
    max-height: 100%;
    height: 100%;
    border: none;
    border-radius: 0;
  }

  /* The group column costs a third of a narrow row; the context line below the
     title already says where a result lives. */
  .site-search-result-group {
    display: none;
  }
}

/* Locator card in the area view: the area's own outline, drawn from a
   pre-normalised ring. It is deliberately not a map — no tiles, no zoom, just
   the shape the numbers belong to. */
/* This card is the drawing, so it carries no padding of its own: the shape runs
   to all four edges and the title sits on top of it rather than above it in a
   strip. Anything inset here reads as a picture frame around a picture that is
   already framed by the card. */
.area-outline-card {
  position: relative;
  padding: 0;
  overflow: hidden;
}

.area-outline-card .area-card-title {
  position: absolute;
  z-index: 1;
  top: 0;
  left: 0;
  right: 0;
  margin: 0;
  padding: 14px 18px 22px;
  color: var(--text);
  pointer-events: none;
  /* A scrim rather than a solid bar: the outline can pass under the label, and
     a hard edge across the top would reintroduce the frame this removes. */
  background: linear-gradient(
    to bottom,
    color-mix(in srgb, #000 62%, transparent),
    transparent
  );
}

/* The drawing fills the card rather than sitting as a small square inside it.
   The window the JS computes is square, so `slice` scales it to COVER the box
   and crops the overflow -- the alternative, letterboxing a square into a wide
   card, left black bars down both sides. Cropping is on the vertical axis,
   since the box is always wider than tall, and the focus area is sized to
   survive it: it takes 0.5 of the window against the 0.667 that stays visible
   at 3:2. */
.area-outline-svg {
  width: 100%;
  max-width: none;
  aspect-ratio: 3 / 2;
  height: auto;
  display: block;
  /* No radius of its own — the card clips it, so the drawing meets the card's
     own corners instead of rounding twice a few pixels apart. */
  background: var(--panel-soft);
}

/* Neighbours are drawn first and kept deliberately quiet: enough to say "this
   is a piece of a city, and here is how it sits in it", not enough to compete
   with the area the page is about. Both use non-scaling-stroke, so the line
   weights stay put however far the viewBox zooms. */
.area-outline-neighbour {
  fill: color-mix(in srgb, var(--accent, #7fd4ff) 4%, transparent);
  stroke: color-mix(in srgb, var(--accent, #7fd4ff) 26%, transparent);
  stroke-width: 1;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.area-outline-shape {
  fill: color-mix(in srgb, var(--accent, #7fd4ff) 22%, transparent);
  stroke: var(--accent, #7fd4ff);
  stroke-width: 12;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

/* Expanded on hover: the whole ranking, scrolled to this area. Capped in
   height so a 3 000-row postal list is a box on the page rather than the page
   itself; `overscroll-behavior` keeps a trackpad from carrying on past the end
   of the list and scrolling the article underneath. */
/* The ranking's own floor switch, under the list it explains. It is the site's
   .ui-switch (see the projection toggle) rather than a bare checkbox, which was
   the one native form control left in either card and looked it.

   The rule above the row, not a border on the card, keeps the list and its
   footnote in one panel while still separating them. */
.area-ranking-foot,
.stats-ranking-foot {
  display: flex;
  margin-top: 10px;
  padding-top: 9px;
  border-top: 1px solid var(--line);
}

/* Both cards inherit .ui-switch wholesale; only the label size differs, the
   map panel being the denser of the two. */
.area-ranking-filter.ui-switch {
  font-size: var(--fs-sm);
}

.stats-ranking-filter.ui-switch {
  font-size: var(--fs-xs);
}

/* The switch is the full width of the row so the tap target reaches the edge
   of the card on a phone, where these two cards are at their narrowest. */
.area-ranking-filter.ui-switch,
.stats-ranking-filter.ui-switch {
  width: 100%;
}

/* A fixed height, not a max: the card held a seven-row window until the
   pointer arrived and then grew by 130px, and flipping the floor switch under
   the pointer made it do that twice. The window is now sized to fill the box,
   so the list swapping from window to full list -- or refiltering under the
   switch -- never moves anything below it. */
.area-ranking {
  height: 340px;
  /* `clip`, not `hidden` or `auto`: those two make the box a scroll container
     even with nothing to scroll, and on a phone such a box swallows the drag
     instead of passing it to the page -- the finger lands on the card and the
     page does not move. `clip` is not a scroll container, so the gesture goes
     through. The collapsed window is eleven rows in a 340px box and fits
     exactly; the full list turns scrolling back on below. */
  overflow-y: clip;
}

/* Below ~375px an area name wraps, and eleven rows need more than 340px --
   clipped content has no scrollbar to reveal it, so the box grows instead. The
   height is still fixed at any given width, which is what keeps the card from
   jumping when the list is swapped or refiltered. */
@media (max-width: 374px) {
  .area-ranking {
    height: 384px;
  }
}

/* `overscroll-behavior: contain` stops a scroll gesture inside the box from
   carrying on into the page -- which is what you want while the box is
   scrolling, and a trap when it is not: a box that cannot scroll swallows the
   drag instead of passing it on, so on a phone the page would not move at all
   while a finger was on the card. The collapsed window is eleven rows in a
   340px box, i.e. exactly the case that cannot scroll, so the rule waits for
   the full list. */
.area-ranking--scroll {
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* Ranking window in the area view: the same neighbours-by-rank the map's info
   box shows, with this area marked. */
.area-ranking {
  list-style: none;
  margin: 0;
  padding: 0;
}

.area-rank-inner {
  display: grid;
  grid-template-columns: 42px 1fr auto;
  align-items: baseline;
  gap: 12px;
  padding: 7px 10px;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
  font-size: var(--fs-md);
}

a.area-rank-inner:hover {
  background: rgba(255, 255, 255, 0.06);
}

.area-rank-row.is-current .area-rank-inner {
  background: color-mix(in srgb, var(--accent, #7fd4ff) 14%, transparent);
  font-weight: 600;
}

.area-rank-number,
.area-rank-value {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.area-rank-row.is-current .area-rank-number,
.area-rank-row.is-current .area-rank-value {
  color: inherit;
}

/* The map panel's link into the area page. Pill-shaped like the other links out
   of the panel, full width so it reads as an action rather than a footnote. */
.stats-area-link {
  display: block;
  margin: 10px 0 2px;
  padding: 9px 14px;
  border-radius: var(--radius-pill);
  border: 1px solid var(--panel-border);
  color: inherit;
  text-decoration: none;
  text-align: center;
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.01em;
}

.stats-area-link:hover {
  border-color: var(--accent, #7fd4ff);
  color: var(--accent, #7fd4ff);
}

/* ─────────────────────────────────────────────────────────────────────────
   Follow link
   The site's one outbound social link, at the foot of the home view and under
   the info view's lead. Built from the same control tokens as the top bar's
   buttons, so it follows the theme without a rule of its own per theme.
   ───────────────────────────────────────────────────────────────────────── */

.social-link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid var(--control-border-strong);
  border-radius: var(--radius-pill);
  background: var(--control-bg);
  color: var(--muted);
  font-family: var(--font-ui);
  font-size: var(--fs-sm);
  font-weight: 600;
  text-decoration: none;
  transition: color 140ms ease, border-color 140ms ease, background 140ms ease;
}

.social-link:hover,
.social-link:focus-visible {
  color: var(--text);
  border-color: rgba(var(--accent-rgb), 0.45);
  background: var(--control-bg-hover);
}

.social-icon {
  width: 14px;
  height: 14px;
  flex: none;
}

/* The lead's 34px gap belongs under the follow row, not above it: the button
   reads as part of the intro, and the first section keeps its own breathing
   room below. */
.site-info-lead:has(+ .site-info-follow) {
  margin-bottom: 18px;
}

.site-info-follow {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 10px;
  margin: 0 0 34px;
}

.site-info-contact {
  margin: 0;
  max-width: 62ch;
  font-size: var(--fs-md);
  line-height: var(--lh-prose);
  color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
  .social-link { transition: none; }
}

/* The share export frames a chart at desktop proportions before capturing it,
   and the frame has to reach the chart, not just the card around it. Every
   height cap between the two is lifted for the moment the picture is taken; the
   class is removed again in the same function that added it. */
/* Where a chart is reframed for its picture. Off the left edge rather than
   display:none or visibility:hidden: the island measures this box to decide how
   big to draw, and a box with no layout measures zero. Fixed so it takes no
   space in the flow and cannot scroll the page. */
#shareCaptureStage {
  position: fixed;
  left: -20000px;
  top: 0;
  z-index: -1;
  pointer-events: none;
}

/* A knowledge-base chart laid out in CSS is captured at more than twice the
   width it has in the column, and its rows do not grow with it: a 4px gap and
   an 11px bar read as compact at 380px and as a stack of hairlines across 2300.
   The rows get their air back for the capture only, which also keeps a short
   chart from floating in the middle of a band far taller than itself.
   Only short lists: the ten rank charts on the site run from 5 rows to 29, and
   a long one already fills the picture. Widening its rows too made a 29-row
   chart 500px taller for nothing, so the rule stops at nine rows. */
#shareCaptureStage .kb-rank-rows:not(:has(> :nth-child(10))) {
  gap: 9px;
}
#shareCaptureStage .kb-rank-rows:not(:has(> :nth-child(10))) .kb-rank-track,
#shareCaptureStage .kb-rank-rows:not(:has(> :nth-child(10))) .kb-rank-from,
#shareCaptureStage .kb-rank-rows:not(:has(> :nth-child(10))) .kb-rank-bar {
  height: 15px;
}

.chart-container-shell.is-share-capture .chart-body-row,
.chart-container-shell.is-share-capture .chart-main-area,
.chart-container-shell.is-share-capture .chart-main {
  /* !important because the caps this has to beat are written with the card's
     own id -- #migriWrap .nat-bar-chart-main pins 220px on a phone, which a
     class cannot outrank, and the chart came out a 5:1 strip in a 3:2 frame. */
  flex: 1 1 0 !important;
  height: auto !important;
  max-height: none !important;
  min-height: 0 !important;
}

/* Not this one: the island measures this box and draws to it, so it has to
   keep filling its parent. Given height: auto it collapses onto the drawing it
   contains, the drawing keeps the height it already had, and the frame reaches
   the card but never the chart. */
.chart-container-shell.is-share-capture .chart-scroll-wrapper {
  height: 100% !important;
  max-height: none !important;
}

/* The capture reframes the live view -- the map is resized, a chart's card is
   widened and made taller -- and on a phone that is a second of the page
   visibly rearranging itself. It happens behind this instead. */
.share-capture-shield {
  position: fixed;
  inset: 0;
  z-index: 9500;
  display: grid;
  place-items: center;
  background: var(--bg);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.12s ease;
}
.share-capture-shield.is-visible {
  opacity: 1;
  pointer-events: auto;
}
.share-capture-shield-label {
  font-family: var(--font-ui);
  font-size: var(--fs-xs);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
  .share-capture-shield { transition: none; }
}

/* .share-dropdown asks for z-index 740 to clear the stats panel (735), and on a
   phone it needs it: the panel is a bottom sheet and the share menu now carries
   a picture preview, so the two overlap and the download button ended up under
   the card. The 740 never applied — the dropdown is a child of the top bar,
   whose own z-index caps everything inside it. Lift the bar itself while one of
   its menus is open, so the ordering the dropdown asked for is the one it gets.
   Only while open: the bar has no reason to sit above the panel otherwise. */
.top-bar:has(details[open]),
.top-bar-layout.top-bar-layout--elm:has(details[open]) {
  z-index: 745;
}

/* The share panel is hidden on the chart and knowledge views because the top
   bar has nothing to share there — each chart carries its own state. A chart's
   own share button borrows the same panel, though, and anchors it beside
   itself, so it has to be able to show through those rules. Two selectors
   because both breakpoints hide it, and `display` cannot be overridden by
   specificity alone across a media query without repeating it. */
.share-dropdown.is-anchored,
body[data-view="chart"] .share-dropdown.is-anchored,
body[data-view="knowledge"] .share-dropdown.is-anchored {
  display: block;
  position: fixed;
  z-index: 1400;
}

@media (max-width: 900px) {
  body:not([data-view="map"]):not([data-view="cartogram"]):not([data-view="area"]):not([data-view="origin"]) .share-dropdown.is-anchored {
    display: block;
  }
}

/* Anchored, the panel is positioned by script rather than hung off the top
   bar, so its menu sits directly under the point it was opened from. */
.share-dropdown.is-anchored > summary { display: none; }
.share-dropdown.is-anchored .share-dropdown-menu {
  position: static;
  min-width: min(300px, calc(100vw - 24px));
}

/* Knowledge-base sources on an area or origin page. The shape follows the
   source cards in the knowledge base itself -- a title that links out, the
   publisher and year under it, one sentence -- so a reader who has seen one
   there recognises it here. */
.area-source-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
}

.area-source + .area-source {
  padding-top: 12px;
  border-top: 1px solid var(--border);
}

.area-source-title {
  display: block;
  font-weight: 650;
  line-height: var(--lh-ui);
  color: var(--text);
  text-decoration: none;
}

.area-source-title:hover,
.area-source-title:focus-visible {
  color: var(--accent);
  text-decoration: underline;
}

.area-source-meta {
  margin-top: 2px;
  font-size: var(--fs-sm);
  color: var(--muted);
}

.area-source-summary {
  margin: 6px 0 0;
  font-size: var(--fs-sm);
  line-height: var(--lh-ui);
  color: var(--muted);
}

/* ── Site footer ─────────────────────────────────────────────────────────────
   Shared by the scrolling content views (home, statistics, knowledge, info).
   Quiet on purpose: no background of its own, a hairline on top, muted type -
   it closes the page rather than competing with it. The map and cartogram are
   full-viewport applications and carry no footer. */
.site-footer {
  margin-top: 56px;
  padding: 30px 0 40px;
  border-top: 1px solid var(--border);
  font-size: var(--fs-sm);
}

.site-footer-grid {
  display: grid;
  grid-template-columns: minmax(220px, 1.6fr) 1fr 1fr;
  gap: 28px 40px;
}

.site-footer-name {
  display: block;
  font-weight: 650;
  font-size: var(--fs-md);
  letter-spacing: 0.02em;
  color: var(--text);
}

.site-footer-tagline {
  margin: 8px 0 14px;
  color: var(--muted);
  line-height: var(--lh-prose);
  max-width: 34ch;
}

.site-footer-col {
  display: flex;
  flex-direction: column;
  gap: 7px;
}

.site-footer-heading {
  font-size: var(--fs-xs);
  font-weight: 650;
  color: var(--muted);
  margin-bottom: 3px;
}

.site-footer-link {
  color: var(--text);
  opacity: 0.82;
  text-decoration: none;
  width: fit-content;
  padding-block: 4px;
}

.site-footer-link:hover {
  color: var(--accent);
  opacity: 1;
}

.site-footer-bottom {
  margin-top: 26px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
}

@media (max-width: 720px) {
  .site-footer-grid {
    grid-template-columns: 1fr;
    gap: 24px;
  }
}

/* ── Europe dataset: mode pill, zoom-floor bar, confirmation sheet ───────────
   All three sit over the map, the pill and the sheet also over the
   cartogram (JS moves them into the visible stage). The sheet is the only modal
   one: switching geography under the reader is the one dataset change that
   must not happen by accident. */
.europe-mode-pill {
  position: absolute;
  top: calc(var(--safe-top) + var(--top-bar-height) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 620;
  display: flex;
  align-items: center;
  /* The back button lost its own box, so the pill's own padding is now the
     only thing keeping that label off the edge. */
  gap: 16px;
  padding: 7px 18px;
  border-radius: var(--radius-pill);
  background: var(--surface-panel);
  border: 1px solid var(--accent);
  color: var(--text);
  font-size: var(--fs-sm);
  font-weight: 600;
  letter-spacing: 0.02em;
  box-shadow: var(--shadow-md);
}

/* A row of plain words, not a segmented control: the pill is already one box
   and boxes inside boxes read as clutter. The chosen tier is told apart by
   colour and weight instead of by a fill. */
.europe-mode-pill__tiers {
  display: inline-flex;
  gap: 12px;
}

.europe-mode-pill__tier {
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 500;
  padding: 4px 2px;
  border: 0;
  background: none;
  color: var(--muted);
  cursor: pointer;
}

.europe-mode-pill__tier[aria-pressed="true"] {
  color: var(--accent);
  font-weight: 700;
}

/* Hover must not overwrite the chosen tier's colour: without the guard, moving
   the pointer onto the tier you just picked repainted it from the accent to
   plain text, so which one was chosen depended on where the mouse sat. */
.europe-mode-pill__tier:hover:not([aria-pressed="true"]) {
  color: var(--text);
}

/* Plain text, not a boxed button: the pill already draws one outline and the
   tier switch a second, and a third box inside them read as clutter. */
.europe-mode-pill__back {
  font: inherit;
  font-size: var(--fs-xs);
  padding: 4px 2px;
  border: 0;
  background: none;
  color: var(--accent);
  cursor: pointer;
}

.europe-mode-pill__back:hover {
  text-decoration: underline;
}

.europe-zoom-bar {
  position: absolute;
  top: calc(var(--safe-top) + var(--top-bar-height) + 10px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 620;
  display: flex;
  align-items: center;
  gap: 10px;
  max-width: min(92vw, 640px);
  padding: 8px 10px 8px 14px;
  border-radius: var(--radius-md);
  background: var(--surface-panel);
  border: 1px solid rgba(112, 195, 226, 0.25);
  color: var(--text);
  font-size: var(--fs-sm);
  box-shadow: var(--shadow-md);
}

.europe-zoom-bar__btn {
  flex-shrink: 0;
  font: inherit;
  font-size: var(--fs-xs);
  padding: 5px 11px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(112, 195, 226, 0.3);
  background: transparent;
  color: var(--text);
  cursor: pointer;
}

.europe-zoom-bar__btn--accept {
  background: var(--accent);
  border-color: var(--accent);
  color: #06222c;
  font-weight: 600;
}

.europe-switch-prompt {
  position: absolute;
  inset: 0;
  z-index: 640;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.45);
}

.europe-switch-prompt__card {
  max-width: 440px;
  padding: 22px 24px 18px;
  border-radius: var(--radius-md);
  background: var(--surface-panel);
  border: 1px solid rgba(112, 195, 226, 0.25);
  color: var(--text);
  box-shadow: var(--shadow-lg);
}

.europe-switch-prompt__title {
  margin: 0 0 10px;
  font-size: 1.05rem;
}

.europe-switch-prompt__body {
  margin: 0 0 16px;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
}

.europe-switch-prompt__actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  flex-wrap: wrap;
}

.europe-switch-prompt__btn {
  font: inherit;
  font-size: var(--fs-sm);
  padding: 8px 16px;
  border-radius: var(--radius-pill);
  border: 1px solid rgba(112, 195, 226, 0.3);
  background: transparent;
  color: var(--text);
  cursor: pointer;
}

.europe-switch-prompt__btn--accept {
  background: var(--accent);
  border-color: var(--accent);
  color: #06222c;
  font-weight: 600;
}

/* Anchored on #mapStage: a light-theme rule for buttons inside the map stage
   otherwise paints these white on white. */
body.light-theme #mapStage .europe-switch-prompt__btn,
body.light-theme #cartogramStage .europe-switch-prompt__btn,
body.light-theme #mapStage .europe-zoom-bar__btn,
body.light-theme #cartogramStage .europe-zoom-bar__btn,
/* A broad light-theme rule paints buttons inside the map stage white; this one
   is plain text, so it has to opt out explicitly in both stages. */
body.light-theme #mapStage .europe-mode-pill__back,
body.light-theme #cartogramStage .europe-mode-pill__back {
  background: none;
  border: 0;
  color: var(--accent);
}

body.light-theme #mapStage .europe-mode-pill__tier,
body.light-theme #cartogramStage .europe-mode-pill__tier {
  background: none;
  color: var(--muted);
}

body.light-theme #mapStage .europe-mode-pill__tier[aria-pressed="true"],
body.light-theme #cartogramStage .europe-mode-pill__tier[aria-pressed="true"] {
  background: none;
  color: var(--accent);
}

body.light-theme #mapStage .europe-switch-prompt__btn--accept,
body.light-theme #cartogramStage .europe-switch-prompt__btn--accept,
body.light-theme #mapStage .europe-zoom-bar__btn--accept,
body.light-theme #cartogramStage .europe-zoom-bar__btn--accept {
  background: var(--accent);
  border-color: var(--accent);
  color: #ffffff;
}

@media (max-width: 700px) {
  .europe-zoom-bar {
    flex-wrap: wrap;
    justify-content: flex-end;
  }

  .europe-mode-pill {
    flex-wrap: wrap;
    justify-content: center;
    /* left: 50% leaves a shrink-to-fit box only half the stage; size it by
       its content and cap it at the stage instead. */
    width: max-content;
    max-width: calc(100% - 24px);
    row-gap: 6px;
  }

  /* Two rows on a phone: the name, then the tier switch and the way back. */
  .europe-mode-pill__label {
    flex-basis: 100%;
    text-align: center;
  }
}

/* ── Source cards fold to their title line ─────────────────────────────────
   Title, year, sample and the count of findings on the line; the text opens
   on click, and the spectacles setting opens every card. Before this the
   findings of every card stood open, and nine cards were a wall of text. */
details.kb-entry {
  padding: 0;
}
details.kb-entry > summary.kb-entry-header {
  cursor: pointer;
  list-style: none;
  margin: 0;
  padding: 12px 16px;
  font-size: var(--fs-md);
  letter-spacing: normal;
}
details.kb-entry > summary.kb-entry-header::-webkit-details-marker {
  display: none;
}
details.kb-entry > summary.kb-entry-header:hover .kb-entry-title {
  color: var(--accent);
}
.kb-entry-body {
  padding: 0 16px 16px;
}
.kb-entry-count {
  font-size: var(--fs-xs);
  color: var(--muted);
  white-space: nowrap;
}
.kb-entry-chev {
  display: inline-block;
  width: 7px;
  height: 7px;
  margin: 0 2px 2px 4px;
  border-right: 1.5px solid var(--muted);
  border-bottom: 1.5px solid var(--muted);
  transform: rotate(-45deg);
  transition: transform 0.15s;
}
details.kb-entry[open] > summary .kb-entry-chev {
  transform: rotate(45deg);
}
@media (prefers-reduced-motion: reduce) {
  .kb-entry-chev { transition: none; }
}

/* ── The cost ledger card ──────────────────────────────────────────────────
   Two stack panels side by side, a ledger with one value column per scope,
   and the time rows under their own heading. Rows borrow the workings
   typography (kb-work-*) and only change the grid. */
.kb-m-link {
  margin: 10px 0 2px;
  font-size: var(--fs-sm);
}
.kb-m-link a {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid rgba(var(--accent-rgb), 0.5);
  border-radius: 999px;
  color: var(--accent);
  font-weight: 600;
  text-decoration: none;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.kb-m-link a::after {
  content: "\203A";
  font-size: var(--fs-md);
  line-height: 1;
}
.kb-m-link a:hover,
.kb-m-link a:focus-visible {
  border-color: var(--accent);
  background: rgba(var(--accent-rgb), 0.08);
}
.kb-ledger-panels {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 20px;
  margin: 8px 0 14px;
}
.kb-ledger-panel {
  min-width: 0;
}
.kb-ledger-panel-title {
  font-size: var(--fs-sm);
  font-weight: 600;
  margin-bottom: 2px;
}
.kb-ledger-sub {
  color: var(--muted);
  font-weight: 400;
  margin-left: 6px;
}
.kb-ledger-svg {
  display: block;
  width: 100%;
  height: auto;
}
.kb-ledger-svg text {
  font-family: var(--font-data);
}
.kb-ledger {
  border-top: 1px solid var(--border);
}
.kb-ledger-row.kb-work-row {
  grid-template-columns: minmax(0, 1fr) 150px 150px;
  gap: 0 12px;
  align-items: baseline;
  padding: 6px 8px;
}
.kb-ledger-row.kb-ledger-timerow {
  grid-template-columns: minmax(0, 1fr) 210px;
}
.kb-ledger-head {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 150px 150px;
  gap: 0 12px;
  padding: 8px 8px 6px;
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.kb-ledger-head .kb-ledger-col {
  text-align: right;
}
.kb-ledger-section {
  padding: 12px 8px 2px;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--text);
}
.kb-ledger-time-title {
  padding-top: 18px;
}
.kb-ledger-subrow .kb-work-label {
  padding-left: 24px;
}
.kb-ledger-notes {
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 4px 8px 12px 24px;
}
.kb-ledger-note .kb-work-note {
  padding: 4px 0 0;
}
.kb-ledger-scope {
  display: block;
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.kb-ledger-formula {
  display: block;
  font-size: var(--fs-sm);
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
.kb-ledger-intro {
  font-style: normal;
  padding: 0;
}
.kb-ledger-ranges {
  max-width: 520px;
  margin: 6px 0;
}
.kb-ledger-bridgerow .kb-work-label {
  color: var(--accent);
}
/* The formula: chips in the bars' colours, a group's caption as a bracket
   line under its chips rather than a box around them, totals as filled chips.
   One visual system with the bridge charts below. */
.kb-ledger-formula-strip {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 4px 0 8px;
}
.kb-ledger-eq {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px 6px;
}
.kb-ledger-eq-question {
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--text);
  margin-top: 8px;
}
.kb-ledger-eq-question:first-child {
  margin-top: 0;
}
.kb-ledger-part {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  gap: 3px;
}
.kb-ledger-part-caption {
  order: 2;
  font-size: var(--fs-xs);
  line-height: 1.3;
  color: var(--muted);
  text-align: center;
  padding-top: 3px;
  border-top: 1px solid var(--border);
}
.kb-ledger-part-pieces {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 0 2px;
}
.kb-ledger-part-pieces.is-bare {
  flex-wrap: nowrap;
  padding-top: 1px;
}
.kb-ledger-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.05);
  font-size: var(--fs-xs);
  line-height: 1.3;
  color: var(--text);
  white-space: nowrap;
}
.kb-ledger-chip i {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  flex: none;
}
.kb-ledger-chip.is-total {
  font-weight: 700;
  background: rgba(255, 255, 255, 0.12);
}
.kb-ledger-op {
  font-family: var(--font-data);
  font-size: var(--fs-md);
  line-height: 22px;
  color: var(--muted);
}
.kb-ledger-bridges {
  margin: 10px 0 0;
  gap: 28px;
}
.kb-ledger-bridge .kb-ledger-panel-title {
  font-size: var(--fs-sm);
}
.kb-ledger-caption {
  margin: 12px 0 4px;
  font-size: var(--fs-sm);
  line-height: var(--lh-prose);
  color: var(--muted);
}
@media (max-width: 720px) {
  .kb-ledger-panels {
    grid-template-columns: 1fr;
  }
  /* Two value columns of 104px overflowed their figures into the label. On a
     phone the label takes the first line on its own and the two figures share
     the second, right-aligned so the columns still line up down the ledger. */
  .kb-ledger-row.kb-work-row,
  .kb-ledger-head {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    gap: 2px 12px;
  }
  .kb-ledger-row.kb-work-row .kb-work-label,
  .kb-ledger-head > span:first-child {
    grid-column: 1 / -1;
  }
  .kb-ledger-row.kb-work-row .kb-work-value {
    font-size: var(--fs-sm);
  }
  .kb-ledger-row.kb-ledger-timerow {
    grid-template-columns: minmax(0, 1fr);
  }
  .kb-ledger-row.kb-ledger-timerow .kb-work-label {
    grid-column: 1;
  }
}

/* ── The lifetime calculator's breakdown ───────────────────────────────────
   Every term of the sum as a row, the arrival against the comparison. The
   numbers line up in two columns so the reader can add them up by eye and
   land on the total, which is why the values are tabular and the label
   carries its own unit note. */
.tool-break {
  margin-top: 16px;
  border-top: 1px solid var(--border);
}
.tool-break-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 104px 104px;
  gap: 4px 12px;
  align-items: baseline;
  padding: 7px 0;
  border-bottom: 1px solid var(--border);
  font-size: var(--fs-sm);
}
.tool-break-row.is-head {
  padding-bottom: 5px;
  border-bottom-color: var(--border-strong, rgba(128, 128, 128, 0.35));
  font-size: var(--fs-xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
}
.tool-break-row.is-total {
  margin-top: 4px;
  padding-top: 9px;
  border-top: 1px solid var(--border-strong, rgba(128, 128, 128, 0.35));
  border-bottom: 0;
  font-weight: 700;
}
.tool-break-label {
  min-width: 0;
  color: var(--text);
  line-height: var(--lh-ui);
}
.tool-break-row.is-head .tool-break-label {
  color: var(--muted);
}
.tool-break-note {
  display: block;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: var(--lh-ui);
}
.tool-break-value {
  font-family: var(--font-data);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}
.tool-break-value.is-compare {
  color: var(--muted);
}
@media (max-width: 560px) {
  .tool-break-row {
    grid-template-columns: minmax(0, 1fr) 84px 84px;
  }
}

/* Readers who ask the operating system for more contrast. The block works
   through the tokens where it can, so every card edge, rule and control on the
   site follows without its own rule being touched: the muted ink comes up to
   nearly the text colour, the hairline borders become lines that can be seen,
   and the small print that dims itself with opacity stops doing so. Links get
   an underline, so they are told apart by more than colour. The ordinary
   palette is what the site is designed around and is not changed here; this
   is the same site with the volume up, for the reader who asked for it.
   Checked with Playwright's contrast emulation in both themes. */
@media (prefers-contrast: more) {
  :root {
    --muted: #e2e8ec;
    --accent-dim: #b9d7e6;
    --border: rgba(112, 195, 226, 0.55);
    --line: rgba(112, 195, 226, 0.45);
    --home-line: rgba(var(--accent-rgb), 0.5);
    --home-line-soft: rgba(var(--accent-rgb), 0.4);
    --control-border: rgba(112, 195, 226, 0.6);
    --control-border-strong: rgba(112, 195, 226, 0.8);
    --control-hover-border: rgba(112, 195, 226, 0.9);
    --control-shadow-ring: 0 0 0 1px rgba(112, 195, 226, 0.5);
    --topbar-surface: rgba(10, 15, 24, 0.86);
    --topbar-surface-border: rgba(255, 255, 255, 0.5);
    --panel-card-border: rgba(112, 195, 226, 0.5);
    --panel-border: rgba(255, 255, 255, 0.5);
    --panel-rule: rgba(255, 255, 255, 0.3);
    --card-inset-border: rgba(255, 255, 255, 0.3);
    --kb-migmap-border: rgba(255, 255, 255, 0.4);
    --year-track: rgba(112, 195, 226, 0.5);
  }

  body.light-theme {
    --muted: #1f2937;
    --accent-dim: #0c4a6e;
    --border: rgba(0, 0, 0, 0.5);
    --line: rgba(0, 0, 0, 0.5);
    --control-border: rgba(82, 99, 115, 0.6);
    --control-border-strong: rgba(82, 99, 115, 0.8);
    --control-hover-border: rgba(82, 99, 115, 0.9);
    --control-shadow-ring: 0 0 0 1px rgba(82, 99, 115, 0.5);
    --topbar-surface: rgba(255, 255, 255, 0.96);
    --topbar-surface-border: rgba(82, 99, 115, 0.5);
    --panel-card-border: rgba(0, 0, 0, 0.45);
    --panel-border: rgba(15, 23, 42, 0.5);
    --panel-rule: rgba(15, 23, 42, 0.3);
    --card-inset-border: rgba(15, 23, 42, 0.3);
    --kb-migmap-border: rgba(0, 0, 0, 0.4);
  }

  .area-chart-note,
  .chart-data-note,
  .chart-method-block p,
  .chart-method-source,
  .chart-share-btn,
  .chart-sidebar-hint,
  .chart-std-summary,
  .chart-subtitle,
  .chart-source-link,
  .chart-xlink,
  .home-cell-note,
  .home-cell-period,
  .home-live-note,
  .home-trend-axis-label,
  .kb-chart-caption,
  .kb-comparison-desc,
  .kb-comparison-toggle,
  .kb-comparisons-sub,
  .kb-migmap-rank-unit,
  .kb-origin-title,
  .kb-stat-tiles-note,
  .kb-work-note,
  .kb-work-src,
  .lang-dropdown-menu .lang-code,
  .map-selection-hint,
  .site-search-hint,
  .site-search-result-group,
  .stats-secondary-label {
    opacity: 1;
  }

  p a,
  td a,
  figcaption a,
  .chart-source-link,
  .chart-data-note a,
  .chart-xlink,
  .kb-work-src a,
  .tool-source a,
  .area-back,
  .area-context-link {
    text-decoration: underline;
  }
}
