:root {
  --bg:        #dfe7ee;   /* around the zones */
  --panel-bg:  rgba(255, 255, 255, 0.94);
  --ink:       #1c2530;
  --ink-muted: #64717f;
  --rule:      #d3dae1;
  /* The Assembly's own blue. Every other hue in Northern Ireland is spoken for
     -- orange and green by the two traditions, red and blue by the flags, and
     the rest by the parties in PARTY_COLORS -- so the accent borrows from the
     institution rather than from anybody's side. It was the DUP's orange
     before, eleven degrees of hue from that party's own colour, which is not a
     thing for every button in the app to be wearing.

     The nearest party to it is the UUP, twenty degrees away since that colour
     moved cyan-ward to make room. They are never seen side by side in any case:
     the party colours live in the pie and the region's count, the accent lives
     on the controls, and the result bars are coloured by region. */
  --accent:    #5e89c2;
}

* { box-sizing: border-box; }

/* The checkbox and radio fills, the range tracks, the progress bars. Set once
   at the root rather than per control, and it inherits -- otherwise the app's
   own blue sits next to the platform's orange, which reads as two designs.
   The range inputs set it again lower down, harmlessly.

   It does NOT reach inside an open dropdown, whatever this comment used to
   claim: the highlight running down a native select popup is the desktop's
   accent and accent-color does not touch it. That needs base-select, further
   down. */
:root { accent-color: var(--accent); }

/* Selected text, which the browser also paints in its own colour. Tinted to
   the same blue rather than filled with it: a solid accent behind dark ink is
   hard to read, and these are number boxes people retype. */
::selection { background: rgba(94, 137, 194, 0.32); color: var(--ink); }

html, body {
  margin: 0;
  height: 100%;
  font: 14px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  color: var(--ink);
  background: var(--bg);
}

#map { position: absolute; inset: 0; }

/* --- info panel --------------------------------------------------------- */
#panel {
  /* The map's own width, kept as a number even when the panel is wider than it
     for a page: the gable is sized and placed off this rather than off
     whatever the panel currently is, so opening a page does not move it. */
  --panel-w: 398px;
  --panel-pad: 18px;
  position: absolute;
  top: 16px;
  left: 16px;
  z-index: 2;
  padding: 14px var(--panel-pad) 12px;
  background: var(--panel-bg);
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(20, 34, 48, 0.10);
  backdrop-filter: blur(6px);
  /* Whatever the measured label column needs, plus a 133px column for the
     sliders and boxes and the padding and gutter around them. The label column
     is sized for a typical head rather than the longest, so Social grade and
     Orientation wrap and the rest do not. */
  width: var(--panel-w);
  max-height: calc(100% - 32px);
  overflow-y: auto;
  /* Room for the scrollbar whether or not one is showing: otherwise every
     control in the panel shifts and narrows the moment the content outgrows
     the screen. */
  scrollbar-gutter: stable;
}

#panel .hint {
  margin: 0;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  font-size: 11.5px;
  color: var(--ink-muted);
}

/* --- mode switch --------------------------------------------------------- */
/* Two buttons acting as one control: which set of variables the run steers by.
   The pressed one is filled, so the current mode reads at a glance. */
/* The gable carries the name, so it stands alone across the top of the panel at
   its own proportions. Line art on transparency: no background of its own. */
.brand {
  margin: 0 0 11px;
}
/* The window the drawing is seen through. On the map it is the width the gable
   has always been, which shows the front and clips the side off; for a page it
   opens to the whole drawing. Just short of the panel's full width either way,
   so the gable reads as a sign hung on the panel rather than as its top edge. */
.brand-frame {
  position: relative;
  display: block;
  width: 90%;
  margin: 0 auto;
  overflow: hidden;
}
/* Whichever drawing is showing fills the frame. Both are the same height and
   the same scale, so the mural is the same size and in the same place in
   either -- the frame widening is the whole of the change. */
.brand-frame img {
  display: block;
  width: 100%;
  height: auto;
}
.brand-frame img[hidden] { display: none; }
/* The measured numbers, from pinGable(): the frame's own width on the map's
   panel and the inset it sits at. Both are measured rather than written down,
   because the width they come from is the panel less its border and its
   reserved scrollbar gutter, and the gutter differs between platforms. */
#panel.is-page .brand-frame {
  width: var(--art-w);
  margin-left: var(--gable-left, auto);
  margin-right: auto;
}

/* The article title, set in the drawn letters over the blank wall. Placed from
   the bounds of the marker rectangle the artwork carried, so moving it in
   Inkscape and rebuilding moves the type. */
.brand-title {
  position: absolute;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  color: var(--ink);
}
.brand-title[hidden] { display: none; }

/* The heading stays for anything reading the page rather than looking at it;
   the gable is what carries the title on screen. */
#panel-page-title {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}


/* A control that cannot be changed right now -- anything in the region group,
   once a run has started -- reads as greyed rather than merely inert. */
