/* Mahjong Compendium — styled to match pandaren.org:
   ink-wash greens and browns, cream text, temple-gold accents, and the
   "ZCOOL XiaoWei" display face for headings and chrome. Body copy stays a
   serif at reading size, as the hub site does, so long entries remain legible. */

:root{
  /* ground */
  --bg:#10271f; --bg-mid:#1f5142; --bg-warm:#6f342d;

  /* surfaces — translucent, so the ground gradient shows through */
  --panel:rgba(12,32,25,.62); --panel-2:rgba(8,24,19,.8);
  --line:rgba(214,173,85,.22); --line-strong:rgba(214,173,85,.44);

  /* ink */
  --ink:#f3e3bd; --ink-bright:#fffaeb;
  --dim:rgba(243,227,189,.74); --faint:rgba(243,227,189,.52);

  /* temple gold */
  --accent:#d6ad55; --accent-lit:#f0cd7d; --accent-ink:#24160d;

  /* evidence grades, warmed to the palette but kept apart from one another */
  --corroborated:#52b98d; --partial:#d6ad55; --conflicting:#d2624a;
  --single-source:#5aa9c9; --unsupported:#a78bd0;

  /* the two layers every page is split into: the sources themselves, in the
     site's own gold, and what an AI model wrote from them, in a cool grey-blue
     that none of the families or evidence grades use */
  --src:#d6ad55; --ai:#9fb4c8; --ai-wash:rgba(159,180,200,.06);

  /* The site's own face, used for headings AND for running text. Georgia and
     Times are only a fallback for a reader who cannot load the web font, not
     the intended look: there is no reason to set the prose in a default serif
     when the chosen face reads perfectly well at paragraph size. */
  --display:"ZCOOL XiaoWei",Georgia,"Times New Roman",serif;
  --body:"ZCOOL XiaoWei",Georgia,"Times New Roman",serif;
  /* small print, labels and anything that has to stay crisp at 12px */
  --ui:Arial,Helvetica,sans-serif;

  --radius:12px; --max:76rem;
  /* the header's and footer's heights, so the landing map fills exactly what is
     left and the footer stays on screen under it. Both are measured in tree.js,
     because the nav wraps on a narrow window; these are only the fallbacks. */
  --bar:3.6rem; --foot:2.6rem;
  /* one duration for the fold, shared by the panel and its chevron so they
     move as one thing */
  --fold:280ms;
  /* a riichi tile: the amber body, the darker face of its thickness, and the
     ivory of the plate set into it, taken from the tile artwork itself */
  --tile-body:#febd3f; --tile-edge:#b9771a; --tile-plate:#f5f0eb;
}
*{box-sizing:border-box}
/* A class that sets display beats the browser's own [hidden] rule, and several
   here do. Nothing on this site wants to be hidden and laid out at once. */
[hidden]{display:none!important}
html{-webkit-text-size-adjust:100%; scroll-behavior:smooth}
body{
  /* a column so that a short page — the tools index is the first one — puts its
     footer at the bottom of the window rather than halfway up it */
  margin:0; min-height:100vh; display:flex; flex-direction:column; color:var(--ink);
  background-color:var(--bg);
  background-image:
    radial-gradient(circle at 50% 0,rgba(214,173,85,.18),transparent 30rem),
    linear-gradient(135deg,#10271f 0%,#1f5142 45%,#6f342d 100%);
  background-attachment:fixed; background-repeat:no-repeat; background-size:cover;
  font:16px/1.7 var(--body);
}
/* keeps the warm end of the gradient from washing out long-form text */
body::before{content:""; position:fixed; inset:0; z-index:-1; pointer-events:none;
  background:linear-gradient(180deg,rgba(5,15,11,.2) 0%,rgba(5,15,11,.55) 100%)}

a{color:var(--accent); text-decoration:none}
a:hover{color:var(--accent-lit); text-decoration:underline; text-underline-offset:.18em}
:focus-visible{outline:2px solid var(--accent); outline-offset:2px; border-radius:4px}
::selection{background:rgba(214,173,85,.3); color:var(--ink-bright)}
code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; font-size:.88em;
  background:var(--panel-2); border:1px solid var(--line); color:var(--ink);
  padding:.1em .35em; border-radius:5px}
main{flex:1 0 auto; width:100%; max-width:var(--max); margin:0 auto; padding:2rem 1rem 4rem}

/* ZCOOL XiaoWei ships one weight, so headings hold at 400 and lean on size,
   colour and shadow for hierarchy rather than a synthesised bold. */
h1,h2,h3{font-family:var(--display); font-weight:400; letter-spacing:.01em}
h1{font-size:clamp(2rem,4.4vw,2.9rem); line-height:1.18; margin:0 0 .5rem;
  color:var(--ink-bright); text-shadow:0 .35rem 1.1rem rgba(0,0,0,.5)}
h2{font-size:1.6rem; margin:2.3rem 0 .6rem; color:var(--ink-bright)}
h3{font-size:1.2rem; margin:1.7rem 0 .4rem; color:var(--accent)}
p{margin:.7rem 0}
.lede{font-size:1.12rem; font-style:italic; color:var(--dim); max-width:60ch}
.dim{color:var(--dim)}

/* chrome */
.bar{display:flex; gap:1rem; align-items:center; justify-content:space-between;
  padding:.7rem 1rem; background:linear-gradient(180deg,rgba(7,19,15,.94),rgba(7,19,15,.78));
  border-bottom:1px solid var(--line-strong);
  position:sticky; top:0; z-index:20; backdrop-filter:blur(8px); flex-wrap:wrap}
.brand{font-family:var(--display); font-size:1.3rem; color:var(--ink-bright);
  letter-spacing:.02em; text-shadow:0 .25rem .8rem rgba(0,0,0,.6)}
