/* rules.css — the rule-variation cluster at /rules/ and /rules/<ruleset-id>/.
   Loaded only by build/build_variations.py. Everything shared with the game
   surface (.chartnote, .btn, .lede, prose measure) lives in base.css; this file
   holds the two things only this cluster has: a definition list of rule flags,
   and the hub's comparison table. */

/* M17. One declared cap, taken from the container scale documented in base.css.
   The stale 60rem fallback is gone: it disagreed with the token it was standing
   in for, so the page width depended on whether tokens.css had loaded. */
.rules-hub,
.rules-page {
  width: 100%;
  max-width: var(--container-lg);
  margin: 0 auto;
  padding: var(--space-7) var(--gutter) var(--space-9);
}
.rules-hub h1,
.rules-page h1 { margin: 0 0 var(--space-3); }
.rules-hub h2,
.rules-page h2 {
  margin: var(--space-8) 0 var(--space-3);
  font-size: var(--text-lg);
}
.rules-hub p,
.rules-page p {
  max-width: var(--measure-prose);
  margin: 0 0 var(--space-3);
  color: var(--text-2);
}
.rules-page .lede,
.rules-hub .lede { color: var(--text-1); }

.crumb { font-size: var(--text-sm); margin: 0 0 var(--space-4); }
.crumb a { color: var(--text-3); }
.crumb a:hover { color: var(--text-1); }

/* Rule flags. Two columns on a wide viewport, stacked pairs on a narrow one.
   Values are short catalogue strings, but a translation can be two or three
   times longer than the English, so the value column wraps rather than
   truncates and the label column is never allowed to squeeze it out. */
.rules-table {
  display: grid;
  grid-template-columns: 1fr;
  gap: 0;
  margin: 0 0 var(--space-5);
  max-width: 44rem;
  border: 1px solid var(--border-ui);
  border-radius: var(--radius-md);
  overflow: hidden;
}
.rules-table .kv {
  display: grid;
  grid-template-columns: minmax(9rem, 14rem) 1fr;
  gap: var(--space-3) var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface-1);
}
.rules-table .kv:nth-child(even) { background: var(--surface-2); }
.rules-table .kv:last-child { border-bottom: 0; }
.rules-table dt {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-3);
}
.rules-table dd {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--text-1);
  overflow-wrap: anywhere;
}
@media (max-width: 30rem) {
  .rules-table .kv { grid-template-columns: 1fr; gap: var(--space-1); }
}

.rules-edge-note { font-size: var(--text-sm); color: var(--text-3); }

/* The hub table. Four columns is one too many for a 360px viewport, so the
   wrapper scrolls horizontally rather than letting the game name wrap to four
   lines. The row header stays readable because it is the scroll anchor. */
/* N6. A scroller with no visible edge is a scroller nobody scrolls: the hub
   table hides 165px of 453px at 320px wide. Three things fix that, and all three
   are needed. The scrollbar is kept visible rather than left to the platform to
   hide, which is the affordance that costs nothing. site.js measures the overflow
   and sets data-scroll on the wrapper, and the fades below key off it, so a
   wrapper whose table fits is never faded. site.js also makes the wrapper a
   named, focusable region, which is what makes it scrollable from the keyboard.
   The mask gradients are read for their alpha only; the colour in them is not a
   theme colour and never paints. */
.table-wrap {
  margin: 0 0 var(--space-5);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: var(--border-ui) transparent;
}
.table-wrap[data-scroll="end"] {
  -webkit-mask-image: linear-gradient(to right, transparent, black var(--space-7));
          mask-image: linear-gradient(to right, transparent, black var(--space-7));
}
.table-wrap[data-scroll="start"] {
  -webkit-mask-image: linear-gradient(to left, transparent, black var(--space-7));
          mask-image: linear-gradient(to left, transparent, black var(--space-7));
}
.table-wrap[data-scroll="both"] {
  -webkit-mask-image: linear-gradient(to right, transparent, black var(--space-7),
                                     black calc(100% - var(--space-7)), transparent);
          mask-image: linear-gradient(to right, transparent, black var(--space-7),
                                     black calc(100% - var(--space-7)), transparent);
}
.table-wrap:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
}
.rules-index {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--text-sm);
}
.rules-index th,
.rules-index td {
  padding: var(--space-3) var(--space-4);
  text-align: left;
  border-bottom: 1px solid var(--border-subtle);
  white-space: nowrap;
}
.rules-index thead th {
  color: var(--text-3);
  font-weight: var(--weight-medium);
  border-bottom: 1px solid var(--border-ui);
}
.rules-index tbody th { font-weight: var(--weight-medium); }
.rules-index tbody th a { color: var(--text-1); }
/* M15. Every link in this table is a standalone action, the row's chart or its
   variation page, and each one measured 64.5x19. Filling the cell height makes
   the whole cell the target without moving a single figure. */