.ctl-grid input:disabled, .ctl-grid select:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
/* Only the disabled control's own label: the next one along belongs to the
   following row, which may well still be live. */
.ctl-grid label:has(+ input:disabled),
.ctl-grid label:has(+ select:disabled) { opacity: 0.45; }

/* --- region builder controls -------------------------------------------- */
.ctl-grid {
  display: grid;
  /* The label column is measured at boot against a typical head, in whatever
     font the browser is using (see sizeLabelColumn); the sliders and boxes take
     everything left. The fallback is what that measurement gives here. */
  grid-template-columns: var(--label-col, 210px) 1fr;
  align-items: center;
  /* The column is measured to its content, so this gap is the whole buffer
     between a label and its control -- there is no slack behind it. */
  gap: 7px 6px;
}
.ctl-grid label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
}

/* Settings gathered under a heading -- what the map is, and what election it
   holds -- divided by the same rule a demographic block carries. The first
   group has the buttons above it, which are their own divider. */
.ctl-group + .ctl-group {
  margin-top: 11px;
  padding-top: 11px;
  border-top: 1px solid var(--rule);
}
/* Stronger than the labels beneath it: this is the CONTROLS face, at full ink,
   where a row label is 10px and muted. The gap below belongs to the body rather
   than the heading, so folding the group away takes the gap with it. */
.ctl-group-title {
  margin: 0;
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink);
}
.ctl-group-title + .ctl-grid { margin-top: 7px; }
/* The whole heading is the disclosure, folding the group away the same way a
   variable block folds. Open on load: a setting you cannot see is one you
   forget you changed. */
.ctl-group-toggle {
  display: flex;
  align-items: baseline;
  gap: 6px;
  padding: 0;
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}
/* The heading is already at full ink, so hovering it goes to the accent --
   where a demo toggle, being muted, hovers up to full ink instead. */
.ctl-group-toggle:hover { color: var(--accent); }
.ctl-group-toggle .chev { display: inline-block; transition: transform 0.12s; }
.ctl-group-toggle[aria-expanded="true"] .chev { transform: rotate(90deg); }
/* display: grid would otherwise beat the hidden attribute. */
.ctl-grid[hidden] { display: none; }

/* Two checkboxes on one line, spanning both columns of the grid. Each label
   wraps its own box so the whole phrase is the hit target. */
/* A checkbox with its name beside it rather than in the label column. Only the
   advanced switch wears this now, standing on its own at the foot of the panel
   outside any grid -- the checkboxes inside the grid are ordinary rows, named
   on the left like everything else, so their boxes line up under the sliders. */
.ctl-checks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 16px;
}
.ctl-checks[hidden] { display: none; }
/* --- info markers --------------------------------------------------------- */
/* A small circled i at the end of a control's label, opening the note below the
   row. Quiet by default: there is one on every row, and twenty markers shouting
   at once would be worse than no markers at all. */
.info-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 13px;
  height: 13px;
  padding: 0;
  font: inherit;
  font-size: 9px;
  font-weight: 700;
  font-style: italic;
  line-height: 1;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-muted);
  background: none;
  border: 1px solid currentColor;
  border-radius: 50%;
  cursor: pointer;
  /* The circle is 13px against an 11px line box and shares its top edge, so it
     hangs below the text -- further below the capitals, which sit higher again
     in the line. Lifted to put its middle where the eye expects it. Transform,
     so the row's height is unchanged. */
  vertical-align: baseline;
  transform: translateY(-2px);
  opacity: 0.6;
}
.info-mark:hover, .info-mark[aria-expanded="true"] {
  color: var(--accent);
  opacity: 1;
}
.info-mark:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The note runs the full width under the row it belongs to, rather than in the
   control column, because it is prose and the control column is 133px. */
.info-note {
  grid-column: 1 / -1;
  margin: 1px 0 3px;
  padding: 7px 9px;
  font-size: 11.5px;
  line-height: 1.5;
  /* Newlines in a note are kept -- the tactical voting note numbers its two
     conditions a line each -- while ordinary wrapping is left alone. */
  white-space: pre-line;
  color: var(--ink);
  background: rgba(94, 137, 194, 0.08);
  border-left: 2px solid var(--accent);
  border-radius: 0 4px 4px 0;
}
.info-note[hidden] { display: none; }

/* --- the menu ------------------------------------------------------------ */
/* Three bars in the corner of the gable. The artwork has nothing there, and the
   panel has no room for a bar of its own. */
.burger {
  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  /* Stated, not left to the default: a button carries align-items: center in
     Chrome's own stylesheet, and whether that or flex's stretch wins differs
     between versions. Where centre won, three empty spans had nothing to give
     them a width and the whole thing rendered at zero. */
  align-items: center;
  gap: 3px;
  width: 26px;
  height: 22px;
  padding: 0;
  background: none;
  border: 0;
  border-radius: 4px;
  cursor: pointer;
}
.burger span {
  width: 20px;             /* stated for the same reason */
  height: 2px;
  background: var(--ink);
  border-radius: 1px;
  transition: transform 0.14s, opacity 0.14s;
}
.burger:hover span { background: var(--accent); }
/* Open, it is the way back: the bars fold into a cross without becoming a
   different control in a different place. */
.burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

#panel-menu[hidden], #panel-body[hidden] { display: none; }

.menu-list { margin: 0; padding: 0; list-style: none; }
.menu-item {
  display: block;
  width: 100%;
  padding: 9px 4px;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  text-align: left;
  color: var(--ink);
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
}
.menu-item:hover:not(:disabled) { color: var(--accent); }
/* Not yet written. Shown rather than hidden, so the shape of the thing is
   visible before its contents are. */
.menu-item:disabled { color: var(--ink-muted); opacity: 0.55; cursor: default; }
.menu-item.is-current { color: var(--accent); }

/* Settings about the app rather than about the map live under the pages. */
.menu-switch { margin-top: 14px; }

/* --- text set in the drawn letters --------------------------------------- */
/* One box per glyph, masked by its slice of the sheet. The ink is the box's
   own background, so a line takes the colour of whatever it sits in rather
   than the black it was drawn in; the sheet carries alpha and nothing else.
   Bottom-aligned, since every glyph is cut to the same band and it is the
   baseline they share. */
.bs-line {
  display: flex;
  align-items: flex-end;
  flex-wrap: nowrap;
}
/* The sheet is scaled as an image and the span shows one glyph's worth of it.
   Not a background-size or a mask-size, which put the same pixels in the same
   place but come out unfiltered -- a staircase where the drawing has a curve.
   The ink is whatever colour the sheet was drawn in, which is the price of
   the image path: currentColor only reaches a mask. */
.bs-glyph {
  display: block;
  flex: none;
  position: relative;
  overflow: hidden;
}
.bs-glyph img {
  display: block;
  position: absolute;
  top: 0;
  max-width: none;
}
.bs-space { display: block; flex: none; }

/* --- the pages ----------------------------------------------------------- */
/* An explanation needs a reading measure the map's own column cannot give, so
   the panel widens for one. It widens rather than being replaced: the gable,
   the burger and the map underneath all stay where they were, which is what
   says you are still on the same page and not somewhere else. */
#panel-nav { display: flex; gap: 18px; }
#panel-nav[hidden] { display: none; }
/* Wide enough for the whole gable, measured by pinGable(), but never narrower
   than the reading column the pages were sized for. */
#panel.is-page {
  width: min(max(680px, var(--page-w, 680px)), calc(100% - 32px));
}
/* Fills the panel on its own -- stated, because a flex child sizes to its
   content otherwise, and the items' underlines would stop halfway across. */
#panel-menu { flex: 1; min-width: 0; }
/* Only once a page is open does the list become a column of its own. */
#panel.is-page #panel-menu {
  flex: 0 0 150px;
  padding-right: 17px;
  border-right: 1px solid var(--rule);
}
#panel-page { flex: 1; min-width: 0; }
#panel-page[hidden] { display: none; }
#panel-page h2 {
  margin: 2px 0 10px;
  font-size: 15px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink);
}
/* Bigger and looser than anything on the map's page: this is read, not
   glanced at. */
#panel-page-body {
  font-size: 14px;
  line-height: 1.62;
  color: var(--ink);
}
#panel-page-body p { margin: 0 0 11px; }
#panel-page-body p:last-child { margin-bottom: 2px; }
/* Centred, so a figure narrower than the column sits in the middle of it
   rather than leaving all its slack on one side. */
#panel-page-body figure { margin: 14px auto; }
#panel-page-body img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 5px;
}
#panel-page-body figcaption {
  margin-top: 6px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-muted);
}
/* Emphasis in the middle of a sentence: the terms of art the page is teaching,
   so it names rather than shouts. */
#panel-page-body em {
  font-style: normal;
  font-weight: 600;
}
#panel-page-body a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 2px;
}
#panel-page-body a:hover { text-decoration-thickness: 2px; }
.ctl-checks label {
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
.ctl-checks label[hidden] { display: none; }
.ctl-checks input { margin: 0; }

/* Each variable can be taken off the page again; the button sits in the block's
   head, quiet until the block is hovered. */
.var-remove {
  padding: 0 2px;
  font: inherit;
  font-size: 15px;
  line-height: 1;
  color: var(--ink-muted);
  background: none;
  border: 0;
  border-radius: 3px;
  opacity: 0;
  cursor: pointer;
  transition: opacity 0.12s;
}
.demo-block:hover .var-remove,
.var-remove:focus-visible { opacity: 1; }
.var-remove:hover { color: var(--accent); }

/* What is not on the page yet. Nothing stands open: a plus, and the menu only
   when it is wanted. */
.add-variable { margin-top: 9px; }
.add-open {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 2px 0;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--accent);
  background: none;
  border: 0;
  cursor: pointer;
}
.add-open:disabled { opacity: 0.45; cursor: default; }
.add-plus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 15px;
  height: 15px;
  /* Optical centring: the glyph sits high in its box at this size. */
  padding-bottom: 1px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1;
  border: 1.5px solid currentColor;
  border-radius: 50%;
}
.add-menu[hidden] { display: none; }
.add-menu {
  display: flex;
  flex-direction: column;
  /* Thirteen things can be added and the panel is not always tall enough for
     them: the menu scrolls on its own rather than pushing the panel about. */
  max-height: 232px;
  overflow-y: auto;
  margin-top: 5px;
  padding: 3px;
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 6px;
  box-shadow: 0 2px 10px rgba(20, 34, 48, 0.12);
}
.add-item {
  padding: 4px 7px;
  font: inherit;
  font-size: 12px;
  text-align: left;
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: 4px;
  cursor: pointer;
}
.add-item:hover, .add-item:focus-visible { background: rgba(28, 37, 48, 0.06); }

