/* election.my — the index is a reference work, so it reads like one:
   a text column, identifiers in monospace, and no chrome that competes with
   the data. The one piece of deliberate emphasis is provenance — the matched
   string, method and score sit ON the card, because that is the product. */

/* Theme. LIGHT IS THE DEFAULT, deliberately: the palette on bare :root is the
   light one, and dark applies only under an explicit [data-theme="dark"] that
   the toggle sets. `prefers-color-scheme` is NOT in the default path -- if it
   were, a reader whose OS is dark would get dark before choosing anything,
   which is exactly what "default to light" rules out. The OS preference is
   still reachable in one click, and the choice persists per browser. */
:root {
  color-scheme: light;
  --bg:        #fbfaf7;
  --panel:     #ffffff;
  --ink:       #16150f;
  --ink-soft:  #57544a;
  --ink-faint: #8b8779;
  --rule:      #e2ded2;
  --accent:    #8c2f1f;   /* oxide red — the seat identifier */
  --accent-bg: #f6ece8;
  --ok:        #2f5d3f;
  --warn:      #8a6d1f;
  --on-accent: #ffffff;   /* text ON --accent; flips in dark, see below */
  --mono: ui-monospace, "SF Mono", "JetBrains Mono", Menlo, monospace;
  --serif: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, sans-serif;
}
:root[data-theme="dark"] {
  color-scheme: dark;
  --bg:#12120f; --panel:#1b1a16; --ink:#eceadf; --ink-soft:#a8a496;
  --ink-faint:#726e62; --rule:#2e2c25; --accent:#e0836c; --accent-bg:#2a1c17;
  --ok:#7fb28e; --warn:#d3b25c;
  /* The accent inverts from oxide red to a light salmon in dark, so white text
     on it drops to ~2:1. The button's foreground follows the accent. */
  --on-accent:#12120f;
}

* { box-sizing: border-box; }
body {
  margin:0; background:var(--bg); color:var(--ink);
  font-family:var(--sans); font-size:16px; line-height:1.55;
  -webkit-font-smoothing:antialiased;
}
a { color:inherit; }
.wrap { max-width:980px; margin:0 auto; padding:0 20px; }

/* ---- masthead ---- */
header.top { border-bottom:1px solid var(--rule); background:var(--panel); }
header.top .wrap { display:flex; align-items:baseline; gap:22px; padding-block:16px; flex-wrap:wrap; }
.brand { font-family:var(--serif); font-size:24px; font-weight:600; letter-spacing:-.01em;
         text-decoration:none; white-space:nowrap; }
.brand span { color:var(--accent); }
.tagline { color:var(--ink-faint); font-size:13px; flex:1 1 auto; min-width:180px; }
/* nowrap: the nav wraps as a whole at narrow widths, and without this a
   two-word label breaks across lines — "Seat" on one, "lookup" on the next. */
nav.top a { font-size:14px; color:var(--ink-soft); text-decoration:none;
            margin-left:16px; white-space:nowrap; }
nav.top a:hover { color:var(--accent); }

/* ---- generic ---- */
h1 { font-family:var(--serif); font-size:30px; line-height:1.2; margin:26px 0 6px; font-weight:600; }
h2 { font-size:13px; text-transform:uppercase; letter-spacing:.09em; color:var(--ink-faint);
     margin:34px 0 12px; font-weight:600; }
.lede { color:var(--ink-soft); margin:0 0 4px; max-width:62ch; }
.code { font-family:var(--mono); font-size:.86em; }
.eid  { font-family:var(--mono); font-size:12px; color:var(--accent);
        background:var(--accent-bg); padding:1px 6px; border-radius:3px;
        text-decoration:none; white-space:nowrap; }
.muted { color:var(--ink-faint); }

/* ---- stat strip ---- */
.stats { display:flex; flex-wrap:wrap; gap:2px; margin:20px 0 8px;
         border:1px solid var(--rule); border-radius:6px; overflow:hidden; background:var(--rule); }