.brand:hover{color:var(--accent); text-decoration:none}
.bar nav{display:flex; gap:.25rem; flex-wrap:wrap}
.bar nav a{font-family:var(--display); color:var(--dim); padding:.3rem .65rem;
  border:1px solid transparent; border-radius:8px; font-size:1rem}
.bar nav a:hover{background:rgba(214,173,85,.12); border-color:var(--line);
  color:var(--ink-bright); text-decoration:none}

/* A nav item with the pages under it hanging off it. It is a link first and a
   menu second: the menu opens on hover and on focus, which covers a mouse and a
   keyboard, and a touch screen has neither — there the tap follows the link to
   the section's own page, which lists the same things. The items stay in the
   tab order while the menu is closed, so tabbing to one is what opens it; that
   is why this hides with opacity rather than display, which would take them out
   of the tab order and leave a keyboard with no way in. */
.drop{position:relative; display:inline-flex}
/* Two things keep the pointer from falling out of the menu on the way to it.
   The panel abuts the item rather than floating below it, because a gap there
   is a gap in the hover: the pointer crosses ground that belongs to neither and
   the menu closes under it. And the panel is centred on the item rather than
   hung off its left edge, so every entry in it sits directly below the word
   that opened it — the reach is straight down, and a straight line down never
   leaves the two boxes. Hung off the left, a panel three times the item's width
   makes every reach a diagonal, and a diagonal clips the corner between them. */
.drop-menu{position:absolute; left:50%; top:100%; z-index:30; margin:0; padding:.35rem;
  list-style:none; min-width:13rem; max-width:calc(100vw - 1.5rem);
  background:rgba(8,21,17,.97); border:1px solid var(--line-strong);
  border-radius:10px; box-shadow:0 .8rem 1.8rem rgba(0,0,0,.5); backdrop-filter:blur(7px);
  opacity:0; pointer-events:none; transform:translate(-50%,-.25rem);
  transition:opacity .16s ease, transform .16s ease}
.drop:focus-within .drop-menu,
.drop.is-open .drop-menu{opacity:1; pointer-events:auto; transform:translate(-50%,0)}
@media(hover:hover){
  .drop:hover .drop-menu{opacity:1; pointer-events:auto; transform:translate(-50%,0)}
}
.bar nav .drop-menu a{display:block; border-radius:7px; font-size:.95rem; white-space:nowrap}
.foot{justify-content:center; position:static; border-top:1px solid var(--line-strong); border-bottom:0;
  background:linear-gradient(0deg,rgba(7,19,15,.9),rgba(7,19,15,.6));
  color:var(--faint); font-family:var(--ui); font-size:.8rem; margin-top:3rem}

/* landing: the map IS the page, so main loses its column and the panels float
   over the canvas rather than sitting beside it */
body.map main{max-width:none; margin:0; padding:0}
/* on a reading page the footer wants air above it; under a full-height map that
   air is what pushes it off the screen */
body.map .foot{margin-top:0}
.hero{position:relative; width:100%;
  height:calc(100dvh - var(--bar) - var(--foot));
  min-height:32rem; overflow:hidden;
  background:radial-gradient(120% 120% at 50% 42%,#1d4538 0%,#081712 74%);
  border-bottom:1px solid var(--line-strong)}
.stage{position:absolute; inset:0; cursor:grab; touch-action:none; user-select:none}
.stage.grabbing{cursor:grabbing}
.map svg.map{display:block; width:100%; height:100%}

/* generation rings and the ancestral hub at the centre */
svg.map .gen{fill:none; stroke:rgba(214,173,85,.10); stroke-width:1}
svg.map .hub{fill:rgba(214,173,85,.05); stroke:rgba(214,173,85,.22); stroke-width:1}
svg.map .edge{fill:none; stroke-width:1.5; stroke-opacity:.5; transition:stroke-opacity .18s ease}
svg.map .edge.dim{stroke-opacity:.09}
svg.map .node{cursor:pointer}
svg.map .node circle{stroke-width:1.5; transition:opacity .18s ease}
svg.map .node .halo{fill:none; stroke-opacity:0; stroke-width:2; transition:stroke-opacity .18s ease}
svg.map .node:hover .halo,svg.map .node:focus .halo,svg.map .node.hit .halo{stroke-opacity:.85}
svg.map .node:focus{outline:none}
svg.map .node.dim{opacity:.16}
/* a variant this compendium added, not one the chart carries */
svg.map .node.added circle{stroke-dasharray:3 3}
/* the chart drew these links dashed: no source establishes the descent */
svg.map .node.c-disputed circle,svg.map .node.c-placeholder circle{stroke-dasharray:2 4}

/* floating panels */
.panel{position:absolute; z-index:2; background:rgba(8,21,17,.86);
  border:1px solid var(--line-strong); border-radius:var(--radius);
  backdrop-filter:blur(7px); box-shadow:0 1rem 2rem rgba(0,0,0,.4)}
.intro-panel{left:1.1rem; top:1.1rem; width:min(23rem,calc(100% - 2.2rem)); padding:1.1rem 1.2rem}
.intro-panel h1{margin:0 0 .4rem; font-size:1.5rem}
.panel-head{display:flex; align-items:flex-start; justify-content:space-between; gap:.6rem}

/* The panel folds like a curtain: the body's row collapses from 1fr to 0fr,
   which animates a height nobody has to measure, while the content inside
   slides up behind the header and fades. The two together read as the box
   being drawn upwards rather than simply vanishing.
   visibility is transitioned too, with a delay, so the links inside leave the
   tab order once they are out of sight but not a moment before. */
.panel-body{display:grid; grid-template-rows:1fr;
  transition:grid-template-rows var(--fold) cubic-bezier(.4,0,.2,1)}
.panel-roll{overflow:hidden;
  transition:opacity calc(var(--fold) * .7) ease,
             transform var(--fold) cubic-bezier(.4,0,.2,1),
             visibility 0s}
.intro-panel.closed .panel-body{grid-template-rows:0fr}
.intro-panel.closed .panel-roll{opacity:0; transform:translateY(-.55rem); visibility:hidden;
  transition:opacity calc(var(--fold) * .55) ease,
             transform var(--fold) cubic-bezier(.4,0,.2,1),
             visibility 0s linear var(--fold)}
/* the map is tall enough on a desktop that the panel never has to get out of
   the way; on a phone it does, so the control only exists there */
.collapse{display:none}
/* the two buttons share the row rather than stacking: they split the width
   between them and hold their labels on one line */
.intro-panel .cta{margin:.9rem 0 0; flex-wrap:nowrap}
.intro-panel .cta .btn{flex:1 1 0; text-align:center; white-space:nowrap;
  font-size:.95rem; padding:.5rem .7rem}
.tools{right:1.1rem; top:1.1rem; padding:.55rem; display:flex; gap:.5rem; align-items:center}
.find input{width:11rem; padding:.36rem .6rem; border-radius:999px;
  border:1px solid var(--line-strong); background:var(--panel-2); color:var(--ink);
  font-family:var(--ui); font-size:.82rem}
.find input:focus{outline:2px solid var(--accent); outline-offset:1px}
.zoomers{display:flex; gap:.3rem}
.zoomers button{min-width:2rem; padding:.3rem .55rem; border-radius:999px; cursor:pointer;
  border:1px solid var(--line-strong); background:var(--panel-2); color:var(--dim);
  font-family:var(--ui); font-size:.82rem}
.zoomers button:hover{color:var(--accent); border-color:var(--accent)}
.vh{position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0)}