.rules-index th a,
.rules-index td a {
  display: inline-flex;
  align-items: center;
  min-height: var(--tap-min);
}
.rules-index td { color: var(--text-2); font-variant-numeric: tabular-nums; }

.rules-caveats,
.rules-sources {
  max-width: var(--measure-prose);
  margin: 0 0 var(--space-5);
  padding-left: var(--space-5);
  display: grid;
  gap: var(--space-2);
}
.rules-caveats li,
.rules-sources li {
  font-size: var(--text-sm);
  color: var(--text-2);
  overflow-wrap: anywhere;
}
.rules-sources a { overflow-wrap: anywhere; }

/* M15. Links inside running prose measured 19px tall. Vertical padding on an
   inline box grows the hit area without touching the line box, so the paragraph
   does not move and the leading is not loosened; no negative margin is needed
   because an inline element's vertical margins do not affect layout either. Only
   in-sentence links are treated this way. The standalone actions on these pages,
   the .btn links and the table links above, carry a real min-height instead. */
:is(.rules-page, .rules-hub) :is(p, li, dd, dt, figcaption) a:not(.btn) {
  padding-block: calc((var(--tap-min) - 1em) / 2);
}
/* The quote is evidence, not prose. It sits under its URL and reads as a
   citation so nobody mistakes it for site copy. */
.src-quote {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-3);
}

/* The hub section for games the engine cannot deal. Each row is a link and a
   one line reason, so the reason is set apart the same way a citation is: the
   reader decides from the hub whether the page is worth opening. */
.rules-unplayable-list {
  max-width: var(--measure-prose);
  margin: 0 0 var(--space-5);
  padding-left: var(--space-5);
  display: grid;
  gap: var(--space-3);
}
.rules-unplayable-list li { color: var(--text-2); }

.rules-play { margin: var(--space-4) 0 var(--space-6); }
/* On the variation pages the note is a section of its own, so it lines up with
   the rule table above it instead of keeping the narrower in-game width. */
.rules-page .chartnote {
  margin-bottom: var(--space-5);
  max-width: 44rem;
}
.rules-page .chartnote p { max-width: none; }
/* Four short columns look loose stretched across a desktop container. */
.rules-hub .table-wrap { max-width: 52rem; }

/* ---------------------------------------------------------------- reviews ----
   The /casinos/ cluster (Thread 17). These pages are the widest and the longest
   on the site: one brand review carries 17 tables and about 240 evidence rows.
   The rules below exist so length does not become the reader's problem.        */

/* A review argues from a number, so the numbers are typeset as a column and not
   as prose. Right alignment on every cell after the row header puts the decimal
   points under each other, which is the only way a reader checks a total. */
.score-table th:not(:first-child),
.score-table td,
.inventory-table td { text-align: right; font-variant-numeric: tabular-nums; }
.inventory-table td:first-of-type,
.inventory-table td:nth-of-type(2) { text-align: left; }

/* The total is the row a reader came to check, so it reads as a rule under the
   working, not as one more row of the same weight. */
.score-table tbody tr.total th,
.score-table tbody tr.total td {
  border-top: 1px solid var(--border-ui);
  border-bottom: 0;
  color: var(--text-1);
  font-weight: var(--weight-medium);
}

/* Three figures at one size. The score is not allowed to be the biggest thing on
   the page while the evidence coverage behind it is set as a caption. */