.stat { flex:1 1 130px; background:var(--panel); padding:12px 14px; }
.stat b { display:block; font-family:var(--mono); font-size:22px; font-weight:600; letter-spacing:-.02em; }
.stat span { font-size:11px; text-transform:uppercase; letter-spacing:.07em; color:var(--ink-faint); }

/* ---- article cards ---- */
.feed { list-style:none; padding:0; margin:0; }
.feed li { border-top:1px solid var(--rule); padding:16px 0; }
.feed li:last-child { border-bottom:1px solid var(--rule); }
.art-title { font-family:var(--serif); font-size:19px; line-height:1.32; margin:0 0 5px;
             font-weight:600; }
.art-title a { text-decoration:none; }
.art-title a:hover { color:var(--accent); text-decoration:underline; text-underline-offset:2px; }
.art-snip { margin:0 0 9px; color:var(--ink-soft); font-size:14.5px; max-width:66ch; }
.art-meta { font-size:12px; color:var(--ink-faint); display:flex; gap:10px; flex-wrap:wrap;
            align-items:center; }
.art-meta .dot::before { content:"·"; margin-right:10px; }

/* provenance — the point of the whole thing */
.prov { margin-top:9px; display:flex; flex-wrap:wrap; gap:6px; }
.chip { display:inline-flex; align-items:center; gap:6px; font-size:11.5px;
        border:1px solid var(--rule); border-radius:999px; padding:2px 9px;
        background:var(--panel); text-decoration:none; color:var(--ink-soft); }
.chip:hover { border-color:var(--accent); color:var(--accent); }
.chip b { font-family:var(--mono); font-weight:600; color:var(--accent); font-size:11px; }
.chip i { font-style:normal; color:var(--ink-faint); }
/* The string that actually matched. Quoted because it is the publisher's
   words, not ours -- the chip reads «"Tok Mat" -> Mohamad Hasan · curated
   alias», which is the alias work stated in one line. */
.chip q { quotes:"\201C" "\201D"; color:var(--ink); font-style:italic; }
.chip .to { color:var(--ink-faint); margin:0 -2px; }
/* The gate score sits with the article's own metadata, not on every chip:
   it is a property of the ARTICLE. `em` carries the threshold, so the number
   is never printed without the thing it has to beat. */
.art-meta .gate { font-family:var(--mono); font-size:11px; }
.art-meta .gate em { font-style:normal; color:var(--ink-faint); }

/* "Mentioned as" — the strings that actually resolved to this entity.
   Wrapped inline rather than a table: it sits inside a kv cell, and a seat has
   three forms where a coalition has four and a person can have ten. */
.forms { list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:5px 14px; }
/* flex-end, not baseline: a long form wraps to two lines at 390px, and a
   baseline-aligned count then sits at the TOP right of the block, next to
   the wrong line. */
.forms li { display:inline-flex; align-items:flex-end; gap:6px; }
.forms q { quotes:"\201C" "\201D"; font-style:italic; }
.forms span { font-family:var(--mono); font-size:11.5px; color:var(--ink-faint); }

/* ---- landing composition: layout A, "stats rail" -------------------------

   The feed is the main column, top to bottom, and EVERYTHING else -- the
   title, the standfirst, the stat strip and the four boards -- is the rail
   beside it. The whole point of the layout is that Latest Coverage is on the
   first screen at BOTH breakpoints, and the cheapest way to hold that is to
   leave nothing above the feed that can grow.

   Three grid children in ONE reading order. `.intro` comes first in the
   markup, so the h1 precedes the "Latest coverage" h2 in the document outline
   and a phone reads title -> standfirst -> coverage -> reference with no
   `order` involved; the desktop grid lifts it into row 1 of column 2 and lets
   `.main` span both rows beside it. Explicit placement, not source order, is
   what puts the title on the right -- so the two layouts can never disagree
   about which comes first.

   At <=860px the grid collapses to one column and the rail becomes a 2-up
   block of cards at the foot of the page: the ranked rows and the captions go
   away and each board is reduced to its heading and the link to its own page.
   Same markup, second job -- see the note in home.html for why the caption is
   hidden by the same rule that hides the numbers it qualifies. */