#hud{position:absolute; inset:0; pointer-events:none; z-index:1}
.hint{position:absolute; left:0; right:0; bottom:.7rem; text-align:center; z-index:2;
  color:var(--faint); font-family:var(--ui); font-size:.76rem; letter-spacing:.06em; margin:0}
.hint a{pointer-events:auto}
#labels{position:absolute; inset:0; overflow:hidden}
/* label text sits over a busy map, so it stays sans for crispness at 12px */
.lab{position:absolute; left:0; top:0; white-space:nowrap; font-family:var(--ui);
  touch-action:none; user-select:none; -webkit-user-drag:none;
  font-size:.76rem; font-weight:600; padding:.08rem .35rem; border-radius:6px;
  text-decoration:none; pointer-events:auto;
  will-change:transform; background:rgba(6,17,13,.74); opacity:0; transition:opacity .2s ease}
.lab.on{opacity:.96}
.lab:hover{background:rgba(6,17,13,.95); text-decoration:underline}

@media(max-width:60rem){
  .hero{height:calc(100dvh - var(--bar) - var(--foot)); min-height:26rem}
  /* On a phone the panel is the one thing standing between the reader and the
     map, so it folds down to its title — by hand, or by itself the moment the
     map is dragged. */
  .collapse{display:flex; align-items:center; justify-content:center;
    flex:0 0 auto; width:2rem; height:2rem; margin:-.2rem -.3rem 0 0; padding:0;
    border:1px solid var(--line-strong); border-radius:50%; cursor:pointer;
    background:var(--panel-2); color:var(--dim)}
  .collapse:hover,.collapse:focus-visible{color:var(--accent); border-color:var(--accent)}
  .chev{width:.5rem; height:.5rem; border-right:2px solid currentColor;
    border-bottom:2px solid currentColor; transform:translateY(-.12rem) rotate(45deg);
    transition:transform var(--fold) cubic-bezier(.4,0,.2,1)}
  .intro-panel.closed .chev{transform:translateY(.1rem) rotate(-135deg)}
  .intro-panel.closed h1{margin-bottom:0}
  .intro-panel{left:.7rem; top:.7rem; width:calc(100% - 1.4rem); padding:.7rem .9rem}
  .intro-panel h1{font-size:1.2rem}
  .intro-panel .legend{display:none}
  .intro-panel .cta{margin-top:.5rem}
  /* matches the specificity of the side-by-side rule above, so the phone
     sizing still wins here */
  .intro-panel .cta .btn{font-size:.9rem; padding:.35rem .8rem}
  .tools{right:.7rem; left:.7rem; top:auto; bottom:2.3rem; justify-content:space-between}
  .find input{width:100%}
  .find{flex:1}
}

#tip{position:absolute; padding:.5rem .7rem; background:rgba(9,25,20,.97);
  border:1px solid var(--line-strong); border-left-width:3px; border-radius:9px;
  font-size:.85rem; max-width:17rem; line-height:1.45; pointer-events:none;
  box-shadow:0 .6rem 1.6rem rgba(0,0,0,.5)}
#tip b{display:block; font-family:var(--display); font-weight:400; font-size:1rem;
  color:var(--ink-bright)}
#tip span{display:block; color:var(--dim); font-family:var(--ui); font-size:.78rem}

/* legend, buttons and the evidence counts, shared with the rest of the site */
.legend{list-style:none; display:flex; flex-wrap:wrap; gap:.4rem .9rem; padding:0; margin:1rem 0}
.legend li{display:flex; align-items:center; gap:.4rem; color:var(--dim);
  font-family:var(--ui); font-size:.82rem}
.legend i{width:.7rem; height:.7rem; border-radius:50%; display:inline-block;
  box-shadow:0 0 .4rem rgba(0,0,0,.5)}
.cta{display:flex; gap:.6rem; flex-wrap:wrap; margin-top:.8rem}