/* The party editor sits in the run of controls rather than under them, so it
   spans the grid -- and without the rule a demo block carries, since a single
   line here divides the controls into two halves that mean nothing. */
#party-editor { grid-column: 1 / -1; margin-top: 4px; }
#party-editor .demo-block { margin-top: 0; padding-top: 0; border-top: 0; }

/* --- demographics, collapsible ------------------------------------------ */
/* One block per demographic, built by app.js from the definition table in
   regions.js. Collapsed still shows the weight slider and the current mode,
   because those are what you glance at; the mode selector and gerrymander
   knobs fold away. */
.demo-block { margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--rule); }
.demo-body[hidden] { display: none; }

/* Matches .ctl-grid label -- it is the same thing in the layout, but a div
   rather than a label because it holds the disclosure button. Wraps rather
   than overflowing: "religion gerrymander 0.398" is close to the column width. */
.ctl-head {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 1px 6px;
  min-width: 0;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
}
.demo-toggle {
  font: inherit;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  padding: 0;
  color: var(--ink-muted);
  background: none;
  border: 0;
  cursor: pointer;
}
.demo-toggle:hover { color: var(--ink); }
.demo-toggle .chev { display: inline-block; transition: transform 0.12s; }
.demo-toggle[aria-expanded="true"] .chev { transform: rotate(90deg); }
.demo-mode-label {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--accent);
}
.demo-mode-label.is-off { color: var(--ink-muted); }

/* Every slider readout, whether written into the HTML or generated. */
.ctl-grid label span, .ctl-head span { font-variant-numeric: tabular-nums; }
.ctl-head-end {
  display: inline-flex;
  align-items: baseline;
  gap: 5px;
  white-space: nowrap;
}

/* Form controls do not inherit the body font. */
.ctl-grid input {
  font: inherit;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  width: 100%;
  padding: 3px 6px;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 5px;
}
.ctl-grid input[type="range"] { padding: 0; border: 0; background: none; accent-color: var(--accent); }
.ctl-sub {
  margin-top: 8px;
  padding-left: 10px;
  border-left: 2px solid var(--rule);
}
.ctl-sub[hidden] { display: none; }

.ctl-grid select {
  font: inherit;
  font-size: 13px;
  width: 100%;
  padding: 3px 4px;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 5px;
}
.ctl-grid select:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* --- the open dropdown --------------------------------------------------- */
/* A native select popup is drawn by the platform, and the bar running down it
   takes the desktop's accent -- orange on stock Ubuntu -- which is the one
   place the app's own blue could not reach. accent-color does not apply to it.
   Chrome's base-select is what hands the popup over to CSS.

   Behind @supports and written against both selects at once, so anything that
   does not know base-select keeps the native popup exactly as it was rather
   than losing its arrow and getting nothing back. */
@supports (appearance: base-select) {
  .ctl-grid select, #bars-stat { appearance: base-select; }
  .ctl-grid select::picker(select), #bars-stat::picker(select) { appearance: base-select; }

  /* base-select draws the closed control as a flex button with a line-height
     of 1.45 and a 24px floor under it, where the native one used its own
     metrics -- 2px taller than before, on every select on the page. Stated in
     ems rather than pixels so the box still follows the font, which is the
     whole reason the label column is measured rather than written down. */
  .ctl-grid select, #bars-stat {
    min-height: 0;
    line-height: 1.3;
  }

  /* The closed control keeps the look it already had. The arrow is ours now,
     the platform's having gone with the platform's appearance. */
  .ctl-grid select::picker-icon, #bars-stat::picker-icon {
    color: var(--ink-muted);
    transition: transform 0.12s;
  }
  .ctl-grid select:open::picker-icon, #bars-stat:open::picker-icon {
    transform: rotate(180deg);
  }

  .ctl-grid select::picker(select), #bars-stat::picker(select) {
    font: inherit;
    font-size: 13px;
    padding: 3px;
    color: var(--ink);
    background: #fff;
    border: 1px solid var(--rule);
    border-radius: 5px;
    box-shadow: 0 6px 18px rgba(16, 24, 33, 0.16);
  }

  .ctl-grid select option, #bars-stat option {
    padding: 3px 6px;
    border-radius: 3px;
  }
  /* The row under the pointer, and the one the keyboard is on: the app's blue
     rather than the desktop's orange, which is the whole point of the block. */
  .ctl-grid select option:hover, #bars-stat option:hover,
  .ctl-grid select option:focus, #bars-stat option:focus {
    background: var(--accent);
    color: #fff;
  }
  /* The one already chosen, when it is not the one being pointed at: tinted,
     so the list says where you are without competing with the highlight. */
  .ctl-grid select option:checked, #bars-stat option:checked {
    background: rgba(94, 137, 194, 0.18);
  }
  .ctl-grid select option:checked:hover, #bars-stat option:checked:hover,
  .ctl-grid select option:checked:focus, #bars-stat option:checked:focus {
    background: var(--accent);
    color: #fff;
  }
  /* base-select offers a tick beside the chosen row. The native popup never
     had one and these lists are three rows of short text, so it would indent
     every label to say what the tint already says. */
  .ctl-grid select option::checkmark, #bars-stat option::checkmark {
    display: none;
  }
}