.score-readout {
  margin: 0 0 var(--space-5);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-4);
  border: 1px solid var(--border-subtle);
  border-radius: 2px;
  padding: var(--space-4);
  background: var(--surface-2);
}
.score-readout .readout { display: grid; gap: var(--space-1); }
.score-readout dt {
  font-size: var(--text-xs);
  color: var(--text-3);
  font-weight: var(--weight-medium);
}
.score-readout dd {
  margin: 0;
  font-size: var(--text-lg);
  color: var(--text-1);
  font-variant-numeric: tabular-nums;
  overflow-wrap: anywhere;
}
@media (max-width: 30rem) {
  .score-readout { grid-template-columns: 1fr; gap: var(--space-3); }
}

/* Rule-vector tables carry eight to fourteen columns, so the wrapper scrolls and
   the row header stays the scroll anchor. Capped on desktop because the numeric
   columns are short and a 1920px row puts a metre of empty space between the
   table name and its rule state. */
.review-page .table-wrap,
.review-hub .table-wrap { max-width: 64rem; }

/* One evidence heading per table, seventeen of them on the widest review. They
   need to read as an index the eye can run down, so they are set tighter than a
   section heading and carry a rule above. */
.review-page h3 {
  margin-top: var(--space-5);
  padding-top: var(--space-3);
  border-top: 1px solid var(--border-subtle);
  font-size: var(--text-base);
}

/* Evidence rows are three columns, not two: the value, the class of evidence
   behind it, and the page it came from. The class is the column that stops a
   reader reading an assumption as a fact, so it is never the smallest thing in
   the row. */
.review-page .edge-table dd { overflow-wrap: anywhere; }
.review-page .ev-class {
  display: inline-block;
  margin-right: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-3);
  font-weight: var(--weight-medium);
}

/* The evidence class and the source page are metadata about the value above them,
   not part of it, so they drop to their own line rather than running on. A reader
   scanning for "assumed" finds a column of labels instead of hunting mid-sentence. */
.review-page .ev-meta {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
}
.review-page .ev-meta a { color: var(--text-3); }
/* A row resting on an assumption is marked in the margin. The value still reads
   normally: the point is to make the weak rows countable, not to hide them. */
.review-page .ev[data-confidence="assumed"],
.review-page .ev[data-confidence="inferred"] {
  box-shadow: inset 2px 0 0 var(--border-ui);
}

/* Seventeen disclosures in a row have to read as an index, so the summary is a
   single line: the table name on the left, the count of published core rule
   fields on the right. A reader picks the table worth opening from the count
   without opening anything. */
.review-page .ev-table {
  max-width: 64rem;
  border-top: 1px solid var(--border-subtle);
}
.review-page .ev-table > summary {
  display: flex;
  gap: var(--space-3);
  align-items: baseline;
  padding: var(--space-3) 0;
  cursor: pointer;
}
/* A flex summary drops the native disclosure marker, and a row with no marker
   reads as a heading a reader never thinks to click. The marker is drawn rather
   than borrowed from an icon set: two borders rotated 45 degrees, turned a
   quarter turn when the row is open. It carries the state on its own, so the
   state is not signalled by colour. */
.review-page .ev-table > summary::marker,
.review-page .ev-table > summary::-webkit-details-marker { display: none; content: ""; }
.review-page .ev-table > summary::before {
  content: "";
  flex: none;
  width: 0.4em;
  height: 0.4em;
  margin-right: var(--space-1);
  border-right: 1.5px solid var(--text-3);
  border-bottom: 1.5px solid var(--text-3);
  transform: rotate(-45deg);
  transition: transform 120ms ease;
}
.review-page .ev-table[open] > summary::before { transform: rotate(45deg); }
@media (prefers-reduced-motion: reduce) {
  .review-page .ev-table > summary::before { transition: none; }
}
.review-page .ev-table > summary h3 { flex: 1 1 auto; }
.review-page .ev-table > summary h3 {
  display: inline;
  margin: 0;
  padding: 0;
  border: 0;
  font-size: var(--text-base);
}
.review-page .ev-table > summary:focus-visible {
  outline: 2px solid var(--focus, var(--border-ui));
  outline-offset: 2px;
}
.review-page .ev-count {
  flex: none;
  font-size: var(--text-xs);
  color: var(--text-3);
  font-variant-numeric: tabular-nums;
}
.review-page .ev-table[open] > summary { border-bottom: 1px solid var(--border-subtle); }
.review-page .ev-table > .rules-table { margin-bottom: var(--space-5); }
@media (max-width: 30rem) {
  .review-page .ev-table > summary { flex-wrap: wrap; gap: var(--space-1); }
}