/* buttons borrow the hub's lacquered-plaque treatment */
.btn{display:inline-block; font-family:var(--display); font-size:1.02rem;
  background:rgba(243,227,189,.9); color:var(--accent-ink);
  border:2px solid rgba(214,173,85,.78); border-radius:.3rem; padding:.5rem 1.1rem;
  text-shadow:0 .1rem .3rem rgba(0,0,0,.2); box-shadow:0 .35rem 1rem rgba(0,0,0,.35);
  transition:transform 200ms ease, background 200ms ease, box-shadow 200ms ease}
.btn:hover{background:rgba(255,240,200,.96); text-decoration:none;
  transform:translateY(-2px); box-shadow:0 .5rem 1.3rem rgba(0,0,0,.45)}
.btn:active{transform:translateY(0); box-shadow:0 .2rem .8rem rgba(0,0,0,.35)}
.btn.ghost{background:rgba(16,39,31,.55); color:var(--ink); border-color:var(--line-strong)}
.btn.ghost:hover{background:rgba(31,81,66,.65); color:var(--ink-bright)}

.counts{display:flex; gap:.6rem; flex-wrap:wrap; margin:1.5rem 0}
.counts.wide{margin-top:2.5rem}
.count{flex:1 1 8rem; background:var(--panel); border:1px solid var(--line);
  border-radius:var(--radius); padding:.8rem 1rem; border-top:3px solid var(--line-strong);
  box-shadow:0 .5rem 1.2rem rgba(0,0,0,.3)}
.count b{display:block; font-family:var(--display); font-weight:400; font-size:1.9rem;
  line-height:1.15; color:var(--ink-bright)}
.count span{color:var(--dim); font-family:var(--ui); font-size:.78rem;
  letter-spacing:.1em; text-transform:uppercase}
.count.e-corroborated{border-top-color:var(--corroborated)}
.count.e-partial{border-top-color:var(--partial)}
.count.e-conflicting{border-top-color:var(--conflicting)}
.count.e-single-source{border-top-color:var(--single-source)}
.count.e-unsupported{border-top-color:var(--unsupported)}

/* tables
   The restyle dropped this block, which left the variants list and every
   cross-comparison table as bare browser defaults: no rules, no header row,
   no full width. Restored in the ink-wash palette. */
.scroll{overflow-x:auto; border:1px solid var(--line-strong); border-radius:var(--radius);
  background:var(--panel); margin:1rem 0}
table{border-collapse:collapse; width:100%; font-size:.92rem}
th,td{text-align:left; padding:.55rem .75rem; border-bottom:1px solid var(--line);
  vertical-align:top}
thead th{background:var(--panel-2); color:var(--faint); font-family:var(--ui);
  font-weight:400; font-size:.74rem; letter-spacing:.06em; text-transform:uppercase;
  white-space:nowrap; border-bottom:1px solid var(--line-strong)}
tbody tr:last-child td{border-bottom:0}
tbody tr:hover{background:rgba(214,173,85,.06)}
td.num{font-variant-numeric:tabular-nums}
/* Auto table layout hands its spare width to the widest column, which left a
   gap between the variant and its family. Every cell in this list is a short
   label, so hold them on one line and let the last column take the slack: the
   named columns then sit at their own width, side by side. Past that the
   scroll container does what it does anywhere else and scrolls sideways. */
.list td,.list th{white-space:nowrap}
.list th:last-child,.list td:last-child{width:100%}
/* the group row over the columns, and the analysis columns under it, washed
   and ruled off from the entry's own so the verdicts never read as sourcing */
.list .groups th{color:var(--dim); letter-spacing:.08em; padding:.5rem .75rem .3rem;
  border-bottom:1px solid var(--line)}
.list .groups .badge{font-size:.62rem; margin-right:.3rem; text-transform:uppercase}
.list .ai-col{background:var(--ai-wash)}
.list thead .ai-col{background:rgba(159,180,200,.08)}
.list th.ai-col:not(.ai-col ~ .ai-col),.list td.ai-col:not(.ai-col ~ .ai-col){border-left:2px dashed color-mix(in srgb,var(--ai) 55%,transparent)}

/* sortable column headers: the button fills the cell so the whole header is the
   target, and inherits the header's type rather than the browser's button face */
th.sortable{padding:0}
th.sortable button{display:block; width:100%; text-align:inherit; cursor:pointer;
  font:inherit; letter-spacing:inherit; text-transform:inherit; color:inherit;
  background:none; border:0; padding:.55rem .75rem}
th.sortable button:hover{color:var(--ink); background:rgba(214,173,85,.1)}
th.sortable button:focus-visible{outline:2px solid var(--accent); outline-offset:-2px}
th.sortable button::after{content:"\2195"; margin-left:.35rem; opacity:.3}
th.sortable[aria-sort="ascending"] button::after{content:"\2191"; opacity:1; color:var(--accent)}
th.sortable[aria-sort="descending"] button::after{content:"\2193"; opacity:1; color:var(--accent)}

@media(prefers-reduced-motion:reduce){
  :root{--fold:0ms}
  .panel-body,.panel-roll,.chev{transition:none}
}

/* pills + tags */
.pill,.tags li{display:inline-block; padding:.12rem .5rem; border-radius:999px;
  font-family:var(--ui); font-size:.74rem; border:1px solid var(--line);
  background:var(--panel-2); color:var(--dim); white-space:nowrap}
.tags{list-style:none; display:flex; gap:.4rem; flex-wrap:wrap; padding:0; margin:.8rem 0 0}
.tags .fam{border-color:var(--fam); color:var(--fam)}
.e-corroborated{color:var(--corroborated); border-color:color-mix(in srgb,var(--corroborated) 45%,transparent)}
.e-partial{color:var(--partial); border-color:color-mix(in srgb,var(--partial) 45%,transparent)}
.e-conflicting{color:var(--conflicting); border-color:color-mix(in srgb,var(--conflicting) 45%,transparent)}
.e-single-source{color:var(--single-source); border-color:color-mix(in srgb,var(--single-source) 45%,transparent)}
.e-unsupported{color:var(--unsupported); border-color:color-mix(in srgb,var(--unsupported) 45%,transparent)}