.ctl-grid input[type="checkbox"] {
  width: auto;
  justify-self: start;
  margin: 0;
  accent-color: var(--accent);
}
.ctl-grid input:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* The 3px matches the switch's track padding above, so GO and STOP line up with
   DEMOGRAPHICS and ELECTION rather than sitting 3px proud of them. */
.ctl-buttons { display: flex; gap: 6px; padding: 0 3px; margin-bottom: 11px; }
.brand + * { margin-top: 0; }
.ctl-buttons button {
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  flex: 1;
  padding: 6px 0;
  /* Plain by default. The accent is worn by whichever button is the obvious
     next action, which moves from START to STOP once a run is going. */
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 5px;
  cursor: pointer;
}
.ctl-buttons button.is-primary {
  color: #fff;
  background: var(--accent);
  border-color: transparent;
}
.ctl-buttons button:hover:not(:disabled) { filter: brightness(1.08); }
.ctl-buttons button:disabled { color: var(--ink-muted); background: #e7ebef; cursor: default; }
#ctl-go:disabled, #ctl-stop:disabled {
  color: var(--ink-muted);
  background: #e7ebef;
  border-color: transparent;
}

/* --- live readout ------------------------------------------------------- */
#run {
  margin: 0 0 2px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 7px 14px;
  margin: 0 0 12px;
}
#run[hidden] { display: none; }
#run .run-wide { grid-column: 1 / -1; }
#run div { margin: 0; }
#run dt {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
}
#run dd {
  margin: 1px 0 0;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* --- results panel ------------------------------------------------------ */
/* Its own panel on the right, so a long list at high N has the full height of
   the window rather than pushing the controls off the bottom of the left one. */
/* A wall, drawn in the same hand as the gable on the other side, so it is an
   image rather than type -- and sized like the gable rather than like a
   wordmark, since that is what it now is. */
.results-title {
  margin: 0 0 16px;
}
/* Standing empty, the panel holds its height rather than collapsing onto one
   paragraph: two panels of wildly different size read as one finished and one
   half-built. The figure came from four fifths of the left panel and is written
   down rather than measured -- a height that moved whenever a control was added
   over there was a surprise waiting to happen, and this one only has to look
   right. Capped by the window on a short screen, where min-height would
   otherwise beat the max-height above and push the panel off the bottom. */
#results.is-resting {
  min-height: min(683px, calc(100% - 32px));
}
/* What the panel holds before there is anything to hold. Larger than the hint
   under the left panel: there it is a footnote to a page of controls, here it
   is the only thing on the panel.

   Centred on the panel rather than tucked under the title. Taken out of the
   flow to do it: centring it in the room the title leaves would sit it a good
   two lines lower than the middle, and the middle is where the eye goes
   looking for the thing that is missing. Directly under the title it read as a
   subtitle; here it reads as a placeholder, which is what it is.

   Safe to take out of the flow because while the panel is resting this is the
   only thing in it, and the panel is held open far taller than the text. */
#results.is-resting #results-hint {
  position: absolute;
  top: 50%;
  left: 16px;                 /* the panel's own side padding */
  right: 16px;
  transform: translateY(-50%);
}
#results-hint {
  margin: 0;
  padding: 0 6px;
  font-size: 13px;
  line-height: 1.55;
  text-align: center;
  color: var(--ink-muted);
}
#results-hint[hidden] { display: none; }
/* The panel has no other heading: the old #results h2 rule was left over from a
   text one and, being an id selector, was quietly zeroing this margin. */