/* The unverified-rules state is the most important sentence on a review, because
   it is the one that stops a reader treating an incomplete rule record as a
   complete one. It uses the same treatment as the chart-unavailable state on the
   variation pages: a rule in the margin and a real explanation, not a warning
   icon and not colour alone. It sits above the score deliberately. A reader
   should meet the caveat before the number. */
.review-page .unverified-state {
  margin: 0 0 var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--brand-line, var(--border-ui));
  background: var(--surface-1);
  color: var(--text-2);
  font-size: var(--text-sm);
  max-width: 62ch;
}

/* The reason a field is missing. It runs to a sentence or two, so it sits under
   the evidence class as body text rather than as another inline label. This is
   the row a reader needs when a value is n.a.: without it, a documented gap and
   an oversight look identical. */
.review-page .ev-reason {
  display: block;
  margin-top: var(--space-1);
  font-size: var(--text-xs);
  color: var(--text-3);
  max-width: 62ch;
}

/* Source lists on the review cluster run to twenty entries on the hub and up to
   forty on a brand page, and several are signed regulator URLs several hundred
   characters long. Wrapped, one of those fills three lines and the list stops
   being a list. Each entry is held to one line here. The href is untouched, so
   the full URL is still what a reader copies, follows or inspects, and the
   provenance is not reduced by making the index readable. */
.review-page .rules-sources,
.review-hub .rules-sources { max-width: 100%; }
.review-page .rules-sources li,
.review-hub .rules-sources li { max-width: 100%; min-width: 0; overflow: hidden; }
.review-page .rules-sources a,
.review-hub .rules-sources a {
  display: block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  overflow-wrap: normal;
}

/* The rule-set column on the hub carries identifiers, not prose, and two of them
   can appear in one cell. Wrapping is correct for an identifier and clipping is
   not, because a clipped identifier reads as a different identifier. */
.review-hub .rules-index tbody th { white-space: nowrap; }

/* Every comparison row is followed by a row holding the client-injected CTA slot,
   which is hidden until the compliance layer decides the visitor may see it. The
   div collapses when hidden but the row and its cell do not, so an empty row was
   still drawing a rule and 25 pixels of padding between every brand. Zero it out
   while it is empty. A populated slot carries its own spacing. */
.review-hub tr.cta-row > td { padding: 0; border: 0; line-height: 0; font-size: 0; }

/* Eight columns of comparison, and six of the headings are longer than the values
   under them. Letting the headings wrap to two or three lines brings the table
   inside the reading measure at 1280, so the whole comparison is visible at once
   instead of two columns sitting past a scroll edge with no cue that they exist.
   The values keep their single line: a wrapped number is a misread number. */
.review-hub .rules-index thead th {
  white-space: normal;
  overflow-wrap: break-word;
  max-width: 8rem;
  vertical-align: bottom;
}
.review-hub .rules-index tbody td { white-space: nowrap; }

/* Rank, score and the three counts are quantities, so they align right on their
   last digit and use tabular figures. The heading aligns with them, because a
   left-set heading over right-set numbers reads as two columns. Columns seven and
   eight hold words and stay left. */
.review-hub .rules-index tr > *:nth-child(n+2):nth-child(-n+6) {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* On a phone the comparison is eight columns wide and the wrapper scrolls. Two
   things follow from that. The brand column pins to the left edge, so the reader
   always knows which row a number belongs to, which is the failure a scrolling
   comparison table normally has. And the headings stop wrapping, because at this
   width an 8rem cap put "Score out of 10" on four lines and made the head of the
   table taller than its body. */
@media (max-width: 48rem) {
  .review-hub .rules-index thead th { white-space: nowrap; max-width: none; }
  .review-hub .rules-index tr > *:first-child {
    position: sticky;
    left: 0;
    background: var(--bg);
    box-shadow: 1px 0 0 var(--border, currentColor);
  }
}

/* The verification date sits under the lede, above the caution and the score. It
   is the first thing a reader needs in order to weigh everything below it. Small,
   muted, and not competing with the H1. */
.review-page .verified-stamp {
  margin: 0 0 var(--space-4);
  font-size: var(--text-sm);
  color: var(--text-2);
}
.review-page .verified-stamp time { font-variant-numeric: tabular-nums; }