/* a node the chart does not carry: dashed, so it never reads as chart evidence */
.tags .added{border-style:dashed; border-color:var(--fam); color:var(--fam)}
.tags .added a{color:inherit}
.pill.added-pill{border-style:dashed; margin-left:.3rem}

/* Sources and analysis. Every page that carries both keeps them in separate
   layers, each opened by the same head: a badge, a heading, and a line saying
   what is in it. Sources hang off a solid gold rail; anything an AI model
   wrote from them hangs off a dashed grey-blue one over a faint wash — dashed,
   as an added node is dashed, because neither is a source. A rail rather than
   a box, so the comparison table can still run wider than the prose column. */
.layer{margin:2.6rem 0 0; padding:.2rem 0 .6rem 1.2rem; border-left:3px solid var(--src)}
.layer-ai{border-left:3px dashed var(--ai); background:linear-gradient(90deg,var(--ai-wash),transparent 70%);
  border-radius:0 var(--radius) var(--radius) 0}
.layer-tree{border-left-color:var(--line-strong)}
.layer-head{margin:0 0 1.2rem}
.layer-head h2,.layer-head h1{margin:.35rem 0 .2rem}
.layer-head p{margin:0; color:var(--dim); font-size:.95rem; max-width:62ch}
.layer > h2:first-child{margin-top:.3rem}
.layer h3:first-of-type{margin-top:1.2rem}
.layer-ai h3,.layer-ai h4{color:var(--ai)}
/* a caveat written by the analysis takes the analysis's rail, not the chart's gold */
.layer-ai .caveat{border-left:3px dashed var(--ai); margin-top:1.4rem}
/* a page that IS one layer opens straight into it */
main > .layer:first-child{margin-top:0}
h4{font-family:var(--display); font-weight:400; font-size:1.05rem; margin:1.3rem 0 .3rem}
.badge{display:inline-block; font-family:var(--ui); font-size:.7rem; letter-spacing:.12em;
  text-transform:uppercase; padding:.12rem .55rem; border-radius:999px; border:1px solid}
.badge-src{color:var(--src); border-color:color-mix(in srgb,var(--src) 55%,transparent)}
.badge-ai{color:var(--ai); border-style:dashed; border-color:color-mix(in srgb,var(--ai) 65%,transparent);
  background:var(--ai-wash)}
/* an inline aside of analysis inside a page that is otherwise sources or prose */
.ai-note{margin:.8rem 0; padding:.1rem 0 .1rem 1rem; border-left:3px dashed var(--ai);
  background:linear-gradient(90deg,var(--ai-wash),transparent 70%)}
.ai-note .badge{margin-top:.5rem}
/* who wrote a source's annotation: the chart, or the analysis */
.by{font-family:var(--ui); font-size:.68rem; letter-spacing:.08em; text-transform:uppercase;
  color:var(--faint); border:1px solid var(--line); border-radius:999px; padding:0 .4rem;
  margin-right:.2rem; white-space:nowrap}
.by-ai{color:var(--ai); border-style:dashed; border-color:color-mix(in srgb,var(--ai) 55%,transparent)}
.jump{font-family:var(--ui); font-size:.8rem; color:var(--faint); margin:.9rem 0 0}
.jump .sep{opacity:.5; padding:0 .4rem}
.assess{display:grid; grid-template-columns:max-content 1fr; gap:.45rem 1rem; margin:.6rem 0 0}
.assess dt{font-family:var(--ui); font-size:.78rem; letter-spacing:.06em; text-transform:uppercase;
  color:var(--faint); padding-top:.15rem}
.assess dd{margin:0; color:var(--dim); font-size:.94rem}
.assess .pill{margin-right:.35rem}

/* variant page */
.variant{max-width:68ch}
/* prose stays a readable column, but the comparison table may use the
   whole page width where there is room for it */
@media(min-width:72rem){.variant .scroll{width:min(calc(var(--max) - 2rem),66rem)}}
.variant .crumb{color:var(--faint); font-family:var(--ui); font-size:.8rem; margin:0 0 .8rem}
.crumb .sep{opacity:.5; padding:0 .15rem}
.variant h1{border-left:4px solid var(--fam); padding-left:.7rem; margin-left:-.7rem}
.caveat{border-left:3px solid var(--partial); padding:.2rem 0 .2rem 1rem; background:var(--panel);
  border-radius:0 var(--radius) var(--radius) 0}
.verdict{background:var(--panel); border:1px solid var(--line); border-radius:var(--radius);
  padding:.6rem .8rem; font-size:.92rem}
.kids,.srcs{padding-left:1.2rem}
.kids li,.srcs li{margin:.3rem 0}
.kids li::marker,.srcs li::marker,.notes li::marker{color:var(--accent)}
.notes{padding-left:1.1rem; color:var(--dim); font-size:.92rem}
.notes li{margin:.5rem 0}
.srcs .role{color:var(--faint); font-family:var(--ui); font-size:.74rem}
.srcs .canonical .role{color:var(--accent)}
/* footnotes carry the whole source, so a reader never has to leave the page */
.srcs li{margin:1.1rem 0}
.srcs .cite{margin:0}
.srcs .dim,.srcs .note{font-size:.92rem; margin:.15rem 0; color:var(--dim)}
.srcs .url{font-size:.84rem; word-break:break-all; margin:.15rem 0}
.srcs .cited{font-size:.86rem; color:var(--dim); margin:.15rem 0}
.srcs .when{color:var(--faint); font-family:var(--ui); font-size:.74rem}
.srcs li:target{background:var(--panel); border-radius:var(--radius);
  box-shadow:0 0 0 .6rem var(--panel)}
.back{margin-top:2.5rem}