/* 🛑 `grid-template-rows` here is load-bearing and its value is not a taste
   call. `.main` spans both rows, and a spanning item's size is distributed
   across every INTRINSIC track it crosses -- so with `auto auto` (and with
   `min-content auto`) the feed's 6,600px pushed row 1 open and the rail
   started 2,500px down an otherwise empty column. A FLEXIBLE second track
   takes the remainder instead of it being shared out, which leaves row 1 at
   the intro's own height. Measured, both ways: rail top y=2622 -> y=301. */
.cols { display:grid; grid-template-columns:minmax(0, 1.6fr) minmax(0, 1fr);
        grid-template-rows:min-content 1fr;
        column-gap:38px; row-gap:0; align-items:start; }
.cols .main h2:first-child { margin-top:26px; }
.intro { grid-column:2; grid-row:1; }
.main  { grid-column:1; grid-row:1 / span 2; }
.rail-wrap { grid-column:2; grid-row:2; }
/* One continuous rule down the right-hand column: two elements, no row gap. */
.intro, .rail-wrap { border-left:1px solid var(--rule); padding-left:22px; }
.intro { padding-top:26px; }
/* A 30px display headline does not fit a 340px column. It is still the h1 --
   the page's claim about itself -- just set as a sidebar masthead. */
.intro h1 { font-size:23px; margin:0 0 8px; }
.intro .lede { font-size:14.5px; }
.rail { display:flex; flex-direction:column; gap:28px; }
.rail-title { display:none; }
.board h2 { margin:0 0 8px; }
.rank { list-style:none; margin:0; padding:0; }
.rank li { border-top:1px solid var(--rule); }
.rank li:first-child { border-top:0; }
.rank li.none { padding:7px 0; font-size:13px; color:var(--ink-faint); }
.rank a { display:flex; align-items:baseline; gap:8px; padding:7px 0;
          font-size:14.5px; text-decoration:none; }
.rank a:hover .nm { color:var(--accent); }
.rank .nm { font-weight:600; }
.rank .c  { font-family:var(--mono); font-size:12.5px; color:var(--accent);
            font-variant-numeric:tabular-nums; }
/* margin-left:auto, so the context is right-aligned however long the name is;
   it is the detail the grid cell used to carry as its own line. */
.rank .st { margin-left:auto; font-family:var(--mono); font-size:11px;
            color:var(--ink-faint); text-align:right; }
.board .cap { font-size:12.5px; color:var(--ink-faint); margin:9px 0 0;
              border-top:1px solid var(--rule); padding-top:9px; }
.board .more { display:block; margin-top:9px; font-size:13px; color:var(--accent);
               text-decoration:none; }
.board .more span { display:block; font-family:var(--mono); font-size:11px;
                    color:var(--ink-faint); }
.board .more:hover { text-decoration:underline; text-underline-offset:2px; }