.results-title img {
  display: block;
  /* Sized so RESULTS stands the same height as BALLYMANDER on the other side:
     the caps are 110px of this 551-wide drawing and 192px of that 1619-wide
     one, which at the gable's rendered width puts this one at 195px. That also
     keeps it inside the scrollable area -- the reserved scrollbar gutter lies
     beyond, and anything crossing into it counts as overflow, which gives the
     panel a horizontal scrollbar the moment a vertical one appears. */
  width: calc(100% - 3px);
  max-width: none;
  height: auto;
  margin: 0 auto;
}

#results {
  position: absolute;
  top: 16px;
  right: 16px;
  z-index: 2;
  display: flex;
  flex-direction: column;
  width: 236px;
  max-height: calc(100% - 32px);
  /* No bottom padding: a scroll container clips at its padding box, so
     whatever sits in the bottom padding is inside the scrolling area and the
     bars showed through it under the sticky axis. The gap below the panel's
     content is given to the content instead, where it scrolls with it. */
  padding: 14px 16px 0;
  background: var(--panel-bg);
  border: 1px solid var(--rule);
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(20, 34, 48, 0.10);
  backdrop-filter: blur(6px);
  scrollbar-gutter: stable;    /* as the controls panel: no shift when it appears */
  overflow-y: auto;            /* like the controls panel, rather than running
                                  off the bottom of the screen */
}

/* The panel is the only thing that scrolls, so nothing below it is asked to
   shrink: each block stands at its natural height and the panel carries them.
   The bottom gap belongs to whichever view is showing, the panel having given
   its own away so the sticky axis can reach the foot of the scrolling area. */
#overall, #region-view {
  padding-bottom: 12px;
}
#overall {
  display: flex;
  flex-direction: column;
}
#overall[hidden] { display: none; }
#results[hidden] { display: none; }

#results-list {
  margin-top: 12px;
  padding-top: 11px;
  border-top: 1px solid var(--rule);
  display: flex;
  flex-direction: column;
}
#results-list[hidden] { display: none; }

/* The one thing on this panel that can be changed, so it is drawn as changeable
   rather than as another framed figure: an accent edge, quiet enough not to
   shout over the bars below it. */
#bars-stat {
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  width: 100%;
  padding: 3px 4px;
  color: var(--ink);
  background: #fff;
  border: 1px solid rgba(94, 137, 194, 0.55);
  border-radius: 5px;
}
#bars-stat:hover { border-color: var(--accent); }
#bars-stat:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Not a scrolling box any more, just the space the bars occupy. It used to
   scroll on its own so the axis below it stayed on screen, but that meant two
   reserved scrollbar gutters in a 236px panel, one inside the other, for 30px
   of width spent on a bar nobody needed twice. The panel scrolls; the axis
   stays put by sticking. */
#results-scroll {
  margin-top: 8px;
}

/* --- population bars ---------------------------------------------------- */
/* Rows never move in the DOM. Each one keeps its place and is drawn at its
   rank via translateY, so re-sorting animates instead of teleporting, the
   browser can composite it on the GPU, and the cursor stays over the same
   element while the ranking shifts underneath it. #bars gets its height from
   JS because absolutely positioned children give it none. */
#bars { position: relative; }

.bar-row {
  position: absolute;
  left: 0;
  right: 0;
  height: 18px;
  display: flex;
  align-items: center;
  gap: 6px;
  /* 160ms read as a twitch once the redraws slowed: a row would arrive at its
     new place and then sit still, so the move was all the eye saw. Longer and
     the move is a glide instead, and still finishes well inside the 1,000ms
     between redraws -- a bar that is still travelling when the next draw lands
     spends its life chasing a target it never reaches. */
  transition: transform 240ms ease;
}

.bar-n {
  width: 20px;
  flex: none;
  text-align: right;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

.bar-track {
  position: relative;
  flex: 1;
  height: 11px;
  background: #e7ebef;
  border-radius: 2px;
  overflow: hidden;
}

.bar-fill {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  border-radius: 2px;
  transition: width 240ms ease;   /* with the row's glide, above */
}

.bar-value {
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  padding: 1px 4px;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
  background: var(--panel-bg);
  border-radius: 3px;
  opacity: 0;
  pointer-events: none;
  transition: opacity 90ms;
}
.bar-row:hover .bar-value { opacity: 1; }
/* Election mode: the regions this party wins. A marker rather than a recolour,
   because a bar's colour is its region's colour on the map. */
.bar-seats {
  width: 12px;
  flex: none;
  text-align: right;
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
.bar-seats[hidden] { display: none; }
.bar-row.is-win .bar-n { color: var(--accent); font-weight: 600; }
.bar-row.is-win .bar-seats { color: var(--accent); font-weight: 600; }
.bar-row.is-win .bar-track { outline: 1px solid var(--accent); outline-offset: 1px; }

#bars-axis {
  display: flex;
  justify-content: space-between;
  margin-top: 7px;
  /* Sticks to the foot of the panel while the bars it labels scroll past, which
     is the one thing the inner scrollbar was buying. Painted, or the bars would
     run under it; the panel's own colour, over the panel's own background, so
     it reads as solid. */
  position: sticky;
  bottom: 0;
  z-index: 1;
  background: var(--panel-bg);
  /* 26px lines the min label up with the track's left edge. Both ends now sit
     in the same box as the bars, so the right needs nothing. */
  padding: 5px 0 0 26px;
  border-top: 1px solid var(--rule);
  font-size: 10px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}

/* --- loading / error ---------------------------------------------------- */
#status {
  position: absolute;
  z-index: 3;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 10px 18px;
  background: var(--panel-bg);
  border: 1px solid var(--rule);
  border-radius: 999px;
  box-shadow: 0 2px 10px rgba(20, 34, 48, 0.10);
  font-size: 13px;
  color: var(--ink-muted);
}
#status[hidden] { display: none; }
#status.error { color: #a8321a; border-color: #e3b6ab; }