/* about */
.prose{max-width:68ch}
.limit{margin:1.8rem 0}
/* the chart's own wording, set apart from the reading of it that follows */
.claim{border-left:3px solid var(--partial); padding:.15rem 0 .15rem .9rem;
  color:var(--dim); font-style:italic; margin:.5rem 0 .9rem}
.anchors{font-family:var(--ui); font-size:.78rem; color:var(--faint); margin-top:.8rem}
.anchors .sep{opacity:.5; padding:0 .15rem}

/* findings + references */
.finding{border:1px solid var(--line); border-radius:var(--radius); padding:.6rem .9rem;
  margin:.5rem 0; background:var(--panel)}
.finding summary{cursor:pointer; font-family:var(--display); font-weight:400;
  font-size:1.05rem; color:var(--ink-bright)}
.finding summary::marker{color:var(--accent)}
.finding p{color:var(--dim); font-size:.94rem}
.finding .finding-src{font-family:var(--ui); font-size:.8rem}
/* the findings are all analysis, so they take its rail colour */
.layer-ai .finding summary::marker{color:var(--ai)}
.ref{border-top:1px solid var(--line); padding:1rem 0}
.ref .cite{margin:0 0 .2rem}
.ref .note{color:var(--dim); margin:.2rem 0}
.ref .url{font-size:.82rem; word-break:break-all; margin:.2rem 0}
.ref .cited{font-size:.86rem; color:var(--dim)}
.ref .uncited{color:var(--faint); font-style:italic}
.ref:target{background:var(--panel); border-radius:var(--radius);
  box-shadow:0 0 0 .6rem var(--panel), inset 0 0 0 1px var(--line-strong)}

@media(max-width:40rem){
  body{font-size:15px}
  main{padding:1.5rem 1rem 3rem}
  /* keep the sticky bar to two rows on a phone: the display face is wider
     than the sans it replaced, so the nav is pulled back in to compensate */
  .bar{padding:.55rem .8rem; gap:.4rem}
  .brand{font-size:1.15rem}
  .bar nav{gap:.1rem}
  .bar nav a{font-size:.92rem; padding:.25rem .4rem}
  h2{font-size:1.4rem}
}

/* the bar is sticky and wraps to two or three rows on narrow screens, so a
   targeted footnote has to clear whatever height it currently has */
.srcs li,.ref{scroll-margin-top:9rem}
@media(min-width:22rem){.srcs li,.ref{scroll-margin-top:6.5rem}}
@media(min-width:35rem){.srcs li,.ref{scroll-margin-top:4.5rem}}

/* A bare `*` does not match a pseudo-element, and some of what moves on this
   site is drawn in one — the shimmer over a tile a dora lifted, for instance.
   Without the two extra selectors a reader who asked for no motion still gets
   it. */
@media(prefers-reduced-motion:reduce){
  *,*::before,*::after{animation:none!important; transition:none!important}
}

/* the tools index: one entry per thing on the site that does something */
.tools{list-style:none; padding:0; margin:1.6rem 0 0; display:grid; gap:1rem}
.tools li{background:var(--panel); border:1px solid var(--line); border-radius:var(--radius);
  border-top:3px solid var(--line-strong); padding:1.1rem 1.3rem 1.2rem;
  box-shadow:0 .5rem 1.2rem rgba(0,0,0,.3)}
.tools h2{margin:0; font-size:1.35rem}
.tools .ruleset{margin:.1rem 0 .5rem; font-family:var(--ui); font-size:.78rem;
  letter-spacing:.08em; text-transform:uppercase; color:var(--accent)}
.tools p:last-child{margin:0; color:var(--dim)}

/* ------------------------------------------------------- the dealing board

   The board the Hand Score Simulator deals onto. It used to be a strip under
   every header and is now a tool's page, which is why it has room under it for
   the yaku to be listed one to a line rather than wrapped into a corner.
   hand.js places every tile by transform, so nothing in here lays anything
   out; the board only has to have a size for the script to fit the hand to. */
.deal{position:relative; margin:1.8rem 0 2.2rem; overflow:hidden;
  border:1px solid var(--line-strong); border-radius:var(--radius);
  box-shadow:0 1rem 2.4rem rgba(0,0,0,.4);
  background:
    radial-gradient(90% 150% at 50% 115%,rgba(45,110,88,.55),transparent 70%),
    linear-gradient(180deg,rgba(5,15,11,.94),rgba(9,26,20,.82))}
.deal-in{padding:1.2rem 1.2rem 1rem}
.deal-board{position:relative; height:7.5rem; width:100%}
#deal-hand{position:absolute; inset:0;
  /* whether the hand wraps onto more than one line; hand.js reads this rather
     than repeating the breakpoint in the script. Upright, a phone has width to
     spare down the page and none across it, so the row breaks and the tiles
     come out a size somebody can actually read. On its side it has the width
     and not the height, so it stays a single row. */
  --wrap:0}