@media (max-width: 860px) {
  .cols { grid-template-columns:1fr; gap:0; }
  .rail-wrap { margin-top:34px; border-left:0; padding-left:0; }
  .rail-title { display:block; margin:0 0 12px; }
  .rail { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); gap:2px;
          background:var(--rule); border:1px solid var(--rule);
          border-radius:6px; overflow:hidden; }
  .board { background:var(--panel); padding:11px 13px; }
  .board h2 { margin:0; }
  /* `.qual` is the part of a board heading that qualifies the ROWS -- the
     window ("· last 30 days"), or the second half of "Parties & coalitions".
     The card that replaces the rows links to the all-time page, so a card
     headed "Seats · last 30 days" would label its own destination wrongly;
     and a heading that wraps to two lines drops that card's link below its
     neighbour's. One word each, so the row of cards reads across. */
  .rank, .board .cap, .board .qual { display:none; }
  .board .more { margin-top:3px; font-size:14px; font-weight:600; }

  /* Everything above the feed is on a budget, because layout A's one
     requirement is that Latest Coverage is on the first screen at BOTH
     breakpoints -- and at 390px the masthead, the headline, the standfirst and
     a six-cell stat strip together came to ~850px, one pixel-row more than the
     viewport. Measured, not guessed: tests/test_web.py renders the page at
     320/360/375/390 and fails if the first headline falls below the fold.

     The tagline goes first and costs nothing: it is the SAME SENTENCE as the
     h1 directly beneath it, which is invisible on a desktop masthead and pure
     duplication once they stack. */
  .tagline { display:none; }
  /* One column, and the grid's explicit placement has to be undone or every
     child lands back in column 2. The stat strip needs no `order` trick to sit
     under the feed: it is inside the rail, which is already the foot of the
     page in the markup. */
  .intro, .main, .rail-wrap { grid-column:auto; grid-row:auto; }
  .intro { border-left:0; padding:0; }
  header.top .wrap { gap:12px 16px; padding-block:13px; }
  nav.top a { margin-left:0; margin-right:14px; }
  .theme-btn { margin-left:0; }
  .intro h1 { font-size:25px; margin:22px 0 8px; }
  .intro .lede { font-size:15px; }
  /* Three across rather than two: six stats in two rows, not three. */
  .stat { flex:1 1 90px; padding:10px 11px; }
  .stat b { font-size:18px; }
  .stat span { font-size:10px; }
}
/* One column of cards below 360px: at 390 a half-width cell still holds
   "Everyone in the corpus" on two lines, and two-up is the pattern. Below that
   the mono sub-line ("with each state's covered-seat count") is what breaks. */
@media (max-width: 360px) {
  .rail { grid-template-columns:1fr; }
}

/* ---- seat grids ---- */
.grid { display:grid; gap:2px; background:var(--rule); border:1px solid var(--rule);
        border-radius:6px; overflow:hidden;
        grid-template-columns:repeat(auto-fill, minmax(210px, 1fr)); }
.cell { background:var(--panel); padding:10px 12px; text-decoration:none; display:block; }
.cell:hover { background:var(--accent-bg); }
.cell .n { font-family:var(--mono); font-size:11px; color:var(--ink-faint); }
.cell .nm { display:block; font-weight:600; font-size:14.5px; margin:1px 0; }
.cell .c  { font-family:var(--mono); font-size:12px; color:var(--accent); }
.cell.empty .c { color:var(--ink-faint); }
.cell.empty .nm { font-weight:500; color:var(--ink-soft); }

/* A jump list, not a nav bar: /seats is 822 cells under 16 headings, and
   without this the only way to reach Terengganu is to scroll past Selangor. */
.jump { display:flex; flex-wrap:wrap; gap:6px 8px; margin:16px 0 4px; }
.jump a { font-family:var(--mono); font-size:12px; text-decoration:none;
          color:var(--ink-soft); background:var(--panel); padding:3px 8px;
          border:1px solid var(--rule); border-radius:4px; }
.jump a:hover { color:var(--accent); border-color:var(--accent); }
h2 a { text-decoration:none; }
h2 a:hover { color:var(--accent); }

/* Data tables. Wrapped in .scroll rather than allowed to widen the page --
   a four-column table at 390px must scroll inside its own box, never make the
   body scroll sideways. */
.scroll { overflow-x:auto; border:1px solid var(--rule); border-radius:6px;
          background:var(--panel); margin:14px 0 4px; }
table.tab { border-collapse:collapse; width:100%; font-size:14px; min-width:min(100%, 420px); }
table.tab th { text-align:left; font-size:11px; text-transform:uppercase;
               letter-spacing:.07em; color:var(--ink-faint); font-weight:600;
               padding:10px 14px; border-bottom:1px solid var(--rule); white-space:nowrap; }