/* --- party editor -------------------------------------------------------- */
#party-editor[hidden] { display: none; }
#pe-list {
  margin: 0 0 8px;
  padding: 0;
  list-style: none;
}
.pe-row {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 1px 0;
}
.pe-row input { margin: 0; }
.pe-name {
  flex: 1;
  font-size: 11.5px;
  cursor: pointer;
}
.pe-row.is-out .pe-name { color: var(--ink-muted); text-decoration: line-through; }
.pe-actions { display: flex; gap: 5px; }
/* CONTROLS, like the switches and the run buttons. Secondary actions, so they
   are set apart by colour -- ink on white, outlined -- not by type. */
.pe-action {
  flex: 1;
  padding: 5px 6px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink);
  background: #fff;
  border: 1px solid var(--rule);
  border-radius: 4px;
  cursor: pointer;
}
.pe-action:hover:not(:disabled) { border-color: var(--ink-muted); }
.pe-action:disabled { opacity: 0.4; cursor: default; }

/* --- one region's election ---------------------------------------------- */
.view-switch {
  display: flex;
  gap: 4px;
  margin: -2px 0 10px;
  padding: 3px;
  background: rgba(28, 37, 48, 0.05);
  border-radius: 6px;
}
.view-switch[hidden] { display: none; }
.view-switch button {
  flex: 1;
  /* CONTROLS, exactly as the mode switch on the other side. */
  padding: 5px 8px;
  font: inherit;
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--ink-muted);
  background: none;
  border: 0;
  border-radius: 4px;
  cursor: pointer;
}
/* The accent, as START and STOP wear it on the other side: this is a control,
   so it can carry the colour without being mistaken for a region's or a
   party's. It was white on grey, which said which tab was current but put no
   colour on a panel whose only other accent is conditional. */
.view-switch button.is-active {
  color: #fff;
  background: var(--accent);
  box-shadow: 0 1px 3px rgba(20, 34, 48, 0.12);
}
.view-switch button:hover:not(.is-active) { color: var(--ink); }
#region-view[hidden], #region-stages[hidden], #region-pie[hidden] { display: none; }
/* A VALUE, like the phase in the other view -- it is a figure the panel is
   reporting, not a heading over it. The swatch carries the colour the map
   paints the region, so it can be found by eye rather than by counting. */
#region-title {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
#region-swatch {
  flex: none;
  width: 11px;
  height: 11px;
  border: 1px solid rgba(20, 34, 48, 0.22);
  border-radius: 3px;
}
#region-seats {
  margin: 2px 0 9px;
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
#region-pie { width: 150px; height: 150px; display: block; margin: 4px auto 0; }
#region-pie path { stroke: var(--panel-bg); stroke-width: 0.012; }
#region-pie path:hover { stroke: var(--ink); stroke-width: 0.03; }

/* One row per stage of the count: number, the bar, and what happened. */
.stage-row {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-bottom: 3px;
}
.stage-n {
  width: 18px;
  flex: none;
  text-align: right;
  font-size: 9.5px;
  font-variant-numeric: tabular-nums;
  color: var(--ink-muted);
}
.stage-bar {
  flex: 1;
  display: flex;
  height: 13px;
  border-radius: 2px;
  overflow: hidden;
  background: #eef1f4;
}
.stage-seg { height: 100%; }
.stage-seg.is-exhausted {
  background: repeating-linear-gradient(135deg, #c9d2db 0 3px, #dde3e9 3px 6px);
}
.stage-seg:hover { filter: brightness(1.15); }
.stage-what {
  width: 66px;
  flex: none;
  font-size: 9.5px;
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#region-caption {
  margin-top: 7px;
  padding-top: 6px;
  border-top: 1px solid var(--rule);
  font-size: 11px;
  color: var(--ink-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
#region-hint {
  margin: 6px 0 0;
  font-size: 10.5px;
  color: var(--ink-muted);
}
#region-hint[hidden] { display: none; }

/* --- seats pie (election mode) ------------------------------------------ */
#pie {
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--rule);
  text-align: center;
}
#pie[hidden] { display: none; }
/* A LABEL, like every other thing in the panel that names a figure. */
/* Names a chart, above it: the pie's subject, and the bars'. The selector
   under this one picks which statistic the bars show, which is a different
   question from what the chart is. */