@media(orientation:portrait) and (max-width:60rem){ #deal-hand{--wrap:1} }

/* the rule between the hand and the indicators turned over beside it */
.deal-bar{position:absolute; left:0; top:0; border-radius:2px;
  box-shadow:0 0 .4rem rgba(214,173,85,.35);
  background:linear-gradient(180deg,rgba(214,173,85,0),rgba(240,205,125,.95) 18%,
             rgba(240,205,125,.95) 82%,rgba(214,173,85,0))}

/* Each tile is placed by transform alone and carries its own size as a
   font-size, so every measurement inside it — the ivory edge, the shadow it
   casts, the lift of the winning tile — is written in em and scales with the
   tile rather than being re-stated at each breakpoint. */
.tile{position:absolute; left:0; top:0; opacity:0; perspective:34em;
  transition:transform .42s cubic-bezier(.22,.9,.26,1), opacity .28s ease}
.tile.is-in,.tile.is-down{opacity:1}
.tile-flip{position:absolute; inset:0; transform-style:preserve-3d;
  transform:rotateY(180deg); transition:transform .5s cubic-bezier(.3,.8,.3,1)}
.tile.is-in .tile-flip{transform:rotateY(0)}
/* A claimed tile came face-up off somebody's discard, so it never had a back
   to turn over; it is laid straight down the way it was picked up. */
.tile.is-called .tile-flip{transform:none}
.tile.is-called .tile-back{display:none}
/* A riichi tile is two pieces: an amber body, and an ivory plate set into its
   face. The plate is flush with the top and left edges and sits short of the
   other two, so the body is seen only down the right side and along the bottom
   — which is the whole of why a mahjong tile reads as a solid object rather
   than a printed card. Its top-left corner is rounded to exactly the body's
   radius so the two arcs coincide and no amber shows up there.

   The plate carries the ivory itself rather than leaving it to the artwork: the
   drawings are transparent outside their own rounded corners, and without a
   ground under them the body would show through at all four. */
.tile-face,.tile-back{position:absolute; inset:0; backface-visibility:hidden;
  overflow:hidden; background:var(--tile-body); border-radius:.13em;
  box-shadow:inset 0 0 0 .012em rgba(120,78,10,.5),
             0 .06em 0 var(--tile-edge), 0 .15em .15em rgba(0,0,0,.5)}
/* The two sides are pushed a hair apart in depth. Left in the same plane they
   sort against each other, and a corner of the reverse surfaces through the
   face of a tile that has long since finished turning over. */
.tile-face{transform:translateZ(.006em)}
.tile-back{transform:rotateY(180deg) translateZ(.006em)}
.tile-plate{position:absolute; inset:0 .07em .085em 0; background:var(--tile-plate);
  border-radius:.13em .04em .08em .04em}
/* mirrored, because the back is seen through a half-turn */
.tile-back .tile-plate{inset:0 0 .085em .07em; border-radius:.04em .13em .04em .08em}
.tile-plate img{position:absolute; display:block;
  -webkit-user-drag:none; user-select:none}
.tile-ground{inset:0; width:100%; height:100%}
/* Held in from the plate's edges, so a symbol never runs into the body. An
   absolutely positioned image takes its size from width and height, not from
   opposing offsets the way a box would, so both are stated. */
.tile-glyph{left:7%; top:7%; width:86%; height:86%}
/* A flower or a season is a word, not a drawing, and no two tile sets ink the
   eight of them alike — so the site sets them itself on the same plate rather
   than vendoring somebody's artwork. The character is sized in em against the
   tile's own font-size, which the script sets to the tile width, so it scales
   with the board like everything else drawn on a tile. The number in the corner
   is the wind it belongs to: 1 East, 2 South, 3 West, 4 North. */
.tile-word{position:absolute; inset:7%; display:flex; align-items:center;
  justify-content:center; font-family:"Noto Serif CJK SC","Noto Sans CJK SC",
  "Source Han Serif SC","Source Han Sans SC","PingFang SC","Hiragino Sans GB",
  "Microsoft YaHei","WenQuanYi Zen Hei",var(--body); font-size:.7em;
  font-weight:700; line-height:1; color:var(--flower-ink)}
.tile-word.flower{--flower-ink:#1d6b34}
.tile-word.season{--flower-ink:#a8321d}
.tile-word b{position:absolute; top:-.12em; left:-.04em; font-family:var(--ui);
  font-size:.42em; font-weight:700; color:#2a3b92}
/* A khung carries no number, so it takes the whole plate. The two colours are
   what the Vietnamese names mean: khung xanh, the green four, and khung đỏ. */
.tile-word.khung{font-size:.78em}
/* A tile an indicator lifted — a dora, an under-dora or a red five — catches
   the light every few seconds, so that the count in the readout can be seen in
   the hand rather than only read. The sweep is the last quarter of the cycle
   and the rest is the wait; hand.js staggers the start down the row so it
   passes as one wave. The stylesheet's reduced-motion rule stops it dead, and
   the indicators beside the hand still say what the dora are. */
.tile.is-dora .tile-face::after{content:""; position:absolute; z-index:2;
  top:-25%; bottom:-25%; width:52%; left:0; pointer-events:none;
  background:linear-gradient(104deg,rgba(255,246,214,0),rgba(255,246,214,.8) 48%,
             rgba(255,246,214,0));
  transform:translateX(-170%);
  animation:tile-shimmer 3.6s ease-in-out infinite;
  animation-delay:var(--shimmer-delay,0ms)}
@keyframes tile-shimmer{
  0%,74%{transform:translateX(-170%)}
  100%{transform:translateX(330%)}
}

/* the tile the hand was won on, lit from the moment it lands in its own space */
#deal-hand.apart .tile.is-win .tile-face{
  box-shadow:inset 0 0 0 .012em rgba(120,78,10,.5),
             0 .06em 0 var(--tile-edge), 0 .15em .15em rgba(0,0,0,.5),
             0 0 .32em .06em rgba(255,225,150,.95)}

/* The banner lands on top of fourteen pale tiles, so it carries its own ground
   with it: a band of the table's own dark green across the hand, and a keyline
   round the letters for the stretch of tile the band does not cover. */
.deal-stamp{position:absolute; left:0; right:0; top:50%; z-index:3; margin:0;
  text-align:center; pointer-events:none; font-family:var(--display);
  font-size:clamp(1.15rem,3.2vw,1.95rem); letter-spacing:.24em; text-indent:.24em;
  padding:.2em 0; line-height:1.2; color:#ffe9b0;
  background:linear-gradient(90deg,rgba(4,12,9,0),rgba(4,12,9,.86) 16%,
             rgba(4,12,9,.92) 50%,rgba(4,12,9,.86) 84%,rgba(4,12,9,0));
  -webkit-text-stroke:.022em rgba(6,16,11,.9);
  text-shadow:0 0 1.1rem rgba(240,205,125,.85), 0 .12em .5rem rgba(0,0,0,.9);
  opacity:0; transform:translateY(-50%) scale(1.4);
  transition:opacity .3s ease, transform .55s cubic-bezier(.16,1.1,.3,1)}
.deal-stamp.on{opacity:1; transform:translateY(-50%) scale(1)}
/* a yakuman is the one the table stands up for */
.deal-stamp.big{color:#fff6d8; letter-spacing:.3em; text-indent:.3em}

/* The readout is a breakdown, not a caption: what the hand was, which yaku it
   holds, which fu it was counted on, and who pays what. There is room for all
   of it on a page, and the total on its own is the one part a reader cannot
   check for themselves. */
/* what the hand was played under: the inputs to the sum, so they are up
   whether or not the answer is */
.deal-facts{list-style:none; margin:1rem 0 0; padding:.85rem 0 0;
  border-top:1px solid var(--line); display:flex; flex-wrap:wrap; gap:.3rem 2rem;
  font-family:var(--ui); font-size:.95rem; line-height:1.5}
.deal-facts li{display:flex; align-items:baseline; gap:.5rem;
  opacity:0; transform:translateY(.2rem); transition:opacity .28s ease, transform .28s ease}
.deal-facts li.on{opacity:1; transform:none}
.deal-facts span{color:var(--faint); font-size:.82rem; letter-spacing:.06em;
  text-transform:uppercase}
.deal-facts b{font-weight:400; color:var(--ink-bright)}

.deal-controls{display:flex; align-items:center; flex-wrap:wrap; gap:.6rem 1.4rem;
  margin-top:.9rem; padding-top:.85rem; border-top:1px solid var(--line);
  font-family:var(--ui); font-size:.95rem; color:var(--ink)}
.deal-opt{display:flex; align-items:center; gap:.45rem; cursor:pointer}
.deal-opt input{accent-color:var(--accent); width:1.1rem; height:1.1rem; cursor:pointer}
.deal-opt input:disabled,.deal-opt input:disabled + span{cursor:default; opacity:.45}
.deal-clock{font-family:var(--display); font-size:1.45rem; color:var(--accent-lit);
  min-width:3.8rem; font-variant-numeric:tabular-nums}
.deal-again{margin-left:auto; flex:0 0 auto; cursor:pointer; font-size:.92rem; padding:.42rem .9rem}
.deal-reveal{display:block; margin:1.2rem auto .2rem; cursor:pointer}

.deal-read{margin-top:.2rem}
.deal-panels{display:grid; gap:1rem 1.8rem; margin-top:1rem;
  grid-template-columns:repeat(auto-fit,minmax(13rem,1fr))}
.deal-panel h3{margin:0 0 .45rem; font-family:var(--ui); font-size:.82rem; font-weight:700;
  letter-spacing:.12em; text-transform:uppercase; color:var(--accent)}
.deal-panel ul{list-style:none; margin:0; padding:0; font-family:var(--ui); font-size:.98rem;
  line-height:1.6; color:var(--ink)}
.deal-panel li,.deal-split li{display:flex; align-items:baseline; gap:.6rem;
  justify-content:space-between; border-bottom:1px dotted rgba(214,173,85,.22); padding:.18rem 0;
  opacity:0; transform:translateY(.2rem); transition:opacity .28s ease, transform .28s ease}
.deal-panel li.on,.deal-split li.on{opacity:1; transform:none}
.deal-panel li.extra span{color:var(--accent)}
.deal-panel li b,.deal-split li b{font-weight:400; color:var(--ink-bright); white-space:nowrap}
.deal-sum{margin:.5rem 0 0; padding-top:.4rem; border-top:1px solid var(--line-strong);
  font-family:var(--ui); font-size:1.02rem; text-align:right; color:var(--ink-bright)}
.deal-sum:empty{border:0; padding:0}

.deal-result{display:flex; align-items:flex-end; justify-content:space-between;
  gap:1.2rem 2rem; flex-wrap:wrap; font-family:var(--ui);
  margin-top:1.2rem; padding-top:.9rem; border-top:1px solid var(--line)}
#deal-note{margin:0; font-size:1.05rem; letter-spacing:.04em; color:var(--accent-lit);
  min-height:1em}
.deal-tally{display:flex; align-items:baseline; gap:.45rem; margin:0; order:-1; flex:0 0 auto}
#deal-count{font-family:var(--display); font-size:2.3rem; line-height:1;
  color:var(--ink-bright); text-shadow:0 .2rem .7rem rgba(0,0,0,.6)}
#deal-count:empty::before{content:"\00a0"}
.deal-tally .unit{font-size:.7rem; letter-spacing:.14em; color:var(--faint);
  text-transform:uppercase}
.deal-split{list-style:none; margin:0; padding:0; min-width:15rem;
  font-size:.98rem; line-height:1.6; color:var(--ink)}
.deal-nojs{margin:0; padding:2rem 1.2rem; text-align:center; color:var(--dim);
  font-family:var(--ui); font-size:.9rem}

@media(max-width:46rem){
  .deal-in{padding:.9rem .9rem .8rem}
  .deal-board{height:5.6rem}
  .deal-facts{gap:.2rem 1.2rem; font-size:.9rem}
  .deal-controls{gap:.5rem 1rem; font-size:.9rem}
  .deal-again{margin-left:0; width:100%}
  .deal-panels{gap:.9rem}
  #deal-count{font-size:1.9rem}
  .deal-result{gap:.7rem}
  .deal-split{min-width:0; flex:1 1 100%}
}
/* upright, the hand wraps and the script sizes the board to the lines it took */
@media(orientation:portrait) and (max-width:60rem){
  .deal-board{height:auto; min-height:5rem}
}