table.tab td { padding:8px 14px; border-top:1px solid var(--rule); vertical-align:top; }
table.tab tbody tr:first-child td { border-top:0; }
table.tab .num { text-align:right; font-family:var(--mono); white-space:nowrap; }
table.tab .num.low { color:var(--ok); }
table.tab .why { color:var(--ink-soft); font-size:13px; }
h3.sub { font-size:13px; text-transform:uppercase; letter-spacing:.08em;
         color:var(--ink-faint); margin:28px 0 0; font-weight:600; }
.verdict .demo-note { margin-top:10px; padding-top:9px; border-top:1px solid var(--rule);
                      font-size:13px; color:var(--ink-faint); }

/* ---- search ---- */
form.q { display:flex; gap:8px; margin:18px 0 6px; flex-wrap:wrap; }
form.q input[type=text] { flex:1 1 260px; font:inherit; padding:10px 12px;
  border:1px solid var(--rule); border-radius:5px; background:var(--panel); color:var(--ink); }
form.q select { font:inherit; padding:10px; border:1px solid var(--rule);
  border-radius:5px; background:var(--panel); color:var(--ink); }
form.q button { font:inherit; font-weight:600; padding:10px 20px; border:0; border-radius:5px;
  background:var(--accent); color:var(--on-accent); cursor:pointer; }

.verdict { border:1px solid var(--rule); border-left:3px solid var(--rule);
           border-radius:5px; background:var(--panel); padding:14px 16px; margin:16px 0; }
.verdict.matched  { border-left-color:var(--ok); }
.verdict.no_match { border-left-color:var(--warn); }
.verdict.stopped  { border-left-color:var(--ink-faint); }
.verdict h3 { margin:0 0 6px; font-size:15px; }
.verdict p { margin:0; color:var(--ink-soft); font-size:14px; max-width:64ch; }
.verdict ul { margin:10px 0 0; padding-left:0; list-style:none; }
.verdict li { padding:7px 0; border-top:1px solid var(--rule); font-size:14px; }

table.kv { border-collapse:collapse; width:100%; font-size:14px; }
table.kv td { padding:7px 10px 7px 0; border-top:1px solid var(--rule); vertical-align:top; }
table.kv td:first-child { color:var(--ink-faint); width:150px; font-size:12.5px;
  text-transform:uppercase; letter-spacing:.06em; }

.pager { display:flex; gap:12px; margin:22px 0; font-size:14px; }
.pager a { color:var(--accent); }

.note { font-size:13px; color:var(--ink-faint); border-top:1px solid var(--rule);
        margin-top:34px; padding-top:14px; max-width:70ch; }
footer.bot { margin-top:44px; border-top:1px solid var(--rule); background:var(--panel); }
footer.bot .wrap { padding:20px; font-size:12.5px; color:var(--ink-faint); }
footer.bot p { margin:0 0 6px; max-width:78ch; }
.warnbar { background:var(--warn); color:#12120f; font-size:13px; padding:7px 0; }

/* ---- theme toggle ---- */
.theme-btn { font:inherit; font-size:13px; line-height:1; color:var(--ink-soft);
  background:none; border:1px solid var(--rule); border-radius:999px;
  padding:5px 11px; margin-left:16px; cursor:pointer; white-space:nowrap; }
.theme-btn:hover { color:var(--accent); border-color:var(--accent); }
/* One button, two labels: whichever the current theme is NOT is the one shown,
   because the label names the ACTION, not the state. */
.theme-btn .to-dark  { display:inline; }
.theme-btn .to-light { display:none; }
:root[data-theme="dark"] .theme-btn .to-dark  { display:none; }
:root[data-theme="dark"] .theme-btn .to-light { display:inline; }
/* No script, no toggle -- it would be a button that does nothing. */
.no-js .theme-btn { display:none; }