.chart-title {
  margin-bottom: 2px;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
}
#pie-svg {
  width: 132px;
  height: 132px;
  overflow: visible;           /* the hover outline sits just outside a wedge */
}
#pie-svg path {
  stroke: var(--panel-bg);
  stroke-width: 0.012;
  cursor: default;
}
#pie-svg path:hover { stroke: var(--ink); stroke-width: 0.03; }
/* Wraps rather than truncating: the instruction is the longest thing this ever
   holds, and an ellipsised instruction is no instruction. No reserved second
   line -- it fits on one at most widths, and where it does not, a jump the
   first time it gives way to a figure costs less than a blank line kept
   forever after. */
#pie-caption {
  margin-top: 4px;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--ink-muted);
}

/* --- hover tooltip ------------------------------------------------------ */
#tooltip {
  position: absolute;
  z-index: 4;
  pointer-events: none;
  padding: 7px 11px 8px;
  background: rgba(28, 37, 48, 0.93);
  color: #fff;
  border-radius: 6px;
  box-shadow: 0 3px 12px rgba(20, 34, 48, 0.28);
  font-size: 12.5px;
  line-height: 1.35;
  white-space: nowrap;
}
#tooltip[hidden] { display: none; }

/* The zone's name, now the second thing here: dimmed to the weight the region
   block used to carry, the two having changed places. */
.tt-name { font-weight: 600; color: #d8e0e8; }

/* Population is a figure about the place like any other, so it is written and
   set like any other -- "population 102,773" beside "religion 0.694" -- rather
   than as a headline with its own units. It used to be two sizes up, which made
   the loudest line the one that varies least: a region holds within a few per
   cent of its target by construction, and a Data Zone is built to hold about
   what every other one holds.

   One rule for all of them -- both halves, population, demographics and the
   zone's party votes alike -- so that "population 565", "average age 41.2" and
   "Alliance 17 votes (18.36%)" are the same kind of line wherever they appear.
   The vote lines were the only figures in the box with no rule of their own,
   so they sat at the tooltip's 12.5px base while everything around them was
   11px, which read as emphasis nobody had asked for. */
.tt-pop, .tt-demo div, .tt-region-pop, .tt-region-demo div, .tt-party div {
  margin-top: 2px;
  font-size: 11px;
  font-variant-numeric: tabular-nums;
  color: #a8b4c0;
}

/* One highlight rule for the whole box: a line is accented when the run is
   actually weighting that term, which is what is-target already means on the
   party rows below. Population is always weighted -- its slider has no off --
   so it is always accented, and a variable joins it as soon as its weight is
   up off zero. */
.tt-pop.is-steered, .tt-region-pop.is-steered,
.tt-demo div.is-steered, .tt-region-demo div.is-steered,
.tt-party div.is-steered {
  color: #bcd6f2;
  font-weight: 600;
}

.tt-region[hidden] { display: none; }

/* The rule sits above the zone rather than above the region, the region having
   moved to the top -- and only when there is a region up there to divide from.

   Strong enough to be told apart from the one inside each half: 18% against
   10% was a distinction on paper and not on screen, so this end of it moves to
   32% and takes a little more air with it. The two are now three times apart
   rather than not quite twice. */
.tt-region:not([hidden]) + .tt-zone {
  margin-top: 7px;
  padding-top: 7px;
  border-top: 1px solid rgba(255, 255, 255, 0.32);
}

.tt-region-name { font-weight: 600; }

/* Lighter than the rule between the two halves: this one separates two kinds
   of figure inside one half, not one place from another. The name and the
   people above it are of a piece with the demographics; the election is not. */
.tt-party:not([hidden]), .tt-region-party:not([hidden]) {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid rgba(255, 255, 255, 0.10);
}

/* Election mode: the region's standings, strongest first. The selected party
   takes the same accent as the population figure. */
.tt-party-row {
  display: flex;
  gap: 8px;
  margin-top: 2px;
  font-size: 11px;               /* the size every figure in the box is set at */
  font-variant-numeric: tabular-nums;
  color: #cfd8e2;
}
.tt-party-rank { width: 14px; text-align: right; color: #97a4b1; }
.tt-party-name { flex: 1; }
.tt-party-votes { margin-left: auto; }
.tt-party-seats {
  width: 12px;
  text-align: right;
  color: #8fa0b0;                /* seats, when a region returns several */
}
.tt-party-row.is-target .tt-party-seats { color: inherit; }
.tt-party-row.is-target { color: #bcd6f2; font-weight: 600; }


/* MapLibre chrome */
.maplibregl-ctrl-group { box-shadow: 0 2px 10px rgba(20, 34, 48, 0.10) !important; }
/* The zoom buttons sit at the top right, left of the results panel: 236px of
   panel plus its 16px offset, and a gap. */
.maplibregl-ctrl-top-right { right: 258px; top: 6px; }
