@charset "utf-8";
/* 台山族谱网 — Bootstrap 5's palette and feel, written into this site's own tokens.
 *
 * NOT the framework. Bootstrap defines .btn, .card, .progress, .row, .col, .top, .open and
 * .disabled, and so does this stylesheet - with its own meanings, across 29 templates and the
 * places the tests reach into them. Dropping bootstrap.css in beside this would have those eight
 * fighting silently. What is wanted is the LOOK, so the look is what was taken: the palette, the
 * type, the radii, the focus rings, the way a card and a button and an alert are put together.
 *
 * Every value below is Bootstrap 5.3's own. The variable NAMES are unchanged, so the seven hundred
 * lines under here keep working and the diff stays about colour rather than about structure.
 *
 * This block is the BASE, not what most readers see. The site ships a theme switcher (see the
 * theme blocks at the foot of this file) and its default is 族谱红 - the warm cream page (#f7f4ee)
 * and red ink accent (#a3261f) that nod to the printed 族谱, which this palette replaced and which
 * came back as a theme. Bootstrap's blue is still what everything here declares, still offered as
 * 默认蓝, and still what any theme inherits for a token it does not set. */
:root {
  /* $body-color, $secondary-color, $border-color, $gray-400.
     --muted is Bootstrap 5.3's $secondary-color - rgba(33,37,41,.75), flattened - and NOT the
     $gray-600 #6c757d that 5.2's .text-muted used. 5.3 made that change and this file follows it
     for the same reason: measured against the page background, #6c757d is 4.45:1, which is under
     AA by a hair and is a lot of this site's text. See tools/theme_contrast_check.py. */
  --ink:#212529; --ink-2:#495057; --muted:#575a5d; --line:#dee2e6; --line-2:#ced4da;
  /* $body-tertiary-bg behind $white cards; $primary, $primary hover, $primary-bg-subtle */
  --bg:#f8f9fa; --paper:#ffffff; --accent:#0d6efd; --accent-2:#0a58ca; --accent-soft:#cfe2ff;
  /* $success/$danger/$warning with 5.3's -bg-subtle behind them */
  --ok:#0f5132; --ok-soft:#d1e7dd; --warn:#664d03; --warn-soft:#fff3cd; --bad:#842029; --bad-soft:#f8d7da;
  /* the solid forms, for a bar or a rule that is the colour itself rather than text on a tint */
  --ok-solid:#198754; --warn-solid:#ffc107; --bad-solid:#dc3545;
  --focus:rgba(13,110,253,.25); --focus-line:#86b7fe;
  /*
   * THE CHROME, which the first pass at this palette did not cover.
   *
   * Tokenising the named colours - ink, accent, the semantic three - left thirty-two colours
   * written literally in the rules themselves, and they are exactly the ones a THEME trips over.
   * A dark page is what shows it: a white form field on near-black, a card hairline of black on
   * black, and a hamburger drawn entirely in rgba(33,37,41,...) that disappears - which on a phone
   * is the only way into the menu that holds the control for changing the theme back.
   *
   * So the rule is now: no colour in a rule body. If a theme might want it different, it is here.
   */
  /* A form field is NOT --paper. On this theme they are both white and only the border separates
     them, which is fine - but a theme that tints its cards has to be able to keep the field it
     contains distinguishable, and it cannot if the two share one token. */
  --field:#ffffff;
  /* The label on a filled button, and on the numbered circles in the guide. Not always white: a
     theme whose accent is pale needs dark text on it, and a literal #fff is a button nobody reads. */
  --on-accent:#ffffff;
  /* Bootstrap's $code-color is #d63384, and on the $gray-200 chip this file draws code on that is
     3.80:1 - under AA. Darkened until it passes; it is still plainly the same pink. */
  --code-ink:#c02a6e;
  /* The header. --nav-ink is 65% of the ink colour baked flat; a theme sets it outright rather
     than inheriting a translucency that was computed against a light bar. */
  --nav-ink:rgba(33,37,41,.65); --nav-toggle-ink:rgba(33,37,41,.55); --nav-toggle-line:rgba(33,37,41,.15);
  /* Tints laid OVER a surface, so each one has to invert with the surface underneath it. */
  --row-hover:rgba(0,0,0,.04); --paper-veil:rgba(255,255,255,.97); --scrim:rgba(0,0,0,.5);
  /* The borders that finish the tinted fills above. Each -soft has a -line to sit in. */
  --accent-line:#b6d4fe; --ok-line:#badbcc; --warn-line:#ffecb5; --bad-line:#f5c2c7;
  /* The one semantic family the file never had. 「侨居美国」 is drawn in it. */
  --info:#055160; --info-soft:#cff4fc; --info-line:#9eeaf9;
  /* Data, not decoration: the census bar's three shares and the 女 mark on a chart node. They have
     to stay apart from each other and from --accent at an eight-pixel bar height. */
  --data-f:#20c997; --data-u:#adb5bd; --mark-f:#d63384;
  --focus-btn:rgba(49,132,253,.5);
  --shadow-hover:0 .5rem 1rem rgba(0,0,0,.15);
  /* Handed to the color-scheme property below, so the browser paints its own scrollbars, form
     controls and caret to match the theme instead of assuming a white page. */
  --scheme:light;
  --gray-100:#f8f9fa; --gray-200:#e9ecef; --gray-300:#dee2e6;
  /* How far down the sticky page header ends. A NUMBER here is only ever right for one window
     width: header nav wraps, and at the widths where it wraps to two lines anything pinned at a
     fixed 3.4rem slides underneath it. app.js measures the header and overwrites this; the value
     below is the no-JavaScript fallback, and is the common case. */
  --headerh:3.6rem;
  /* $border-radius and $box-shadow-sm. Bootstrap's corners are a third of what this site had. */
  --radius:.375rem; --shadow:0 .125rem .25rem rgba(0,0,0,.075);
  --shadow-lg:0 1rem 3rem rgba(0,0,0,.175);
  /* $font-family-sans-serif, with the CJK faces this site needs kept in front of the Latin
     fallbacks - Bootstrap's own stack has nothing that can set 田稠赵氏族谱. */
  --font:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans SC","PingFang SC",
         "Microsoft YaHei","Noto Sans","Liberation Sans",Arial,sans-serif;
  --serif:"Noto Serif SC","Songti SC","SimSun",serif;
}
/* Tell the browser which way round the page is, so the parts IT paints - scrollbars, the caret,
   a date picker, a <select> drop-down - match. Without this a dark theme gets white scrollbars. */
:root { color-scheme:var(--scheme) }
* { box-sizing:border-box }
/* The browser's own [hidden] rule is a default, and any author rule with an element selector beats
   it - label { display:block } below, for one. So a <label hidden> stayed on screen, which is how
   the raw markup box under the description editor was still visible after the editor replaced it.
   Stated here as intent rather than as a default. */
[hidden] { display:none !important }
html { -webkit-text-size-adjust:100% }
/* $font-size-base 1rem, $line-height-base 1.5 - Bootstrap sets type on the body and lets the
   page inherit it, where this had 15.5px and 1.6 of its own. */
body { margin:0; font-family:var(--font); color:var(--ink); background:var(--bg); line-height:1.5; font-size:1rem }
a { color:var(--accent); text-decoration:none } a:hover { color:var(--accent-2); text-decoration:underline }
/* $headings-font-weight is 500, and Bootstrap tightens no letters. The SIZES below are this
   site's own and stay: its h2 is a label inside a card, and Bootstrap's 2rem h2 - a scale meant
   for landing pages - would set every settings group in display type. */
h1,h2,h3 { font-weight:500; letter-spacing:normal; line-height:1.2 }
h1 { font-size:1.9rem; margin:.2rem 0 .6rem } h1 small { font-size:.85rem; font-weight:400; color:var(--muted); margin-left:.6rem }
h2 { font-size:1.15rem; margin:1.6rem 0 .6rem; color:var(--ink-2) } h3 { font-size:1.05rem; margin:.2rem 0 }
code { font:.875em ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; background:var(--gray-200);
  color:var(--code-ink); padding:.1em .4em; border-radius:var(--radius) }
.muted { color:var(--muted) }

/* header */
/* max-width so the bar can never grow past the window. Where a page's own content is wider than
   the viewport the header was stretching with it, and justify-content:space-between then put the
   menu button at the far edge of that wider bar - off the side of a phone, unreachable. */
header.top { position:sticky; top:0; z-index:20; display:flex; justify-content:space-between; align-items:center; gap:1rem;
  max-width:100vw; box-sizing:border-box;
  /* $navbar-padding-y .5rem on a solid bar. The translucent blur went with the cream: Bootstrap's
     navbar is an opaque surface with a hairline under it, and a blur costs a re-composite of
     whatever scrolls beneath it for an effect that is no longer visible. */
  padding:.5rem 1rem; background:var(--paper); border-bottom:1px solid var(--line) }
/* $navbar-brand-font-size 1.25rem. The serif stays: it is the printed book's own face and the
   one piece of this page that is about the 族谱 rather than about the software. */
.brand { font-family:var(--serif); font-weight:600; font-size:1.25rem; color:var(--accent); letter-spacing:.04em; white-space:nowrap }
/* the name in 楷书 (app/views/_logo.*.svg): as tall as two lines of the old wordmark's type, its width
   the drawing's own; it takes .brand's colour through currentColor */
.brand { display:inline-flex; align-items:center }
.brand svg.logo { display:block; height:1.9rem; width:auto }
.brand:hover { text-decoration:none }
header nav { display:flex; align-items:center; gap:.2rem; flex-wrap:wrap }
header nav a, header nav button.link { padding:.5rem .75rem; border-radius:var(--radius);
  color:var(--nav-ink); background:none; border:0; font:inherit; cursor:pointer }
header nav a:hover, header nav button.link:hover { background:var(--accent-soft); color:var(--accent); text-decoration:none }
/* The theme picker is written into the SAME rule as the languages rather than a second one like
   it: tools/css_lint.py flags a selector that overrules itself, and two rules of the same shape a
   few lines apart is the exact bug it was written to catch. */
.lang, .theme { margin-left:.6rem; padding-left:.8rem; border-left:1px solid var(--line-2); font-size:.86rem }
.theme { display:inline-flex; align-items:center; gap:.3rem }
/* An ordinary field would be as wide as the page here; it is a nav control, so it is sized to its
   own text and set at the header's scale. */
.theme select { width:auto; padding:.15rem 1.4rem .15rem .4rem; font-size:.86rem; margin:0;
  border-radius:.25rem; background:var(--field); color:var(--nav-ink); border-color:var(--line-2) }
.theme button { margin:0 }
/* Reachable by a screen reader, invisible to everyone else - the <select> carries an aria-label
   for the eye-free case and this labels it properly for the rest. */
.vh { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip-path:inset(50%); white-space:nowrap; border:0 }
.lang a { padding:.25rem .5rem; color:var(--muted) } .lang a.on { color:var(--accent); font-weight:600 }

/* The menu, collapsed whenever its row will not fit.
   It was a breakpoint - max-width:860px - and a breakpoint is one number standing in for a
   question about content: how wide the row needs depends on the language, on how long the
   signed-in name is, and on whether this account sees 管理 as well. Between 860px and wherever
   the row actually ran out, the nav wrapped onto a second line, the header grew, and the chart's
   search box - pinned under a header assumed to be one line tall - slid underneath it.
   So layout.php measures instead: if the nav is taller than one of its own rows, .nav-compact
   goes on and the whole thing becomes the ☰. Scoped to .js-nav as well, because without the
   script there is no button to open the panel with: the old wrapping row comes back and
   everything stays reachable, which is worse-looking and still usable. */
/* $navbar-toggler: a bordered square that is all border and no fill. */
.navtoggle { display:none; font-size:1.25rem; line-height:1; padding:.25rem .75rem; border-radius:var(--radius);
  border:1px solid var(--nav-toggle-line); background:transparent; color:var(--nav-toggle-ink); cursor:pointer }
.navtoggle:hover { border-color:var(--accent); color:var(--accent) }
/* The bar stops spreading its two ends apart and simply starts from the left. With space-between
   the button was pinned to the header's right edge, and on a narrow screen that edge sat outside
   the window - measured: at a 500 px viewport the button lands at x 425, and at 390 it is not
   reachable at all. Beside the wordmark it is always on screen and still the first thing a thumb
   finds. */
.js-nav.nav-compact header.top { justify-content:flex-start }
.js-nav.nav-compact .navtoggle { display:block }
.js-nav.nav-compact header nav { display:none }
.js-nav.nav-compact header nav.open { display:flex }
/* A panel under the header rather than a squeeze inside it: the bar keeps its one-line height
   whatever the menu is doing, and the menu gets the full width to list itself down. */
.js-nav.nav-compact header nav { position:absolute; top:100%; right:0; left:0; flex-direction:column;
  align-items:stretch; gap:.1rem; padding:.5rem .9rem .8rem; background:var(--paper);
  border-bottom:1px solid var(--line); box-shadow:var(--shadow) }
.js-nav.nav-compact header nav a, .js-nav.nav-compact header nav button.link { padding:.6rem .7rem; text-align:left; width:100% }
.js-nav.nav-compact header nav .inline { display:block }
/* the languages read as a row of their own, not as three more menu entries */
.js-nav.nav-compact .lang, .js-nav.nav-compact .theme { display:flex; margin:.35rem 0 0; padding:.55rem 0 0; border-left:0;
  border-top:1px solid var(--line); font-size:.95rem }
.js-nav.nav-compact .lang a { flex:1 1 0; text-align:center; padding:.45rem .3rem }
/* In the ☰ panel the picker gets the width of the menu, like everything else in there. */
.js-nav.nav-compact .theme select { flex:1 1 auto; font-size:.95rem; padding:.45rem .5rem }

main { max-width:1120px; margin:0 auto; padding:1.6rem 1.4rem 3rem }
footer { text-align:center; color:var(--muted); font-size:.85rem; padding:2.5rem 1rem 3rem }

/* surfaces */
/* $card: a white surface, a hairline of rgba(0,0,0,.175), and $card-spacer 1rem inside. */
.box, .card { background:var(--paper); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow) }
.box { padding:1rem 1rem; margin:.75rem 0 } .box.narrow { max-width:520px } .box h2 { margin-top:0 }
.card { display:block; padding:1rem; color:inherit; transition:box-shadow .15s ease-in-out }
/* A card lifting off the page under the pointer is not Bootstrap's idiom; the shadow deepening
   to $box-shadow is. */
.card:hover { box-shadow:var(--shadow-hover); text-decoration:none }
.card h3 { color:var(--accent); font-family:var(--serif); font-size:1.25rem } .card p { margin:.25rem 0 }
.cards { display:grid; grid-template-columns:repeat(auto-fill,minmax(270px,1fr)); gap:1rem }
.row { display:grid; grid-template-columns:1fr 1fr; gap:1rem } @media (max-width:800px) { .row { grid-template-columns:1fr } }
.hero { padding:1.4rem 0 .6rem } .hero h1 { font-family:var(--serif); font-size:2.4rem; letter-spacing:.04em } .hero p { font-size:1.05rem }
/* ...across the page's whole width, like the sections under it (the user asked, 2026-10-02) */
/* the summary of 关于 under the site's name: its link never breaks, or 「›」 is left on a line alone */
.hero p.hero-about a { white-space:nowrap }
.crumbs { color:var(--muted); font-size:.9rem; margin:0 0 .4rem } .crumbs a { color:var(--muted) }

/* pills, flashes */
/* $badge: .35em .65em, 75% of the line it sits in, bold, and square-cornered rather than a pill.
   The subtle fills are 5.3's -bg-subtle / -text-emphasis pairs. */
.tag { display:inline-block; font-size:.75em; font-weight:700; line-height:1; padding:.35em .65em;
  border-radius:var(--radius); background:var(--gray-200); color:var(--ink-2); vertical-align:middle }
.tag.st-pending { background:var(--warn-soft); color:var(--warn) } .tag.st-applied,.tag.st-approved { background:var(--ok-soft); color:var(--ok) } .tag.st-rejected { background:var(--bad-soft); color:var(--bad) }
/* The result of an action, floated in the middle of the window so it is seen wherever the
   reader happens to be scrolled to - and where the eye already is, rather than at an edge. */
.flash-tray { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); z-index:200;
  width:max-content; max-width:min(92vw, 40rem); display:flex; flex-direction:column; gap:.5rem }
.flash-tray .flash { margin:0; box-shadow:var(--shadow-lg); display:flex; gap:.75rem;
  align-items:flex-start; animation:flash-in .18s ease-out }
.flash-tray .flash.out { opacity:0; transform:scale(.97); transition:opacity .2s, transform .2s }
.flash-x { background:none; border:0; color:inherit; font-size:1.15rem; line-height:1; padding:0 0 0 .25rem;
  cursor:pointer; opacity:.65 }
.flash-x:hover { opacity:1 }
@keyframes flash-in { from { opacity:0; transform:scale(.96) } to { opacity:1; transform:none } }
@media (prefers-reduced-motion:reduce) { .flash-tray .flash { animation:none } }
/* $alert: $alert-padding 1rem, a tinted fill, a border of the same hue and text in the emphasis
   colour. Bootstrap's own alert-success / -danger / -warning triples. */
.flash { padding:1rem; border-radius:var(--radius); margin-bottom:1rem; border:1px solid transparent }
.flash.ok { background:var(--ok-soft); color:var(--ok); border-color:var(--ok-line) } .flash.err { background:var(--bad-soft); color:var(--bad); border-color:var(--bad-line) }
.flash.warn { background:var(--warn-soft); color:var(--warn); border-color:var(--warn-line) }
.redraw-of { display:flex; gap:.5rem; align-items:baseline; flex-wrap:wrap }

/* forms */
/* $input-padding .375rem .75rem, $input-border-color $gray-400, and the focus ring that is the
   most recognisable thing about a Bootstrap form: the border lightens to $input-focus-border-color
   and a quarter-rem of 25%-opacity primary sits outside it. */
input, select, textarea { font:inherit; padding:.375rem .75rem; border:1px solid var(--line-2); border-radius:var(--radius); width:100%; max-width:100%; background:var(--field); color:var(--ink); line-height:1.5 }
input:focus, select:focus, textarea:focus { outline:none; border-color:var(--focus-line); box-shadow:0 0 0 .25rem var(--focus) }
input[type=search] { padding-left:.9rem }
/* $form-label-margin-bottom .5rem, and Bootstrap's labels are the body weight, not a heavier one. */
label { display:block; margin:.75rem 0; font-weight:400 } label input, label select, label textarea { margin-top:.5rem } label small { display:block; font-weight:400; margin-top:.25rem; color:var(--muted) }
label input[type=radio], label input[type=checkbox] { width:auto; margin-right:.45rem; accent-color:var(--accent) }
/* .btn / .btn-primary: $btn-padding .375rem .75rem at the body size, a 1px border of its own
   colour, and $border-radius. Bootstrap's weight here is 400, not 500. */
button, .btn { display:inline-flex; align-items:center; gap:.35rem; font:inherit; font-weight:400; line-height:1.5;
  padding:.375rem .75rem; border:1px solid var(--accent); background:var(--accent); color:var(--on-accent);
  border-radius:var(--radius); cursor:pointer; text-decoration:none; margin:.25rem .25rem .25rem 0;
  transition:color .15s ease-in-out, background-color .15s ease-in-out, border-color .15s ease-in-out, box-shadow .15s ease-in-out }
button:hover, .btn:hover { background:var(--accent-2); border-color:var(--accent-2); color:var(--on-accent); text-decoration:none }
button:focus-visible, .btn:focus-visible { outline:none; box-shadow:0 0 0 .25rem var(--focus-btn) }
/* .btn-outline-primary: the colour on the outside until it is pointed at, then filled with it. */
button.plain, .btn.plain { background:transparent; color:var(--accent); border-color:var(--accent) }
button.plain:hover, .btn.plain:hover { background:var(--accent); color:var(--on-accent) }
/* .btn-outline-danger, the same way round. */
button.danger { background:transparent; color:var(--bad-solid); border-color:var(--bad-solid) }
button.danger:hover { background:var(--bad-solid); color:var(--on-accent); border-color:var(--bad-solid) }
/* $btn-padding-sm .25rem .5rem at $font-size-sm, with $border-radius-sm. */
button.sm, .btn.sm { padding:.25rem .5rem; font-size:.875rem; border-radius:.25rem }
form.inline, .inline { display:inline } .inline input { width:auto } .inline-form { display:flex; gap:.6rem; align-items:center; flex-wrap:wrap }
.checks { display:grid; grid-template-columns:repeat(auto-fill,minmax(230px,1fr)); gap:.1rem .8rem; margin:.5rem 0 } .checks label { margin:.15rem 0; font-weight:400 }
.searchbar { display:flex; gap:.5rem } .searchbar input { flex:1 } .searchbar button { margin:0 }

/* tables */
/* $table-cell-padding .5rem, $table-border-color $border-color, and $table-hover-bg, which in
   Bootstrap is a black tint of the surface rather than a colour of its own. */
table { border-collapse:collapse; width:100%; margin:.5rem 0; font-size:1rem } td, th { padding:.5rem; border-bottom:1px solid var(--line); text-align:left; vertical-align:top }
th { color:var(--muted); font-weight:500; font-size:.875rem } tr:hover td { background:var(--row-hover) }
table.form td:first-child { width:8em; color:var(--muted) } table.diff td:first-child { width:9em }
/* 提出修改: 字段 / 原值 / 新值, and the third is the only one anybody types into.
   Measured at 390px before this rule: 122px of label, 101px of old value, 81px left for the box -
   62px of it usable. The narrowest column on the page was the one being used.
   The old value can wrap rather than hold a column open: the 父 and 母 rows carry a stable id,
   23 unbreakable characters of it. */
table.form td:nth-child(2) { overflow-wrap:anywhere }
@media (max-width:640px) {
  /* Below this the three-column arrangement cannot be made to work - the label and the old value
     have to be SOMEWHERE - so the row stops being a row: 字段 and 原值 share one line and 新值 gets
     the width of the page underneath them. The header row goes with it, being three headings for
     columns that no longer exist; nothing is lost, because each line still reads label, old value,
     then the box. */
  table.form tr:first-child { display:none }
  table.form tr { display:grid; grid-template-columns:auto 1fr; gap:.1rem .5rem;
                  padding:.45rem 0; border-bottom:1px solid var(--line) }
  table.form td { display:block; border:0; padding:0 }
  table.form td:first-child { width:auto; font-size:.9rem }
  table.form td:nth-child(2) { color:var(--muted); font-size:.85rem }
  table.form td:last-child { grid-column:1 / -1 }
}
/* 承继关系: each word on its own line with the rule it stands for under it. The site-wide label
   margin is a form's, and three of them stacked in one table cell read as three questions. */
td.succ-choice label { margin:.2rem 0 0 }
td.succ-choice label input { margin-top:0 }
td.succ-choice small { display:block; margin:.2rem 0 0 }
td.succ-choice small.rule { margin:0 0 .35rem 1.6rem }

/* 原书扫描: every sheet as a button, in whichever numbering the reader chose */
.scanpages { margin:.4rem 0 .6rem }
.scanpages-head { display:flex; align-items:center; gap:.6rem; flex-wrap:wrap; margin-bottom:.25rem }
.numtoggle { display:inline-flex; gap:.25rem } .numtoggle[hidden] { display:none }
.numtoggle button[aria-pressed="true"] { background:var(--accent); color:#fff; border-color:var(--accent) }
.scanstrip { position:relative; display:flex; gap:.25rem; overflow-x:auto; padding:.2rem 0 .45rem;
             overscroll-behavior-x:contain; scrollbar-width:thin }
.scanstrip .pg { flex:0 0 auto; min-width:2.3em; padding:.2rem .4rem; text-align:center;
                 border:1px solid var(--line); border-radius:var(--radius); text-decoration:none;
                 font-variant-numeric:tabular-nums; font-size:.9rem; line-height:1.4 }
.scanstrip a.pg:hover { border-color:var(--accent) }
.scanstrip .pg.cur { background:var(--accent); color:#fff; border-color:var(--accent); font-weight:600; cursor:default }
.scanpages[data-num="pdf"] .nb, .scanpages[data-num="book"] .np { display:none }
.scanstrip .nb.none { color:var(--muted) }
.scanstrip .pg.cur .nb.none { color:inherit }

/* person page */
.person-head { display:flex; align-items:baseline; gap:1rem; flex-wrap:wrap }
.facts { display:flex; flex-wrap:wrap; gap:.4rem .9rem; color:var(--ink-2); margin:.2rem 0 .8rem } .facts b { color:var(--ink) }
ol.ancestry { padding-left:0; list-style:none; display:flex; flex-wrap:wrap; gap:.3rem; align-items:center }
ol.ancestry li { display:flex; align-items:center; gap:.3rem } ol.ancestry li + li::before { content:"›"; color:var(--line-2); margin-right:.2rem }
ol.ancestry li b { color:var(--accent) }
ul.kids { list-style:none; padding:0; margin:0; display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:.35rem }
ul.kids li { padding:.45rem .7rem; border:1px solid var(--line); border-radius:var(--radius); background:var(--paper) }
ul.kids li a { font-weight:500 }
/* A family reads DOWN. The grid above fits as many columns as the screen allows and fills across
   before it fills down, so the second son sat to the right of the first and the column under 长子
   read first, fourth, seventh - which hid the one thing the list is for. Children are ordered;
   spouses are not, and keep the grid. */
/* An icon set in a line of text: 1em square, dropped a touch so its middle sits on the text's
   middle rather than its baseline. Sized off the font so it follows the button it is in. */
.ico { width:1em; height:1em; vertical-align:-.13em }
/* The 世系长图 options. Set below the button rather than beside it - the form sits in a row of
   buttons, and a panel opening inside that row pushes the others about as it opens and closes. */
.imgopts { flex-basis:100%; margin-top:.5rem }
.imgopts summary { cursor:pointer; color:var(--muted); font-size:.9em }
.imgopts label { display:inline-block; margin:.5rem .9rem .2rem 0; font-size:.9em }
.imgopts select { margin-left:.3rem }
.imgopts p { margin:.2rem 0 0 }
ul.kids.col { grid-template-columns:1fr }
ul.kids li .rank { color:var(--muted); font-size:.85em; margin-right:.35rem }
/* Wide enough for 「第10女」 so the names below each other start in the same place - a ragged left
   edge on a numbered list makes it harder to read the order, which is the point of the list. */
ol.kids.ranked li .rank { display:inline-block; min-width:3.6em }
ol.kids.ranked { counter-reset:none }
ol.kids.ranked li { display:flex; align-items:center; gap:.3rem }
ol.kids.ranked li .nm { font-weight:500 }
ol.kids.ranked li .up, ol.kids.ranked li .down { padding:.1rem .4rem; line-height:1 }
/* Nothing to move a name past: the top one cannot go up and the bottom one cannot go down. Dimmed
   rather than removed, so the row keeps its shape as a name travels through it. */
ol.kids.ranked li button[disabled] { opacity:.25; cursor:default }
details.reorder { margin-top:.8rem; padding-top:.7rem; border-top:1px solid var(--line) }
details.reorder summary { cursor:pointer; font-weight:500 }
details.reorder ol.kids { margin:.6rem 0 }
.actions { margin-top:1.6rem; padding-top:1rem; border-top:1px solid var(--line); display:flex; flex-wrap:wrap; gap:.5rem; align-items:center }
/* 提出修改 and 删除 are not two buttons in a row. The second carries a 理由 box of its own, and set
   beside the first that box reads as belonging to it - somebody types their evidence for a
   correction into the field that will delete the person. Its own line, with a little air above it,
   so the box is plainly the delete's. */
.actions .inline-form { flex-basis:100%; margin-top:.7rem; padding-top:.7rem; border-top:1px solid var(--line) }
ul.roots { columns:2; column-gap:2rem; padding-left:1.2rem } @media (max-width:800px) { ul.roots { columns:1 } } ul.roots li { break-inside:avoid; margin:.2rem 0 }
/* 「1世10传必」 needs no spaces between its parts; "Gen. 1Trans. 10必" very much does. Which of
   those it is depends on the language of the page, not on the markup, so the gap is set by :lang
   rather than by putting a space in the template that Chinese would then have to carry. */
.gens span { display:inline-flex; align-items:baseline; gap:0; margin:.15rem .5rem .15rem 0; font-size:.9rem; padding:.1rem .5rem; background:var(--paper); border:1px solid var(--line); border-radius:var(--radius) } .gens b { color:var(--accent) }
.gens span:lang(en) { gap:.4em }

/* the tree's own account of itself: prose, so a comfortable measure and a little more leading */
.richedit { border:1px solid var(--line-2); border-radius:var(--radius); background:var(--field); overflow:hidden }
.richbar { display:flex; gap:.2rem; padding:.3rem; border-bottom:1px solid var(--line); background:var(--paper) }
.richbar button { width:auto; padding:.3rem .6rem; min-width:2.2rem; border:1px solid transparent;
  border-radius:.25rem; background:none; color:var(--ink-2); font:inherit; font-size:.9rem; cursor:pointer }
.richbar button:hover { background:var(--accent-soft); color:var(--accent) }
.richbar button[aria-pressed="true"] { background:var(--accent-soft); color:var(--accent);
  border-color:var(--line-2) }
.richbox { min-height:8rem; max-height:22rem; overflow-y:auto; padding:.6rem .8rem; line-height:1.7 }
/* The focus ring was drawn in --accent-soft - the pale wash used to TINT a selected row - which
   against the field it rings is about 1.3:1 and is not a ring at all. It is the focus border
   colour, like every other focused control here. */
.richbox:focus { outline:2px solid var(--focus-line); outline-offset:-2px }
.richbox p { margin:0 0 .6rem } .richbox ul, .richbox ol { margin:0 0 .6rem; padding-left:1.4rem }
.richbox > :last-child { margin-bottom:0 }
.richsrc textarea { font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size:.85rem }
/* A provenance note naming a source URL is one unbreakable seventy-character word, and at phone
   width it used to push the whole page sideways. Applies to the editor too: what the writer sees
   in the box should be what the reader gets. */
.zupu-about, .richbox { overflow-wrap:anywhere }

/* ---- tables in a 族谱简介 -------------------------------------------------------------------
   A table is drawn rather than written, and the drawing is ENTIRELY here: Html.php keeps no
   attribute on any part of a table - no border, no width, no colspan - so the stylesheet is the
   only thing that decides how one looks. That is the point rather than a limitation. The editor
   and the published page load the same rules, so what an admin arranges is what a reader gets,
   and nothing an author pastes can change the geometry of the page.

   border-collapse, so two neighbouring cells share ONE line instead of setting two side by side.
   var(--ink) rather than a literal black: it IS black on the light themes (#212529) and stays
   legible on the seven dark ones, where #000 would be a line nobody could see. */
.zupu-body table, .richbox table { border-collapse:collapse; margin:.6rem 0; max-width:100% }
.zupu-body th, .zupu-body td,
.richbox th, .richbox td { border:1px solid var(--ink); padding:.45em .7em; vertical-align:top;
  /* An empty cell is still something to aim at - with a caret on a desk and with a finger on a
     phone. Without a floor it collapses to the width of its borders and cannot be tapped into. */
  min-width:2.5em }
.zupu-body th, .richbox th { background:var(--gray-200); font-weight:700; text-align:start }
.zupu-body table p, .richbox table p { margin:0 }
/* Only the READER's copy scrolls. A wide table would otherwise push a phone's whole page sideways,
   which is the one layout fault that makes everything else unreadable too. The editor keeps a real
   table box: display:block reparents the rows into an anonymous table, and inside a
   contenteditable that moves the caret about in ways an author would have to fight. */
.zupu-body table { display:block; overflow-x:auto; width:max-content }

/* ---- the row and column menu ----------------------------------------------------------------
   position:fixed because it is placed from clientX/clientY, which are viewport coordinates; in an
   absolutely positioned menu the numbers would be out by however far the page is scrolled. */
.tblmenu { position:fixed; z-index:60; min-width:11rem; display:flex; flex-direction:column;
  padding:.25rem; background:var(--paper); border:1px solid var(--line-2);
  border-radius:var(--radius); box-shadow:var(--shadow-lg) }
.tblmenu button { width:100%; text-align:start; font:inherit; color:var(--ink); background:none;
  border:0; border-radius:calc(var(--radius) - .1rem); padding:.55rem .8rem; cursor:pointer;
  /* 44px at the default root size: a finger, not a pointer. This menu is the ONLY way to reach
     these commands on a touch screen, so the items have to be the size of a touch target. */
  min-height:2.75rem }
.tblmenu button:hover, .tblmenu button:focus { background:var(--accent-soft); color:var(--accent);
  outline:none }
/* The two that take something away, set apart from the four that add - so a finger aiming at
   「在下方插入行」 does not land on 「删除本行」 because they looked like one list. */
.tblmenu button.cut { margin-top:.25rem; padding-top:.7rem; border-top:1px solid var(--line) }
.richbar button[disabled] { opacity:.4; cursor:default }
.richbar button[disabled]:hover { background:none; color:inherit }

/* ---- how big a table? ---------------------------------------------------------------------
   Borrowed from dialog.findbox rather than invented: the site already has one modal and two that
   look different would read as two different kinds of thing. Narrower, because it asks for two
   numbers rather than showing a list of results. */
.tbldlg { width:min(20rem,92vw); border:1px solid var(--line); border-radius:var(--radius);
  background:var(--paper); color:var(--ink); padding:1rem 1.1rem 1.1rem; box-shadow:var(--shadow-lg) }
.tbldlg::backdrop { background:var(--scrim) }
.tbldlg-h { display:flex; align-items:baseline; gap:.5rem; margin-bottom:.5rem }
.tbldlg-h .findbox-x { margin:0 0 0 auto }
.tbldlg label { margin:.5rem 0 }
/* A number field with its own spinner is fiddly with a fingertip, so it is given room: this is
   the same 2.75rem the menu items use, and for the same reason. */
.tbldlg input { min-height:2.75rem }
.tbldlg .inline { display:flex; gap:.6rem; margin:.9rem 0 0 }
.tbldlg .inline button { min-height:2.75rem }
.zupu-about { max-width:46rem }
/* Five lines, then a choice. Clamped by height rather than by -webkit-line-clamp: the description
   is paragraphs and lists, not one block of text, and line-clamp does not reach across children.
   The class is added by app.js only when the content actually overflows, so a short description
   never grows a button and one with JavaScript off is never truncated. */
.zupu-about .zupu-body.clamped { max-height:9.4em; overflow:hidden; position:relative }
.zupu-about .zupu-body.clamped::after { content:""; position:absolute; left:0; right:0; bottom:0;
  height:2.6em; background:linear-gradient(to bottom, transparent, var(--paper)) }
.zupu-about .zupu-more { margin:.5rem 0 0; padding:.2rem 0; border:0; background:none; font:inherit;
  font-size:.9rem; color:var(--accent); cursor:pointer }
.zupu-about .zupu-more:hover { text-decoration:underline }
.zupu-about a { color:var(--accent) }
.zupu-about p { margin:.45rem 0; line-height:1.8 }
.zupu-about p:first-child { margin-top:0 }
.zupu-about > :last-child { margin-bottom:0 }
.zupu-about ul, .zupu-about ol { margin:.45rem 0 .7rem; padding-left:1.5rem; line-height:1.8 }
.zupu-about li { margin:.15rem 0 }
details { margin:1rem 0 } summary { cursor:pointer; color:var(--accent); font-weight:500 }
ul.notes li { margin:.3rem 0; font-size:.92rem }

/* admin quick-fix */
.fixbox { background:var(--accent-soft); border:1px solid var(--accent-line) }
.fixbox h3 { margin-top:0; color:var(--accent) }
/* The row's padding, named, because the rule that makes a row clickable has to cancel exactly
   this much and would otherwise be a pair of magic numbers waiting to drift from it. */
.cands { --cy:.45rem; --cx:.7rem }
.cands { list-style:none; padding:0; margin:.5rem 0; max-height:300px; overflow:auto; border:1px solid var(--line); border-radius:var(--radius); background:var(--paper) }
.cands li { position:relative; display:flex; justify-content:space-between; align-items:center; gap:.6rem; padding:var(--cy) var(--cx); border-bottom:1px solid var(--line) }

/*
 * A result row that IS a link should be the link, from edge to edge.
 *
 * These lists put the row's padding on the <li> and the link inside it, so the link ended where
 * the text did: the padding, and the empty space to the right of a short name, did nothing - the
 * same complaint as the results page, that you had to aim at the characters.
 *
 * :only-child rather than a class, because it picks out exactly the right rows without the three
 * renderers in app.js having to agree about one. A row whose only content is a link is a row that
 * navigates - the chart's 查名 box, the 查名 dialog - and should be clickable all over. A row with
 * anything else in it DOES something: the parent picker's 设为父, 最近查看's ×. Neither of those
 * should become easier to hit by accident.
 *
 * An overlay on the link rather than a negative margin on it. The margin was tried first and was
 * wrong in a way only a browser shows: it pushes the link out past the list's content box, and
 * .cands is overflow:auto, so the part hanging over the padding was clipped and unclickable -
 * measured as a dead strip at each edge of every row. inset:0 is bounded by the row itself and
 * cannot escape it. Hovering the overlay counts as hovering the link, so :hover still works; the
 * tint goes on the row, which is the thing lighting up.
 */
.cands li > a:only-child { color:inherit; text-decoration:none }
.cands li > a:only-child::after { content:''; position:absolute; inset:0 }
.cands li:hover { background:var(--accent-soft) }
.cands li > a:only-child:focus-visible { outline:2px solid var(--accent); outline-offset:2px }
.relform { display:flex; flex-wrap:wrap; gap:.8rem; align-items:flex-end; margin:0 0 1rem }
.personpick { position:relative; display:flex; flex-direction:column; gap:.25rem; min-width:16rem }
.personpick label { font-size:.8rem; color:var(--muted) }
.personpick .cands { position:absolute; top:100%; left:0; right:0; z-index:20; margin:.25rem 0 0; box-shadow:var(--shadow-lg) }
.personpick .cands li { padding:0 }
.personpick .pp-opt { display:block; width:100%; text-align:left; padding:.45rem .7rem; border:0; background:none; cursor:pointer }
.personpick .pp-opt:hover { background:var(--accent-soft) }
/* 族谱信息's village, picked from 村落 (tree_create.php): the person picker's list, under the box */
/* in the page's flow rather than over it, so nothing under the box is hidden while it is open */
.placepick > label { margin-bottom:.25rem }
.placepick .cands { margin:.25rem 0 .5rem; max-height:16rem; box-shadow:var(--shadow) }
.placepick .cands li { padding:0 }
.placepick .pp-opt { display:block; width:100%; text-align:left; padding:.45rem .7rem; border:0; background:none; cursor:pointer }
.placepick .pp-opt:hover, .placepick .pp-opt:focus-visible { background:var(--accent-soft) }
.placepick > small { display:block; color:var(--muted) }
.placepick .picked { margin:.25rem 0 .6rem; color:var(--ok) }
/* a village with no genealogy yet: 创建本村族谱 beside the words (village.php) */
.zupunone { display:flex; flex-wrap:wrap; align-items:center; gap:.5rem 1rem; margin:0 }
#zupu ul + .zupunone { margin-top:.75rem }   /* under the list, whose last row draws the line above it */
.relcols { display:grid; grid-template-columns:repeat(auto-fit,minmax(15rem,1fr)); gap:1.2rem }
.sugg-h { margin:.9rem 0 .3rem; font-size:.95rem }
.cands.sugg { background:var(--gray-100) }
.cands li:last-child { border-bottom:0 } .cands .lbl b { color:var(--ink) }
.cands .lbl a { color:inherit; text-decoration:underline dotted; text-underline-offset:2px }
.cands .lbl a:hover { color:var(--accent); text-decoration-style:solid } .cands .lbl span { color:var(--muted); font-size:.9rem; margin-left:.4rem }

/* explorable tree */
/* 世系图. The connectors are drawn the way the `tree` command draws them - a vertical down from
   each parent, a tee into every child, an elbow into the last - because the alternative, one
   dashed rule down the whole of each level, ran on past the last child and attached to nothing.
   Sixteen generations deep that was a wall of parallel lines with no way to tell which belonged
   to which name.
   --tv-mid is the drop from the top of a row to the middle of its node box, which is where the
   tee has to meet it: the row's own padding plus half the box. --tv-in is the indent per
   generation, and the vertical sits --tv-arm to the left of the child, so the two numbers
   together decide how far the line stands out from the parent above it. */
.treeview { font-size:.95rem; --tv-mid:1.2rem; --tv-in:1.6rem; --tv-arm:1rem }
.treeview ul { list-style:none; margin:0; padding-left:var(--tv-in) }
.treeview > ul { padding-left:0 }
/* padding, not margin: the rows have to touch, or the vertical breaks between every sibling */
.treeview li { margin:0; padding:.15rem 0; position:relative }
.treeview ul li::before, .treeview ul li::after { content:""; position:absolute; background:var(--line-2) }
/* the vertical runs the whole height of the row INCLUDING its descendants, so it carries on down
   the side of a subtree while later brothers are still to come */
.treeview ul li::before { left:calc(var(--tv-arm) * -1); top:0; bottom:0; width:1px }
/* ...and stops at the elbow on the last child, which is what closes a family off */
.treeview ul li:last-child::before { bottom:auto; height:var(--tv-mid) }
.treeview ul li::after { left:calc(var(--tv-arm) * -1); top:var(--tv-mid); width:var(--tv-arm); height:1px }
/* the founder has nothing above him to hang from */
.treeview > ul > li::before, .treeview > ul > li::after { display:none }
/* Everything below 本人 is drawn darker. The chart usually opens with several generations of
   ancestors above the person asked for, and in one flat grey their line and everybody else's
   looked alike; this makes his own descent read as the subject of the page. */
.treeview li.focus > ul li::before, .treeview li.focus > ul li::after { background:var(--ink-2) }

/* Twenty generations at the full indent is 300 px of empty left margin, which on a phone is most
   of the screen. The step and the connector arm shrink together so the elbows stay in proportion,
   and the chart keeps its whole width for names. */
@media (max-width:600px) {
  .treeview { font-size:.9rem; --tv-in:.85rem; --tv-arm:.6rem; --tv-mid:1.1rem }
  .treeview .node { gap:.3rem; padding:.24rem .45rem }
  .treeview .tgl { width:1.2rem; height:1.2rem }
}
/* The chart scrolls sideways rather than wrapping or being squeezed. A generation chart that
   reflows is hard to read - a name belongs on one line with its 字 and its counts - so the rows
   keep their length and the box moves under them.
   width:max-content on the list is what lets that happen: without it the rows are as wide as the
   box and the flex items shrink below their own text, which is how 「善仓 字壁轩 邑庠生」 once came
   out one character to a line. */
.treeview { overflow:auto; overscroll-behavior-x:contain }
.treeview > ul { width:max-content; min-width:100% }
.treeview .node { display:inline-flex; align-items:center; gap:.45rem; padding:.28rem .6rem; border-radius:var(--radius); background:var(--paper); border:1px solid var(--line) }
.treeview .node > * { flex:0 0 auto; white-space:nowrap }
.treeview .node:hover { border-color:var(--accent) }
.treeview .node .g { font-size:.72rem; color:var(--muted); background:var(--gray-200); padding:.05em .45em; border-radius:.25rem }
.treeview .node .zi { color:var(--muted); font-size:.88rem } .treeview .node .f { color:var(--mark-f); font-size:.8rem }
/* 妻陈氏、朱氏、冼氏 - quieter than the name it follows and set like the 字/号 beside it, because
   it is the same kind of fact: something the book records about the person, not the person. */
.treeview .node .sp { color:var(--muted); font-size:.82rem }
/* 本人: the person the chart was opened on. Tinted and outlined already, and now his name is
   bold as well - the same 600 the spine above him uses, because he and his ancestors are the one
   line through the page a reader is actually following. On a chart of a thousand nodes the tint
   is easy to scroll past; a bold name is not. */
.treeview .node.focus { background:var(--accent-soft); border-color:var(--accent) }
.treeview .node.focus a { font-weight:600 }
.treeview .node .desc { font-size:.72rem; color:var(--muted); background:var(--gray-200); padding:.05em .45em; border-radius:.25rem; white-space:nowrap }
/* 「侨居美国」 on a chart node, and beside the name on a person page. Its own colour rather than
   the muted grey the 字 and 号 use: a place is a different kind of fact from another name, and in
   a line of five hundred nodes the branch that went abroad should be findable by eye. */
.place { font-size:.74rem; color:var(--info); background:var(--info-soft); border:1px solid var(--info-line);
  padding:.05em .45em; border-radius:.25rem; white-space:nowrap }
.treeview .node .place { flex:0 0 auto }
/* 出继 / 入继. Its own colour rather than the place's: an adoption between branches is a fact
   about WHERE A LINE GOES, which is the one thing a 世系图 is for, and a reader scanning five
   hundred nodes for the branch that changed hands should not have to read every chip to find it.
   Shares the place's geometry so a node carrying both keeps one rhythm. */
.succ { font-size:.74rem; color:var(--warn); background:var(--warn-soft);
  border:1px solid var(--warn-line);
  padding:.05em .45em; border-radius:.25rem; white-space:nowrap }
.treeview .node .succ { flex:0 0 auto }
/* A son the book draws in ANOTHER man's line, standing in the family that lost him or took him.
   Dashed, because the row is a pointer and not a member of this family: his 世 and his name are
   here so the line is not simply missing him, and everything else about him - his wives, his
   sons, his 后裔 count - is on the entry this one points at. A solid node would say he belongs
   to this family twice over, which is the one thing the row must not say. */
.treeview .node.xref { border-style:dashed; background:transparent }
.treeview .node.xref a { font-weight:500 }

/* 族谱溯源. One generation to a line, reading downwards like the chart does, with the join to the
   book's own 世1 marked - a reader following the line arrives in the genealogy rather than at a
   second copy of the man it starts from. The citation sits under each man because the sources
   genuinely disagree: two of them spell a name another way, and a bibliography at the foot would
   make a reader checking one name work out which of five books he came from. */
/* The same chain as one line above the chart: names and arrows, no boxes. Boxes would read as
   nodes, and a node in this chart is a person with a page in this genealogy - which none of
   these men is. Muted, because the chart below it is the subject. */
.originstrip { margin:0 0 .6rem; font-size:.85rem; color:var(--muted); line-height:1.7 }
.originstrip a { font-weight:600 }
ol.originline { list-style:none; padding:0; margin:1rem 0; max-width:760px }
ol.originline li { position:relative; padding:.55rem 0 .55rem 1.6rem; border-left:2px solid var(--line) }
ol.originline li::before { content:''; position:absolute; left:-5px; top:1.05rem;
  width:8px; height:8px; border-radius:50%; background:var(--line) }
ol.originline li.apex { border-left-color:transparent }
/* Rows above the numbering stand apart from the numbered line - the break before 世1 is the point -
   but not from each other: a forebear put above 宋太宗 is his father, so a line runs from each such
   row's dot down to the next one's (the dot sits 1.05rem + 4px into every row). Without :has() the
   rows simply stand unjoined, as a single apex always has. */
ol.originline li.apex:has(+ li.apex)::after { content:''; position:absolute; left:-2px;
  top:calc(1.05rem + 4px); bottom:calc(-1.05rem - 4px); border-left:2px solid var(--line) }
ol.originline li.apex::before { background:var(--muted) }
ol.originline li.joins { border-left-color:var(--accent) }
ol.originline li.joins::before { background:var(--accent); width:10px; height:10px; left:-6px }
ol.originline .who { font-weight:600; font-size:1.05rem }
ol.originline .g { color:var(--muted); font-size:.85rem; margin-right:.4rem }
/* the years a 族谱溯源 row spans and its place, beside the name, quieter than it */
ol.originline .oyears { color:var(--ink-2); font-size:.9rem; margin-left:.45rem; font-variant-numeric:tabular-nums }
ol.originline .oplace { color:var(--muted); font-size:.9rem; margin-left:.45rem }
ol.originline .gc { color:var(--muted); font-size:.8rem; margin-left:.35rem }
/* a page about him elsewhere: small, at the end of his line, never broken across two */
ol.originline .xlink { font-size:.82rem; margin-left:.45rem; white-space:nowrap }
/* 族谱溯源 editor: one box per row, its fields in a grid that folds from seven across to two on a
   phone (7.5rem is what still fits two at 360px). The notes and the citation take the full width - they are sentences, not names. */
form.originedit { max-width:960px }
fieldset.orow { margin:.75rem 0; padding:.5rem 1rem .75rem }
fieldset.orow legend { padding:0 .35rem; font-weight:600 }
fieldset.orow.spare { border-style:dashed; box-shadow:none }
fieldset.orow .ofields { display:grid; grid-template-columns:repeat(auto-fill, minmax(7.5rem, 1fr)); gap:0 .75rem }
fieldset.orow .ofields label { margin:.4rem 0 }
fieldset.orow .ofields label input, fieldset.orow .ofields label select, fieldset.orow .ofields label textarea { margin-top:.25rem }
fieldset.orow .ofields .wide { grid-column:1 / -1 }
fieldset.orow .odel { margin-top:.4rem; color:var(--bad) }
/* 字辈 editor: the list by 世 as a narrow table, three short boxes a row, so a whole ruler of 28 世
   stays within a screen or two on a phone. */
form.gcedit { max-width:640px }
form.gcedit fieldset.box { padding:.5rem 1rem .75rem }
form.gcedit legend { padding:0 .35rem; font-weight:600 }
form.gcedit textarea.gcpoem { font-family:var(--serif); font-size:1.15rem; letter-spacing:.1em }
table.gctable { width:auto; margin:.25rem 0 }
table.gctable th, table.gctable td { padding:.2rem .35rem; border-bottom:0; vertical-align:middle }
table.gctable th { white-space:nowrap }
table.gctable td.n { color:var(--muted); font-size:.8rem; text-align:right }
table.gctable input { width:4.5em; padding:.25rem .5rem }
table.gctable input.gc { width:3.5em; font-family:var(--serif); font-size:1.1rem; text-align:center }
table.gctable input[type=checkbox] { width:auto; accent-color:var(--bad) }
table.gctable tr.spare input { border-style:dashed }
/* a 字辈 worked out from the 字辈诗 rather than written down - here and on the genealogy's front
   page - in a lighter hand than the ones somebody wrote */
.gens span.proj { border-style:dashed } .gens span.proj b { color:var(--muted) }
/* the slot above 第1行, folded until it is wanted */
details.oinsert { margin:.75rem 0 }
details.oinsert > summary { cursor:pointer; color:var(--accent); padding:.3rem 0; width:max-content; max-width:100% }
ol.originline .onote { margin:.3rem 0 0; font-size:.9rem }
ol.originline .src { margin:.2rem 0 0; font-size:.82rem; color:var(--muted) }
/* 附注 and 出处 keep the line breaks they were typed with - two citations on two lines stay two */
ol.originline .onote, ol.originline .src, ol.originline li.apex > p { white-space:pre-line }
/* An ancestor on the spine above the person you asked for. Solid, like every other line here -
   it is told apart by being unfilled, not by a different kind of stroke.
   The NAME is bold, though, and the rest of the row stays quiet. These are the twenty-odd people
   the reader is descended FROM, standing in a page of five hundred they are merely related to, and
   at a glance the spine is the one thing worth being able to pick out. The weight is the site's
   own 600, the same as a table's row headings. */
.treeview .node.line { background:transparent; color:var(--ink-2) }
.treeview .node.line a { color:var(--ink-2); font-weight:600 }
.treeview .node.line:hover { border-color:var(--accent) }
.treeview .tgl { width:1.35rem; height:1.35rem; border-radius:.25rem; border:1px solid var(--line-2); background:var(--field); color:var(--ink-2); font:600 .8rem/1 var(--font); cursor:pointer; display:inline-flex; align-items:center; justify-content:center; padding:0; margin:0 }
.treeview .tgl:hover { border-color:var(--accent); color:var(--accent); background:var(--field) }
.treeview .tgl.leaf { visibility:hidden }
.treeview li.collapsed > ul { display:none }
.treeview .more { color:var(--muted); font-size:.85rem; margin-left:.3rem }

/* What you can do from a member, at the back of the row.
   On a touch screen it is always there - there is no hover to reveal it with, and a control you
   cannot discover is a control you do not have. With a real mouse it stays out of the way until
   the row is pointed at, so five thousand rows are not five thousand buttons. visibility rather
   than display: the row keeps the same width either way, so nothing shifts under the pointer. */
/* The ▾ half again the size of the row's text (the user asked, 2026-10-05: it was too small to find
   or to hit), in a box no taller than the ＋/− toggle's, so the row does not grow with it. */
.treeview .nodemenu { margin-left:.15rem; padding:0 .3rem; border:0; border-radius:.25rem;
  background:none; color:var(--muted); font:inherit; font-size:1.5em; line-height:1; height:1.35rem;
  display:inline-flex; align-items:center; cursor:pointer }
.treeview .nodemenu:hover, .treeview .nodemenu[aria-expanded="true"] { background:var(--accent-soft); color:var(--accent) }
@media (hover:hover) and (pointer:fine) {
  .treeview .nodemenu { visibility:hidden }
  .treeview .node:hover .nodemenu,
  .treeview .node:focus-within .nodemenu,
  .treeview .nodemenu[aria-expanded="true"] { visibility:visible }
}
.nodepop { position:fixed; z-index:40; min-width:11rem; padding:.3rem; display:flex; flex-direction:column;
  background:var(--paper); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow) }
.nodepop a, .nodepop button { padding:.5rem .7rem; border-radius:.25rem; color:var(--ink-2); font:inherit;
  font-size:.9rem; white-space:nowrap; text-align:left; background:none; border:0; cursor:pointer; width:100% }
.nodepop a:hover, .nodepop button:hover { background:var(--accent-soft); color:var(--accent); text-decoration:none }
.nodepop hr { border:0; border-top:1px solid var(--line); margin:.25rem .3rem }
/* 添加子女 from the ▾: the person page's own form, in a dialog drawn like 查名's (app.js). Scrolls inside
   itself on a short screen, as the form is taller than a phone held sideways. */
dialog.adddlg { width:min(32rem,92vw); max-height:92vh; overflow:auto; border:1px solid var(--line);
  border-radius:var(--radius); background:var(--paper); color:var(--ink); padding:1rem 1.1rem 1.1rem;
  box-shadow:var(--shadow-lg) }
dialog.adddlg::backdrop { background:var(--scrim) }
.adddlg .flash { padding:.6rem .8rem; margin:0 0 .7rem }
/* the child just drawn there, marked until the reader has seen where it went */
.treeview .node.justadded { border-color:var(--accent); box-shadow:0 0 0 2px var(--accent-soft) }
.nodepop .togen { display:flex; align-items:center; gap:.4rem; padding:.35rem .7rem; cursor:text }   /* a tap on it goes to the box (app.js) */
.nodepop .togen label { display:flex; align-items:center; gap:.4rem; margin:0; font-weight:400;
  font-size:.9rem; color:var(--ink-2); white-space:nowrap }
/* 16px, not the menu's .9rem: under 16 Safari zooms the page in on a box as it is tapped, on an
   iPhone, and the menu was lost in the zoom (the user, 2026-10-05). */
.nodepop .togen input { width:4.2rem; padding:.25rem .4rem; font-size:16px }
.nodepop .togen .togo { width:auto; padding:.25rem .7rem; border:1px solid var(--line-2); border-radius:.25rem }

/* 待审修改: the bar that acts on what is ticked. Sticky, because the list is as long as the queue
   and the controls should not be somewhere above the part being read. */
.batchbar { position:sticky; top:var(--headerh); z-index:15; display:flex; flex-wrap:wrap; gap:.5rem;
  align-items:center; margin:.6rem 0; padding:.6rem .8rem; background:var(--paper);
  border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow) }
.batchbar input[name=note] { flex:1 1 12rem; width:auto; min-width:8rem }
.batchbar button { margin:0 }
label.pick { display:inline-flex; align-items:center; gap:.35rem; margin:0; font-weight:400 }
label.pick input { width:auto; margin:0 }
.box.edit .pick { margin-right:.2rem }
/* the deletion queue is marked apart from the other one, because it is a different question */
.batchbar.danger { border-color:var(--bad); background:var(--bad-soft) }
.box.edit.del { border-left:3px solid var(--bad) }
.box.edit .flash.sm { font-size:.85rem; padding:.4rem .6rem; margin:.3rem 0 }

/* The chart's own horizontal scrollbar sits at the foot of the chart, and a chart is thousands of
   rows long - so on a phone the control that moves it sideways is a very long way from the part
   you are looking at. This is a second bar, fixed to the bottom of the window, that drives the
   chart's scroll and follows it. It appears only when the chart is both too wide AND on screen.
   Small screens only: with a mouse the chart's own bar is where it is expected. */
/* No backdrop-filter. It had one, and a blur behind a 94%-opaque strip is not visible - but it
   does force the browser to re-composite whatever is under the bar on every frame, and what is
   under the bar while you are dragging the chart sideways is the chart, moving. On iOS that is
   paid for in exactly the gesture this bar exists to serve. */
.hscroll { position:fixed; left:0; right:0; bottom:0; z-index:30; height:15px;
  overflow-x:auto; overflow-y:hidden; background:var(--paper-veil);
  border-top:1px solid var(--line) }
.hscroll > div { height:1px }

/* Shown while a line is being expanded. 展开所有后代 on the founder is five and a half thousand
   people, which is a few seconds of fetching and laying out - long enough that without this the
   page looks broken rather than busy. Centred, like the other overlay on this site. */
.busy { position:fixed; inset:0; z-index:210; display:flex; align-items:center; justify-content:center;
  background:var(--scrim); backdrop-filter:blur(2px) }
.busy-card { min-width:min(20rem, 86vw); max-width:86vw; padding:1.2rem 1.4rem;
  background:var(--paper); border:1px solid var(--line); border-radius:var(--radius);
  box-shadow:var(--shadow-lg); text-align:center }
.busy-card p { margin:0 }
.busy-card .progress { margin:.9rem 0 .5rem }
.busy-note { font-size:.85rem; min-height:1.2em }
.busy-card button { margin-top:.6rem }
/* No width rule here on purpose. It used to be hidden above 600 px, which left exactly the case
   being complained about: a window of 700 or 800 px is still far narrower than twenty generations
   of chart, so the content ran off the right with no bar in reach. The script shows it whenever
   the chart is genuinely wider than its box AND on screen, whatever the window - that is the
   condition that matters, not a guess at what kind of device is asking. */
.toolbar { display:flex; flex-wrap:wrap; gap:.5rem; align-items:center; margin:.4rem 0 1rem }
/* wrap: label, field and button would not fit one line on a phone, and being flex they did not
   wrap - they simply ran past the edge, taking the page's whole width with them and everything
   else laid out to that. */
/* 在本族谱中查名, kept within reach.
   The chart is the one page on this site you scroll a long way down - five thousand rows of it -
   and the search box was pinned to the top of the document, so looking a cousin up from halfway
   down meant scrolling all the way back first.
   It is NOT sticky itself, though it was at first: the whole .charthead around it is, and this
   sat inside that declaring position:sticky with `.charthead .treefind { position:static }` two
   dozen lines below taking it straight back off again. A rule that says the opposite of what
   happens is how the header came to not stick at all last week; it spans the column and leaves
   the pinning to its parent. */
.treefind { display:flex; flex-wrap:wrap; gap:.5rem;
  align-items:center; margin:0; padding:.55rem 0 }
.treefind input { min-width:8rem }
.treefind input { flex:1; max-width:22rem } .treefind button { margin:0 }
/* On a phone the label, the box and the button do not fit on one line, and a search bar that
   wraps to two is a third of the screen gone before the chart starts. The placeholder says
   「名字 / 字 / 号」, which is the same thing the label says, so the label goes - to screen
   readers only, which still have it through for="tree-q". */
@media (max-width:600px) {
  .treefind > label { position:absolute; width:1px; height:1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap }
  /* the fallback differs on a phone, where the header is shorter; JS overwrites both */
  :root { --headerh:3.45rem }
}
.treefind .cands { position:absolute; top:100%; left:0; right:0; z-index:20; margin:.25rem 0 0; box-shadow:var(--shadow-lg) }
.treefind .cands li { padding:0 } .treefind .cands a { display:block; padding:.45rem .7rem; text-decoration:none }
.treefind .cands a:hover { background:var(--accent-soft) }

/* The chart's header, and how much of it follows you down the page.
   The whole block is sticky - the chart is the one page here you scroll a long way down, and
   losing WHOSE line you are reading is the thing that happens. But not all of it earns the room:
   once it sticks, 世系图：仲东 and the search box are what you still need, while the crumbs and
   the toolbar are a page you have already read. Those fold away, the title shrinks to a line, and
   the button brings the rest back for anyone who wants it.
   The search box inside goes back to ordinary flow: two sticky things arguing over one offset is
   how the last version of this got it wrong. */
.charthead { position:sticky; top:var(--headerh); z-index:16; background:var(--bg); margin:0 0 1rem }
/* No transform here. There was one - translateZ(0), to put this box on its own compositing
   layer, on the theory that it would make Safari paint the caret of the search field against the
   same surface as the field itself. It was a guess at an iOS bug that cannot be reproduced on this
   machine, and it arrived alongside a flicker that can: a promoted layer reports its position a
   frame behind, and this box's position is what decides whether it has pinned itself. A guess at
   one bug is not worth a second, real one.
   The caret is addressed instead by measuring the mark above rather than this box - see app.js -
   which takes the layer out of the question entirely. If the cursor still misbehaves on an iPhone,
   the fallback is to stop being sticky while the field has focus, behind (hover:none) and
   (pointer:coarse) so a desktop keeps its pinned header. */
.chartmark { height:0 }

/*
 * Scroll anchoring off, on chart pages only. The class is put on <html> by app.js, beside the code
 * that made this necessary.
 *
 * When the chart header pins itself it folds two rows away and loses about 113px. It is
 * position:sticky, so it stays in flow: that 113px comes out of the document ABOVE the viewport,
 * and everything below moves up by it. Chrome sees content above the viewport change size and
 * helpfully scrolls by the same amount to keep what you are looking at where it was - which is
 * exactly the right thing to do for an image loading at the top of an article, and exactly the
 * wrong thing here, because the scroll position is what decides whether the header is pinned.
 *
 * Round it went, one flip per frame: pinned at scrollY 114 -> folds -> Chrome compensates to
 * scrollY 1 -> not pinned any more -> unfolds -> Chrome compensates back to 114 -> pinned. The
 * class alternated 「charthead」/「charthead stuck」 for as long as you left it there. Both earlier
 * attempts at this - not measuring the header itself, and a band around the threshold - were
 * about the first half of the loop, and neither could touch the second: the page was being moved
 * BY the answer, so no way of asking the question was ever going to settle.
 *
 * This is NOT made redundant by the compensation app.js now puts on the header's bottom margin,
 * which holds the document's height constant. That was measured, not assumed: with the
 * compensation in and this rule out, the probe still finds positions that will not settle -
 * working out how much to compensate by means briefly taking the margin off to read what the
 * stylesheet gives, and Chrome anchors on that intermediate layout. The two fix different halves
 * of the same fault and the page needs both.
 *
 * tools/chart_flicker_probe.js drives a real Chrome and reproduces it; the model in
 * tools/stuck_test.js cannot, which is why it passed throughout.
 *
 * What is given up: anchoring keeps your place when something above the viewport grows - a web
 * font arriving, an image loading. On this page the only thing above the viewport that changes
 * size is the header, and this is the change we do not want compensated. Everything the chart
 * loads, it loads below you.
 */
html.noanchor, html.noanchor body { overflow-anchor:none }
/* Room above the title once it is stuck. Padding inside the block rather than a bigger `top`:
   raising where it pins would open a gap between the two bars for the chart to scroll through,
   and two sticky strips with the page moving between them reads as broken rather than as spaced.
   This way the header's lower edge and the title are apart, with nothing showing between. */
.charthead.stuck { border-bottom:1px solid var(--line); padding-top:.55rem }
.charthead.stuck .crumbs, .charthead.stuck .toolbar { display:none }
/*
 * The picture WeChat makes a share card's thumbnail from - see layout.php. Off the screen rather
 * than display:none, which WeChat is reported to skip, and out of the way of everything: it is not
 * a picture this site is showing anybody.
 */
.wxcard { position:absolute; left:-9999px; top:0; width:1px; height:1px; opacity:0; pointer-events:none }

/*
 * Where WeChat's ··· menu is, said over the page.
 *
 * A layer over everything, because the thing it points at is not in the document: WeChat's own bar
 * sits above the viewport, so the arrow has to reach the very top right of the screen and no
 * element inside the page can be put there. See share.js for why the button cannot simply share.
 *
 * The arrow is a character, not an image or a border trick: ↗ is in every font a phone ships with,
 * costs nothing to load, and points the way whatever the screen's size or scale.
 */
.wxguide {
  position:fixed; inset:0; z-index:1000; background:rgba(0,0,0,.72);
  display:flex; flex-direction:column; align-items:flex-end; justify-content:flex-start;
  padding:.6rem 1rem 0; gap:.4rem; color:#fff; text-align:right; cursor:pointer;
  -webkit-tap-highlight-color:transparent;
}
.wxguide::before {
  content:'\2197'; font-size:2.6rem; line-height:1; margin-right:.2rem;
  /* Not animated on a reader who has asked for less of that; see the rule below. */
  animation:wxpoint 1.4s ease-in-out infinite;
}
.wxguide .wxsay { margin:0; font-size:1.05rem; font-weight:600; max-width:15rem; line-height:1.5 }
.wxguide .wxnote { margin:0; font-size:.85rem; opacity:.75; max-width:15rem }
@keyframes wxpoint { 0%,100% { transform:translate(0,0) } 50% { transform:translate(3px,-3px) } }
@media (prefers-reduced-motion:reduce) { .wxguide::before { animation:none } }

/* Opened out again, and the fold is STILL in the corner. The crumbs come back with 分享本页 at
   their right end - margin-left:auto puts it there - and the fold is out of flow above it, so the
   two were drawn on top of each other: one visible button, in the wrong place, and the way back
   to the folded header covered by it. The padding is what the fold occupies plus a gap. */
.charthead.stuck.open .crumbs { display:flex; padding-right:2.4rem }
.charthead.stuck.open .toolbar { display:flex }
/* ...and the title needs no room reserved here: in this state the corner belongs to the crumbs
   line above it, not to the heading. */
.charthead.stuck.open h1 { padding-right:0 }
/* A heading is a heading when you arrive at it and a label once you are past it.
   The padding keeps it clear of the controls in the corner - the fold, 分享本页 and 🔑/🔓 - which
   are out of flow and would otherwise be overrun by a long name rather than pushed by it.

   16rem, and it is a MEASURED number rather than a judged one. The corner is the fold (28px),
   分享本页 and the padlock, and 分享本页 is 91px of 「分享本页」 and 135px of "Share this page" -
   so the widest case is English, where the group came to 183px and the whole corner to 243px.
   8.5rem was right for two controls and wrong for three: the title overlapped the group by 44px
   at 390px in English, which is exactly the width the padlock added. */
.charthead.stuck h1 { font-size:1.15rem; margin:.15rem 0 0; padding-right:16rem }
/* ...and on a phone the labels go instead of the title. 243px of a 390px strip is nearly two
   thirds of it given to three controls, and what is left will not hold a name. Below this the
   corner is three glyphs - 🔑 ➦ ▲ - which is about 112px, and the heading gets the rest.
   The full wording stays on every one of them as title and aria-label. */
@media (max-width:700px) {
  .charthead.stuck .headtools .sharebtn .lbl { display:none }
  .charthead.stuck h1 { padding-right:9.5rem }
}
/* The folded header's corner controls - 🔑/🔓 and 分享本页 - BESIDE the fold and never beneath
   it: ▲ is how a reader gets the rest of the header back, and a control stacked on it would
   cover the way out. `right` clears the fold button, about 1.75rem of glyph, padding and border,
   with a gap after it.

   A GROUP, not one `right` per button. With two of them each offset has to clear whatever is to
   its right, and that width is the label: 「分享本页」 in Chinese, "Share this page" in English,
   "Enter the family code" beside it. A flex row measures itself and cannot be wrong in a
   language nobody tested. */
.charthead .headtools { display:none }
.charthead.stuck .headtools { display:flex; align-items:center; gap:.35rem;
  position:absolute; top:.25rem; right:2.4rem; z-index:17 }
/* ...and not when the header has been opened out again: the crumbs are back, and the buttons in
   them are the same buttons. One set on screen at a time, always. */
.charthead.stuck.open .headtools { display:none }
/* :not([hidden]) is load-bearing. app.js clears `hidden` on every .sharebtn it finds, but only
   where a browser can share or copy at all; without that guard the group's display would beat
   the [hidden] attribute's own and put a dead button on screen wherever neither API exists. */
.charthead .sharehead { display:none }
.charthead.stuck .headtools .sharehead:not([hidden]) { display:inline-block }
/* THE GLYPH ALONE in the folded strip. Three text buttons and a title do not fit a phone's
   corner - 390px is about 24rem and the labels alone are past 14 - and this is the one of the
   three whose meaning a single character carries: a closed padlock to get in, an open one to say
   you already are. The full wording stays on the button as title and aria-label, so a screen
   reader and a hover both still get the sentence. */
.charthead .unlockhead .lbl { display:none }
.charthead .unlockhead { padding-left:.4rem; padding-right:.4rem }
/* Offered only while there is something folded away - at the top of the page the whole header is
   already on screen and a button to reveal it would do nothing. Its arrow is the direction the
   block is about to move: ▼ opens it out, ▲ folds it back up. */
.pinhead { display:none; position:absolute; right:0; top:.25rem; z-index:17; padding:.1rem .45rem;
  line-height:1; font-size:.75rem; border:1px solid var(--line-2); border-radius:.25rem;
  background:var(--paper); color:var(--muted); cursor:pointer }
.charthead.stuck .pinhead { display:block }
/* Hover only where there is a pointer to hover WITH. On a touch screen :hover latches after a tap
   and the button sits there lit, looking like it is still doing something. */
@media (hover:hover) { .pinhead:hover { color:var(--accent); border-color:var(--accent) } }
/* ...and clicking it leaves the focus on it, which did the same thing by another route. A pointer
   gets no change at all; a keyboard still gets a ring, because somebody tabbing through the page
   has no other way to know where they are. */
.pinhead:focus:not(:focus-visible) { color:var(--muted); border-color:var(--line-2); outline:none }
.pinhead:focus-visible { outline:2px solid var(--accent); outline-offset:1px }
/* No `.charthead { position:relative }` down here. There was one - left over from when only a
   pinned charthead was sticky and the block needed to be a positioning context for the button -
   and being later in the file at the same specificity it quietly won, so the header was relative
   and never stuck at all. A sticky element is already a containing block for an absolutely
   positioned child, so the rule was not only harmful, it was not needed. */

/* ◎ beside the name in the title: put 本人 back on screen. Sized off the line it sits in rather
   than off the h1, so it stays the same small mark whether the header is at rest or stuck and the
   title has shrunk to a label. No hover or pointer-focus state, like the fold beside it. */
.findfocus { margin-left:.5rem; padding:.05rem .4rem; line-height:1; font-size:.9rem;
  vertical-align:.08em; border:1px solid var(--line-2); border-radius:.25rem;
  background:var(--paper); color:var(--muted); cursor:pointer }
.findfocus:focus:not(:focus-visible) { outline:none }
.findfocus:focus-visible { outline:2px solid var(--accent); outline-offset:1px }

/* 最近查看 - the ▾ at the head of the chart's search box. Sized to the input beside it so the
   two read as one control rather than a button that happened to land there. */
.recentbtn { flex:0 0 auto; padding:.5rem .55rem; line-height:1; background:var(--paper);
  border:1px solid var(--line-2); border-radius:var(--radius); color:var(--muted); cursor:pointer }
.recentbtn:hover, .recentbtn[aria-expanded="true"] { color:var(--accent); border-color:var(--accent) }
.treefind .cands.recents { top:100% }
.cands .rgen { color:var(--muted); font-size:.8rem; margin-right:.4rem }
.cands .rempty { padding:.45rem .7rem; color:var(--muted); font-size:.9rem }
/* Each row can be dropped on its own, and the whole list at once - a trail through somebody's
   family that cannot be cleared is one you would rather not have been keeping. The × sits at the
   far end so it is never where the thumb lands when the row itself is what was wanted. */
.cands.recents li { display:flex; align-items:center }
/* 最近查看 rows hold a link AND a ×, so there is no :only-child to stretch and an overlay would
   swallow the button. This row has no bare text in it either, so the padding simply moves off the
   row and onto the two things in it: the link then fills its own height and everything up to the
   ×, and stops there. The one control that must NOT be easy to hit by accident is the one that
   forgets a person. */
.cands.recents li { padding:0 }
.cands.recents li > a { flex:1; min-width:0; display:flex; align-items:center;
  padding:var(--cy) var(--cx); text-decoration:none }
.cands.recents .rclear { padding:0 }
.cands.recents .rx { flex:0 0 auto; margin-right:.35rem; padding:.2rem .5rem; line-height:1;
  border:none; background:none; color:var(--muted); font-size:1.05rem; cursor:pointer; border-radius:.25rem }
.cands.recents .rx:hover { color:var(--bad); background:var(--bad-soft) }
.cands.recents .rclear { border-top:1px solid var(--line); padding:0 }
.cands.recents .rclear button { width:100%; margin:0; padding:.45rem .7rem; text-align:left;
  border:none; background:none; color:var(--muted); font-size:.88rem; cursor:pointer }
.cands.recents .rclear button:hover { color:var(--bad); background:var(--bad-soft) }

/* pdf options */
.opts { display:grid; grid-template-columns:repeat(auto-fit,minmax(200px,1fr)); gap:.7rem; margin:.5rem 0 1rem }
/* 世系首页页码: a page number, not a line of text - room for its placeholder and no more */
label.firstpage input { max-width:18rem }
/* A checkable card, the shape Bootstrap makes with .btn-check + a .card: a hairline until it is
   chosen, then the primary border over $primary-bg-subtle. */
.opt { border:1px solid var(--line); border-radius:var(--radius); padding:.75rem; cursor:pointer; background:var(--paper);
  transition:border-color .15s ease-in-out, background-color .15s ease-in-out }
.opt:hover { border-color:var(--line-2) }
.opt:has(input:checked) { border-color:var(--accent); background:var(--accent-soft) }
.opt:has(input:focus-visible) { box-shadow:0 0 0 .25rem var(--focus) }
.opt input { display:none } .opt b { display:block; margin-bottom:.15rem } .opt small { color:var(--muted) }
/* 审核方式: one row per kind of request, two radios each. */
table.policytable { width:100%; border-collapse:collapse; margin:.6rem 0 1rem }
table.policytable th, table.policytable td { padding:.4rem .5rem; border-bottom:1px solid var(--line); text-align:left }
table.policytable th:nth-child(n+2), table.policytable td:nth-child(n+2) { width:7rem; text-align:center }
table.policytable .pick { margin:0; display:inline-flex }

/* 分享本页 sits at the top right of the page, on the crumbs line under the navbar.
   margin-left:auto, NOT float:right - .crumbs is already a flex row (further down this file), and
   a flex container ignores float entirely, so the floated button simply sat wherever its source
   order put it, which was the far LEFT. The auto margin eats the free space to its left instead.
   Shown at every width: a phone is where the browser's own share sheet is most worth having. */
.crumbs .sharebtn { margin-left:auto }

/* 待复核 on the tree admin page: 355 rows an administrator works down with the scan open, so
   one line each and the 原书页码 where the eye can run along it. */
ol.revlist { list-style:none; margin:.6rem 0 0; padding:0 }
ol.revlist li { display:flex; align-items:baseline; flex-wrap:wrap; gap:.45rem;
                padding:.3rem .2rem; border-bottom:1px solid var(--line) }
ol.revlist li:hover { background:var(--accent-soft) }
ol.revlist .pick { margin:0 }
ol.revlist a { text-decoration:none }
ol.revlist .tag { margin-left:0 }
.revpager { margin:.6rem 0 0; display:flex; gap:.5rem; flex-wrap:wrap }
.revpager a { text-decoration:none }

/* 字体, on the two option pages. Folded away by default - see font_picker() - because the pages it
   sits on already ask five and eight other questions and this is the one that has a right answer
   for almost everybody. */
details.fold { margin:1rem 0; border:1px solid var(--line); border-radius:var(--radius); padding:.6rem .9rem;
               background:var(--paper) }
details.fold > summary { color:var(--ink); font-weight:600 }
details.fold[open] > summary { margin-bottom:.4rem; padding-bottom:.5rem; border-bottom:1px solid var(--line) }
details.fold h3 { font-size:1rem; margin:.9rem 0 .2rem }
/* The same fold holding form fields rather than a panel: 字, 号 and 功名 on 添加人物. Inside a
   .box the fill would be paper on paper, so it is dropped and the hairline alone marks the group.
   The summary takes the accent, unlike the panel version - a fold nobody notices is a field
   nobody fills, and these five are the ones somebody entering an elder specifically came for. */
details.fold.names { background:none; box-shadow:none }
details.fold.names > summary { color:var(--accent); font-weight:500 }
details.fold.names > label:last-of-type { margin-bottom:.15rem }
details.fold .opts { margin-top:.5rem }

/* THE SPECIMEN. Its whole job is to be looked at, so it is set large: at the size of body text two
   宋 weights are a guess, and a 宋 against a 黑 is a guess at any size below about 20px. The line is
   allowed to wrap and the box is not given a fixed height, because the same eleven characters are
   wider in some faces than others and clipping them would hide the difference being shown. */
.opts-face .spec { display:block; font-size:1.55rem; line-height:1.45; margin:-.1rem 0 .45rem;
                   color:var(--ink); word-break:break-all }
/* Not italic, not oblique, not synthesised: the browser must draw exactly the face that arrived, or
   the specimen is a picture of something the renderer will not produce. */
.opts-face .spec { font-style:normal; font-synthesis:none }
.zf-serif400, .zf-sans400 { font-weight:400 }
.zf-serif700, .zf-sans700 { font-weight:700 }

.swatch { height:54px; border-radius:var(--radius); margin-bottom:.5rem; border:1px solid var(--line) }
/* The cover picture, at the proportions of the paper it is drawn on. aspect-ratio rather than a
   height, so the box keeps the shape of a book even before the image arrives and even if it never
   does - and the swatch colour under it is what shows then, which is why the class stays. */
.coverpic { display:block; width:7.5rem; max-width:100%; height:auto; aspect-ratio:1/1.414;
            margin:0 auto .55rem; object-fit:cover }
/* A cover card carries two controls that are NOT part of choosing it - the zoom over the picture
   and the photo bar under it - so the card is the positioning context and they are siblings of
   the label, never inside it. */
.opt-cover { position:relative }
/* Centred by FLEX, not by line-height: ⤢ U+2922 carries its own ascent and sat low in the
   circle, which a line-height equal to the height cannot correct - it centres the line box, not
   the glyph's ink. */
.coverzoom { position:absolute; top:.95rem; right:.95rem; width:1.7rem; height:1.7rem; padding:0;
             border:0; border-radius:50%; background:rgb(0 0 0 / .42); color:#fff; font-size:.85rem;
             display:flex; align-items:center; justify-content:center; line-height:1;
             cursor:pointer; opacity:0; transition:opacity .15s ease-in-out }
.opt-cover:hover .coverzoom, .coverzoom:focus-visible { opacity:1 }
@media (hover:none) { .coverzoom { opacity:.85 } }     /* a finger has no hover to reveal it with */
.photobar { display:flex; flex-wrap:wrap; align-items:center; gap:.35rem; margin-top:.15rem }
.pbtn { font-size:.82rem; padding:.2rem .5rem; border:1px solid var(--line); border-radius:var(--radius);
        background:var(--paper); color:var(--ink); cursor:pointer; line-height:1.5 }
.pbtn:hover { border-color:var(--line-2) }
.pmsg { font-size:.78rem; color:var(--muted) }
.pmsg.bad { color:var(--danger, #b3261e) }
/* The cover as a page, not as a thumbnail. Plain <dialog>, so Esc and the top layer come free. */
/* A layout sample is a PAGE of the book, so it is shown whole rather than cropped to fill -
   and on paper, so the plain ground behind it is the sheet. */
.coverpic.sample { object-fit:contain; background:#fff; border:1px solid var(--line) }
dialog.coverbig { border:0; padding:0; background:#0000; max-width:92vw; max-height:92vh }
dialog.coverbig::backdrop { background:rgb(0 0 0 / .72) }
dialog.coverbig img { display:block; max-width:92vw; max-height:92vh; width:auto; height:auto;
                      box-shadow:0 1.2rem 3rem rgb(0 0 0 / .5) }
/* NOT THEME COLOURS, and the next person doing a #fff sweep should leave them alone: these are
   pictures of the physical cover stock the reader is choosing between - red cloth, black ink,
   jade - and the printed book is not themed. */
.sw-red { background:linear-gradient(135deg,#8b1a1a,#b8322a) } .sw-ink { background:linear-gradient(135deg,#1c1c1c,#3a3a3a) } .sw-jade { background:linear-gradient(135deg,#2e5e4e,#4b8a72) }
/* The three traditional ones show their STRUCTURE, not just their colour, because that is what is
   being chosen: 瓷青 the pasted title slip down the middle, 朱丝栏 the ruled columns, 线装 the
   cloth spine down the right edge. Each is the same drawing the cover itself makes, in CSS - so
   the swatch cannot quietly stop describing the book. */
.sw-qingzhi { background:linear-gradient(90deg,#1d344f 0 38%,#f3ead6 38% 62%,#1d344f 62% 100%) }
.sw-zhulan { background:repeating-linear-gradient(90deg,#bf242a 0 1px,#0000 1px 13px),#f4eddd }
.sw-xianzhuang { background:linear-gradient(90deg,#4f2e21 0 86%,#3e2218 86% 100%) }
/* ...and the four modern bindings: quarter-bound navy spine, gold-on-black brocade, the ink
   block over sand, and flat vermilion. */
.sw-cangqing { background:linear-gradient(90deg,#ede8db 0 80%,#26436b 80% 100%) }
.sw-jinjin { background:linear-gradient(135deg,#241a16,#42331f) }
.sw-kuaimian { background:linear-gradient(180deg,#1b1d21 0 40%,#bf242a 40% 43%,#e8dcc8 43% 100%) }
.sw-zhumian { background:#bd3227 }
/* The 世次栏 styles: a tall strip repeating the pattern each one rules - the box, the hairline,
   the pale block, the dark tab. A pattern is CSS's to draw; the covers above are not, which is
   why they are pictures from the renderer and these are not. */
.rulerpic { width:22px; height:54px; margin:0 0 .5rem; border-radius:2px }
.rp-boxed { border:1px solid #8a8a8a; background:repeating-linear-gradient(180deg,#0000 0 12px,#8a8a8a 12px 13px) }
.rp-plain { background:repeating-linear-gradient(180deg,#0000 3px 12px,#b5b5b5 12px 13px,#0000 13px) }
.rp-tint  { background:repeating-linear-gradient(180deg,#e5e2d9 0 11px,#0000 11px 13px) }
.rp-tab   { background:repeating-linear-gradient(180deg,#4d4843 0 11px,#0000 11px 13px) }
/* 圆角标 is the tab rounded and pulled in off the edge, so its strip is inset and its blocks
   carry the same corner the page does. */
.rp-pill  { background:repeating-linear-gradient(180deg,#171717 0 10px,#0000 10px 13px);
            background-clip:content-box; padding:0 2px; border-radius:4px }

/* home guide: how to use / what is a zupu / FAQ */
.jump { margin:.5rem 0 0; font-size:.95rem } .jump a { margin-right:1.1rem; font-weight:500 } .jump a::before { content:"↓ "; color:var(--line-2) }
.guide { scroll-margin-top:4.6rem; margin-top:2.4rem }
/* 待复核 is scrolled to by name - from the 每页显示 control and from the list's own pager - and the
   site header is sticky, so without this the heading arrives underneath it. A little more than the
   header's own height, so the fold's summary is clear of it rather than touching. */
#q-review { scroll-margin-top:calc(var(--headerh) + .75rem) }
/* ...and so are 族谱管理's settings boxes, by the redirect after their 保存 (routes.php) */
#policy, #description, #access, #access-view, #delete { scroll-margin-top:calc(var(--headerh) + .75rem) }
.guide h2 { font-family:var(--serif); font-size:1.4rem; color:var(--ink); margin-bottom:.8rem; padding-left:.7rem; border-left:4px solid var(--accent) }
.guide h3 { margin:1rem 0 .3rem; color:var(--accent) }
.guide p { max-width:72ch }
ol.steps { list-style:none; counter-reset:step; padding:0; margin:.6rem 0; display:grid; grid-template-columns:repeat(auto-fill,minmax(min(320px,100%),1fr)); gap:.8rem }
ol.steps li { counter-increment:step; position:relative; padding:1rem 1.1rem 1rem 3.3rem; background:var(--paper); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); line-height:1.55 }
ol.steps li::before { content:counter(step); position:absolute; left:1rem; top:1rem; width:1.7rem; height:1.7rem; border-radius:50%; background:var(--accent); color:var(--on-accent); font-weight:600; font-size:.9rem; display:flex; align-items:center; justify-content:center }
ol.steps li > b:first-child { display:block; margin-bottom:.25rem; font-size:1.02rem; color:var(--ink) }
ol.steps li > b:first-child + .muted { display:block; margin:-.25rem 0 .25rem; font-size:.85rem }
/* 族谱统计 - counts, so every column but the first two reads as a number and is set right.
   The table has seven columns and a phone has none to spare, so it gets its own scroller rather
   than pushing the page sideways; the bars are the first thing to go when there is no room. */
.tablewrap { overflow-x:auto }
/* 系统管理's and 账号管理's account tables, in a .tablewrap of their own: squeezed to a phone's width, a
   cell's buttons broke between every two characters, 「设为族谱管理员」 one character a line and each row
   317px tall. A button, a list or a tag is one piece; the table scrolls sideways in its box instead. */
table.users button, table.users select, table.users .tag { white-space:nowrap }
table.census { min-width:30rem }
table.census td, table.census th { text-align:right; white-space:nowrap }
table.census thead th:first-child, table.census tbody th, table.census tfoot th,
table.census .gc { text-align:left }
table.census tbody th { font-weight:600; color:var(--ink); font-size:.95rem }
table.census .gc { font-family:var(--serif); color:var(--accent) }
table.census tfoot th, table.census tfoot td { border-top:2px solid var(--line); border-bottom:none;
  color:var(--ink); font-weight:600; font-size:.95rem }
.barcell { width:34%; min-width:8rem }
/* 每日访客 in 系统管理: two figures a row, so the count stays near its day rather than across the page */
table.census.visits { min-width:0; max-width:40rem }
.bar { display:flex; height:.5rem; min-width:3px; border-radius:var(--radius); overflow:hidden;
  background:var(--gray-200); vertical-align:middle }
.bar i { display:block; height:100%; min-width:1px }
/* Two shades of one hue is what this was - two reds before, two blues after - and at the widths
   these bars actually get, a 20-pixel run of #0d6efd beside #0a58ca is one bar. Bootstrap ships a
   whole palette; $primary and $teal are told apart at any size, and $gray-500 for the unrecorded. */
.bar i.m { background:var(--accent) } .bar i.f { background:var(--data-f) } .bar i.u { background:var(--data-u) }
/* A boxed aside that is neither an error nor a success - the page's own remark on its figures. */
.note { border-left:3px solid var(--accent-2) }
@media (max-width:600px) { .barcell { display:none } table.census { min-width:0 } }

/* 族谱统计's two charts: the population of each generation, and the growth between them.
   Drawn as inline SVG by app/views/_census_charts.php, so everything here reaches it - the theme's
   own custom properties included, which is why there is no second set of colours for dark mode.

   SCROLLS RATHER THAN SHRINKS, like the table above it. An SVG told to be 100% wide scales its
   text with it: at phone width the axis labels reach about five pixels and the chart stops being
   readable at exactly the size where a reader has least room to spare. The min-width holds it at a
   legible size and .chartwrap lets it move sideways instead. */
.chartwrap { overflow-x:auto; margin:0 0 1.2rem }
.censuschart { display:block; width:100%; min-width:34rem; height:auto }
.charth { font-size:1rem; margin:1.2rem 0 .4rem; color:var(--ink-2) }
.censuschart .cbar { fill:var(--accent) }
.censuschart .grid { stroke:var(--line); stroke-width:1 }
.censuschart .axisline { stroke:var(--line-2); stroke-width:1 }
.censuschart .axis { fill:var(--muted); font-size:11px; font-family:var(--font) }
/* 行辈字 under its 世, set as table.census .gc sets it - the serif face and 族谱红 - because it is
   the same fact in both places. A point smaller than the number above it: the number is what the
   eye counts along and the character is what it recognises, so the two should not compete. */
.censuschart .gchar { fill:var(--accent); font-size:10px; font-family:var(--serif) }
/* The growth line and its points are one colour: the chart has a single series, and a second
   colour on the dots would say there were two things to compare. */
.censuschart .cline { fill:none; stroke:var(--accent); stroke-width:2;
  stroke-linejoin:round; stroke-linecap:round }
.censuschart .cdot { fill:var(--accent) }
/* A generation whose growth runs off the top: the caret sits on the ceiling and its real figure is
   printed above it, so the reader sees both that the line was cut and where it actually went. */
.censuschart .cover { fill:var(--accent) }
.censuschart .overlabel { fill:var(--accent); font-size:10px }
/* 1.00 - the generation that held its own. Dashed, and in the muted ink, because it is a rule to
   read the line against rather than a value the book recorded. */
.censuschart .hold { stroke:var(--muted); stroke-width:1; stroke-dasharray:4 3 }
.censuschart .hold-label { fill:var(--muted); font-size:10px }

/* 世代名录 - one row of 族谱统计 opened up, which in 世24 of this book is 1,018 rows. Tighter
   padding than the site's default table because the page is read as a list rather than studied a
   cell at a time, and tabular figures so the two count columns line up down the page. On a phone
   the running number is the first thing to go: it is an aid to counting, not information. */
table.roster { min-width:34rem }
table.roster td, table.roster th { padding:.35rem .6rem }
table.roster .idx { width:3.5em; text-align:right; color:var(--muted) }
table.roster .num { text-align:right; white-space:nowrap }
table.roster .idx, table.roster .num { font-variant-numeric:tabular-nums }
table.roster tr.self > td { background:var(--accent-soft) }
@media (max-width:600px) { table.roster { min-width:0 } table.roster .idx { display:none } }
.pager { display:flex; align-items:center; gap:.8rem; flex-wrap:wrap }

/* 查询结果. Each result is one link and the link is the whole row - see the comment in
   search.php for why it stopped being a table.

   Laid out as a grid rather than left to wrap, because the point of the 世 and the father is that
   they line up down the page: that is how a reader picks their 必繁 out of the several the book
   holds. The columns give way at 640px, where four of them across a phone would leave the names
   two characters wide.

   Coloured like ordinary text, not like a link. Six blue underlined rows would be a wall; the name
   carries the accent so it is still clear what the row is about, and the whole row lights up under
   the pointer, which is what says it can be clicked. */
.results { list-style:none; margin:.5rem 0; padding:0; border-top:1px solid var(--line) }
.results li { border-bottom:1px solid var(--line) }
.results .hit { display:grid; grid-template-columns:minmax(9rem,2fr) 4rem minmax(7rem,1.6fr) auto;
  gap:.15rem .8rem; align-items:baseline; padding:.5rem .6rem; color:var(--ink); text-decoration:none }
.results .hit:hover { background:var(--accent-soft) }
/* :focus-visible, so this shows for somebody tabbing through and never for a mouse click. */
.results .hit:focus-visible { background:var(--accent-soft); outline:2px solid var(--accent); outline-offset:-2px }
.results .hit b { color:var(--accent); font-weight:600 }
.results .nm span, .results .kin span { margin-left:.35rem }
.results .gen { color:var(--muted); font-size:.9rem; white-space:nowrap }
.results .ref { text-align:right; font-size:.88rem; white-space:nowrap }
@media (max-width:640px) {
  .results .hit { grid-template-columns:1fr auto }
  .results .kin { grid-column:1 } .results .ref { grid-column:2 }
}

table.glossary td:first-child { width:9.5em; font-weight:600; color:var(--accent); white-space:nowrap } table.glossary td { font-size:.93rem; padding:.4rem .5rem }
.faq details { margin:.4rem 0; background:var(--paper); border:1px solid var(--line); border-radius:var(--radius); padding:.6rem 1rem }
.faq summary { font-weight:600; color:var(--ink) } .faq details[open] summary { color:var(--accent) } .faq details p { margin:.4rem 0 .3rem }
@media (max-width:600px) { .jump a { display:inline-block; margin:.2rem .9rem .2rem 0 } table.glossary td:first-child { width:6.5em; white-space:normal } }

/* tools: simplified <-> traditional converter */
.convert textarea { min-height:16rem; font-size:1.05rem; line-height:1.7; resize:vertical }
.convert-opts { display:flex; flex-wrap:wrap; gap:.4rem 1.4rem; align-items:center; margin-bottom:.2rem }
.convert-opts label { margin:0; font-weight:500 } .convert .row label { margin:.3rem 0 }
.convert-actions { display:flex; flex-wrap:wrap; align-items:center; gap:.2rem .6rem; margin:.4rem 0 0 }

/* PDF generation progress */
.genbox { max-width:560px; margin:1.2rem auto; text-align:center }
.genbox h1 { font-family:var(--serif) }
/* $progress-height 1rem on $progress-bg $gray-200, square-cornered at $border-radius, with a
   flat $primary bar - no gradient, no pill, no border. */
.progress { height:1rem; border-radius:var(--radius); background:var(--gray-200); overflow:hidden; margin:1rem 0 .5rem; font-size:.75rem }
.progress i { display:block; height:100%; width:0; background:var(--accent);
  transition:width .6s ease } .progress.indet i { width:35% !important; animation:slide 1.3s ease-in-out infinite }
@keyframes slide { 0% { margin-left:-35% } 100% { margin-left:100% } }
.genpct { font-family:var(--serif); font-size:2rem; color:var(--accent); line-height:1 }
.genstage { color:var(--ink-2); margin:.35rem 0 } .genmeta { color:var(--muted); font-size:.88rem }
.genbox .btn { margin-top:1rem } .genbox .err { color:var(--bad) }
/* nothing to download yet: the button says so rather than handing over a request that hangs */
/* Bootstrap dims a disabled button rather than repainting it: $btn-disabled-opacity .65, the
   colours left alone. The pointer-events line is NOT cosmetic and is unchanged - see below. */
button:disabled, .btn.disabled { cursor:not-allowed; pointer-events:none; opacity:.65 }
/* ...except the download on the progress page, which has something to say when it is pressed too
   early. pointer-events:none means the element never receives the click AT ALL, so the handler
   written to explain the wait could not run and never had: a reader who pressed 「立即下载长图」
   during a ten-second render got no file, no message and no sign the press had registered. The
   handler was there and its comment said 「stop it and say why」; the stylesheet was quietly
   preventing both halves.
   Not a general change: a real <button disabled> cannot be clicked whatever CSS says, and the
   other disabled things on the site have nothing to explain. */
.btn.disabled#gen-dl { pointer-events:auto }
/* The reason, beside the button that gave it. Hidden until there is something to say. */
.dlnote { margin:.5rem 0 0; color:var(--muted); font-size:.92em }
@media (prefers-reduced-motion:reduce) { .progress i { transition:none } .progress.indet i { animation:none; width:100% !important } }

/* the book's own marginalia beside a name */
.facts .rem { background:var(--warn-soft); color:var(--warn); border-radius:var(--radius); padding:.05em .5em }
.annots h2 { margin-top:0 }
.annot-list { list-style:none; padding:0; margin:.4rem 0 0; display:flex; flex-wrap:wrap; gap:.4rem }
.annot-list li { font-family:var(--serif); background:var(--paper); border:1px solid var(--line-2); border-radius:var(--radius); padding:.25rem .7rem }

/* The book's own prose: the 刊词 and the 传记. Set in the serif face and given a wider measure
   than the rest of the page - these run to a hundred characters of unbroken classical Chinese,
   and read as text rather than as a field. The rule down the left marks them as quoted. */
.prose-box .bio { font-family:var(--serif); font-size:1.05rem; line-height:2; text-align:justify;
  margin:.5rem 0; padding-left:.9rem; border-left:3px solid var(--accent-soft) }
.prose-box .src { font-size:.85rem; margin:.3rem 0 0 }
.prose-box h3 { margin:0 0 .2rem } .prose-box h3 .muted { font-weight:400; font-size:.85rem; margin-left:.4rem }
.prose-box .bio-elsewhere { font-size:.9rem; margin:.2rem 0 .4rem }
.foreword .bio { padding-left:0; border-left:0 }   /* the 　　 is in the text itself */

/* 传记 written HERE, as opposed to the book's above. The piece itself is deliberately NOT given a
   style of its own: it is a .bio inside a .prose-box, so it is set in the same face and the same
   measure as the book's prose. On a person's page the two boxes sit one under the other, and type
   that differed would read as one of them counting for less. What is styled here is the apparatus
   around it - the counter, the offer of the book's words, and the admin panel's list. */
.charcount { margin:.25rem 0 0; text-align:right }
.charcount .n { font-variant-numeric:tabular-nums; font-weight:700 }
/* Over the limit. Colour ALONE would say nothing to a reader who cannot see it, so the number is
   also announced - the counter is aria-live - and the browser refuses the form with a sentence. */
.charcount.over { color:var(--bad) }
.charcount.over .n { color:var(--bad) }
.biofrombook { margin:.5rem 0 0; display:flex; gap:.5rem; align-items:center; flex-wrap:wrap }
.biorow .bio { font-family:var(--serif); line-height:1.9; margin:.4rem 0 .6rem; padding-left:.9rem;
  border-left:3px solid var(--accent-soft); white-space:pre-line }
.biorow .inline { display:flex; gap:.6rem; align-items:center; flex-wrap:wrap }

/* 功名: an imperial-examination title printed beside a name */
.honour { display:inline-block; background:var(--accent-soft); color:var(--accent-2); border:1px solid var(--accent-line);
  border-radius:var(--radius); padding:.05em .55em; font-family:var(--serif); font-weight:600 }
.honour.sm { font-size:.8rem; font-weight:500; margin-left:.3rem }
/* the relationship chart: the shared ancestor is the point of the picture, so it is marked */
.relTree { margin-top:.8rem }
.relTree .node.common { border-color:var(--accent); background:var(--accent-soft) }
/* the two lines under the shared ancestor are the answer to the question; the spine above is not */
.relTree .node.onpath { border-color:var(--ink-2); font-weight:600 }
.relTree .node.onpath a { font-weight:700; color:var(--ink) }
.relTree li.onpath > ul { border-left-color:var(--ink-2); border-left-style:solid }
.relTree .node.common > a { font-weight:700 }

/* Privacy: a recent-generation member's name is masked. Rendered in a lighter, letter-spaced grey so it
   reads as "withheld" rather than as a name that happens to be punctuation. */
.masked { color: var(--muted); letter-spacing: .15em; text-decoration: none }
a.masked:hover { color: var(--ink) }
h1.masked { letter-spacing: .2em }
/* The family's access code: readable on demand, not by default. */
.pw { display: inline-flex; gap: .4rem; align-items: center }
.pw input { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .18em; width: 10em }
/* the spacing is for reading a code out; an empty box's （未设置） is words, not a code */
.pw input::placeholder { font-family: var(--font); letter-spacing: normal }

/* 查名 - the name search, offered wherever the genealogy's name is at the top of a page */
.crumbs { display:flex; align-items:baseline; flex-wrap:wrap; gap:.3rem }
.crumbs .findname { margin:0 0 0 1.1rem }
h1 .findname { vertical-align:middle; margin-left:.7rem; position:relative; top:-.15em }
dialog.findbox { width:min(38rem,92vw); border:1px solid var(--line); border-radius:var(--radius);
  background:var(--paper); color:var(--ink); padding:1rem 1.1rem 1.1rem;
  box-shadow:var(--shadow-lg) }
dialog.findbox::backdrop { background:var(--scrim) }
.findbox-h { display:flex; align-items:baseline; gap:.5rem; margin-bottom:.7rem }
.findbox-x { margin:0 0 0 auto; padding:0 .25rem; border:0; background:none; color:var(--muted);
  font-size:1.4rem; line-height:1; cursor:pointer }
.findbox-x:hover { background:none; color:var(--ink) }
.findbox-q { display:flex; gap:.4rem; align-items:center }
.findbox-q input { flex:1; margin:0 }
.findbox-q button { margin:0 }
.findbox .cands { margin:.7rem 0 .3rem }
.findbox .cands li { padding:0 }
.findbox .cands a { display:block; padding:.45rem .7rem; color:inherit }
.findbox .cands a:hover { background:var(--accent-soft); text-decoration:none }
.findbox .cands li.muted { padding:.45rem .7rem }


/* ==================================================================== themes
 *
 * A theme is a block of tokens and nothing else: no selector below this point knows which one is
 * in use. That is the whole reason the chrome was tokenised first - thirty-two colours were
 * written into rule bodies, and every one of them was a place a theme could not reach.
 *
 * The attribute is written by PHP into <html data-theme="..."> before a byte of CSS is fetched
 * (app/Theme.php, app/views/layout.php), so there is no flash of the wrong theme and no JavaScript
 * involved in the first paint.
 *
 * Every block here is measured by tools/theme_contrast_check.py, which resolves each theme the way
 * a browser does and holds it to WCAG AA on thirteen pairs - plus three that are two FOREGROUNDS,
 * because this site carries meaning in the difference between them: a withheld name's *** is
 * --muted where a real name is --ink, and 本人's own line of descent is --ink-2 where everybody
 * else's connectors are --line-2. A palette that pulls its foregrounds together passes an ordinary
 * contrast check and destroys both.
 *
 * WRITE LITERAL COLOURS HERE. A var() or a color-mix() is skipped by the checker rather than
 * failed, so a token defined that way is silently never measured.
 */

/* Classic red ink - 族谱红. The theme this site wore before the Bootstrap one, restored from the stylesheet it was replaced in rather than from memory - so the cream, the vermilion, the 14px corners and the softer shadow are the values that actually shipped. Three are not: --muted, --warn and the two data colours were lightened past AA in the original and are darkened here just far enough to pass tools/theme_contrast_check.py. */
:root[data-theme="classic"] {
  --ink:#1f1d1a; --ink-2:#4a4640; --muted:#6e6960; --line:#e6e1d8; --line-2:#d3cdc2; --bg:#f7f4ee;
  --paper:#fffdf9; --field:#ffffff; --accent:#a3261f; --accent-2:#7d1a15; --accent-soft:#fbeeec;
  --accent-line:#f0c9c5; --on-accent:#ffffff; --ok:#2a7247; --ok-soft:#e6f4ea; --ok-line:#cfe8d6;
  --ok-solid:#2f7d4f; --warn:#7a5500; --warn-soft:#fff6d9; --warn-line:#eddba4; --warn-solid:#9a6b00;
  --bad:#b3261e; --bad-soft:#fdecea; --bad-line:#f3c9c4; --bad-solid:#b3261e; --info:#255e55;
  --info-soft:#e6f0ed; --info-line:#cfe2dc; --gray-100:#fbfaf6; --gray-200:#f0ece4; --gray-300:#ece7de;
  --code-ink:#9c2a5a; --nav-ink:#4a4640; --nav-toggle-ink:#4a4640; --nav-toggle-line:#d3cdc2;
  --row-hover:#faf7f1; --paper-veil:rgba(255,253,249,.97); --scrim:rgba(31,29,26,.38);
  --data-f:#7d1a15; --data-u:#b3a893; --mark-f:#a8005c; --focus:rgba(163,38,31,.22);
  --focus-line:#a3261f; --focus-btn:rgba(163,38,31,.4); --radius:14px;
  --shadow:0 1px 2px rgba(31,29,26,.05), 0 6px 20px -8px rgba(31,29,26,.14);
  --shadow-lg:0 24px 70px -24px rgba(31,29,26,.55);
  --shadow-hover:0 2px 4px rgba(31,29,26,.06), 0 14px 30px -10px rgba(31,29,26,.22);
  --font:"Inter","Noto Sans SC","PingFang SC","Microsoft YaHei",system-ui,-apple-system,sans-serif;
  --scheme:light;
}

/* Dark - 深色.  */
:root[data-theme="dark"] {
  --bg:#212529; --paper:#2b3035; --ink:#dee2e6; --ink-2:#c3c7cb; --muted:#b1b6ba; --line:#495057;
  --line-2:#9b9fa4; --gray-100:#212529; --gray-200:#343a40; --gray-300:#495057; --accent:#6ea8fe;
  --accent-2:#8bb9fe; --accent-soft:#031633; --accent-line:#0d6efd; --on-accent:#000000;
  --btn-fg:#000000; --ok:#75b798; --ok-soft:#051b11; --ok-line:#198754; --ok-solid:#75b798;
  --warn:#ffda6a; --warn-soft:#332701; --warn-line:#997404; --bad:#ea868f; --bad-soft:#2c0b0e;
  --bad-line:#dc3545; --bad-solid:#ea868f; --info:#6edff6; --info-soft:#032830; --info-line:#087990;
  --focus:rgba(110,168,254,.4); --focus-line:#86b7fe; --focus-btn:rgba(110,168,254,.5);
  --nav-ink:rgba(255,255,255,.65); --nav-toggle-line:rgba(255,255,255,.15);
  --nav-toggle-ink:rgba(255,255,255,.55); --code-ink:#e685b5; --mark-f:#e685b5;
  --row-hover:rgba(255,255,255,.075); --paper-veil:rgba(43,48,53,.97); --data-f:#20c997;
  --data-u:#adb5bd; --shadow:0 .125rem .25rem rgba(0,0,0,.45);
  --shadow-hover:0 .5rem 1rem rgba(0,0,0,.6); --shadow-lg:0 1rem 3rem rgba(0,0,0,.75); --field:#212529;
  --scrim:rgba(0,0,0,.7); --scheme:dark;
}

/* High contrast - 高对比度.  */
:root[data-theme="contrast"] {
  --ink:#14171a; --ink-2:#2c2e31; --muted:#373a3c; --bg:#ffffff; --line:#6b7176; --line-2:#494b4e;
  --accent:#0a3f9e; --accent-2:#06306f; --accent-soft:#d0e2fb; --accent-line:#2f66b5;
  --focus:rgba(10,63,158,.75); --focus-line:#06306f; --focus-btn:rgba(20,23,26,.7); --ok:#0a3d26;
  --ok-soft:#d4ecdd; --ok-line:#24694a; --ok-solid:#146c43; --warn:#4d3a00; --warn-soft:#fbeec2;
  --warn-line:#7d6000; --warn-solid:#7d6000; --bad:#7a1420; --bad-soft:#fadcdf; --bad-line:#9b3440;
  --bad-solid:#a4121f; --info:#04414c; --info-soft:#d3f1f7; --info-line:#256f7e; --gray-100:#f1f3f4;
  --gray-200:#e6e9eb; --gray-300:#cfd4d8; --row-hover:rgba(0,0,0,.09); --nav-ink:#22272b;
  --nav-toggle-line:#3f4549; --nav-toggle-ink:#22272b; --code-ink:#85114f; --mark-f:#85114f;
  --data-f:#10a686; --data-u:#2c3136; --paper-veil:#ffffff; --shadow:0 .125rem .25rem rgba(0,0,0,.28);
  --shadow-hover:0 .5rem 1rem rgba(0,0,0,.35); --shadow-lg:0 1rem 3rem rgba(0,0,0,.45);
  --paper:#ffffff; --field:#ffffff; --on-accent:#ffffff; --scrim:rgba(0,0,0,.6); --scheme:light;
}

/* Celadon - 青瓷.  */
:root[data-theme="celadon"] {
  --accent:#11604e; --accent-2:#0a4739; --accent-soft:#cde5da; --accent-line:#a7cebf; --ink:#17241f;
  --ink-2:#39504a; --muted:#4c5d56; --line:#c7d4cc; --line-2:#adbfb5; --bg:#e9efeb; --paper:#fbfcfb;
  --ok:#2a5716; --ok-soft:#e3f0d0; --ok-line:#c6e0a6; --ok-solid:#4a8f23; --bad-solid:#b02a37;
  --info:#134c8a; --info-soft:#dbe8f6; --info-line:#a9c8e6; --focus:rgba(17,96,78,.25);
  --focus-line:#3f9277; --focus-btn:rgba(28,124,101,.5); --gray-100:#f1f5f2; --gray-200:#dde6e0;
  --gray-300:#cfdbd4; --code-ink:#92305f; --mark-f:#a63268; --nav-ink:#42534c;
  --nav-toggle-line:#b6c5bd; --nav-toggle-ink:#57675f; --row-hover:rgba(18,58,47,.055);
  --shadow:0 .125rem .25rem rgba(13,41,33,.10); --shadow-hover:0 .5rem 1rem rgba(13,41,33,.17);
  --shadow-lg:0 1rem 3rem rgba(13,41,33,.22); --paper-veil:rgba(251,252,251,.96); --data-f:#c9698f;
  --data-u:#aab8b0; --field:#ffffff; --on-accent:#ffffff; --scrim:rgba(18,42,34,.5); --scheme:light;
}

/* Solarized Light - 日光浅色. Ethan Schoonover's Solarized (2011), light mode, from the canonical sixteen: base03 #002b36, base02 #073642, base01 #586e75, base00 #657b83, base0 #839496, base1 #93a1a1, base2 #eee8d5, base3 #fdf6e3, yellow #b58900, orange #cb4b16, red #dc322f, magenta #d33682, violet #6c71c4, blue #268bd2, cyan #2aa198, green #859900. Used as published: base3 is the page (--bg) and the form field (--field) and t */
:root[data-theme="solarized-light"] {
  --bg:#fdf6e3; --paper:#f6efdc; --field:#fdf6e3; --gray-100:#f2ebd8; --gray-200:#eee8d5;
  --gray-300:#dedbcc; --ink:#073642; --ink-2:#34555e; --muted:#586e75; --on-accent:#fdf6e3;
  --code-ink:#b33678; --line:#d3d3c5; --line-2:#93a1a1; --accent-line:#a4ceec; --accent:#1b6b9d;
  --accent-2:#125577; --accent-soft:#d7eaf7; --ok:#4c6c1e; --ok-soft:#e0e5bd; --ok-line:#c5cf86;
  --ok-solid:#859900; --warn:#6d661b; --warn-soft:#f1e4bc; --warn-line:#dec882; --warn-solid:#b58900;
  --bad:#b63332; --bad-soft:#f9dbda; --bad-line:#f3bbb9; --bad-solid:#c83231; --info:#1a7070;
  --info-soft:#ceeae7; --info-line:#9bd3ce; --nav-ink:#586e75; --nav-toggle-ink:#34555e;
  --nav-toggle-line:#93a1a1; --row-hover:rgba(7,54,66,.07); --paper-veil:rgba(253,246,227,.97);
  --scrim:rgba(0,43,54,.55); --focus:rgba(38,139,210,.35); --focus-line:#268bd2;
  --focus-btn:rgba(38,139,210,.45); --data-f:#d33682; --data-u:#93a1a1; --mark-f:#b33678;
  --shadow:0 .125rem .25rem rgba(0,43,54,.10); --shadow-lg:0 1rem 3rem rgba(0,43,54,.22);
  --shadow-hover:0 .5rem 1rem rgba(0,43,54,.17); --scheme:light;
}

/* Solarized Dark - 日晒暗色. Ethan Schoonover's Solarized (2011), the sixteen fixed swatches: the eight-step neutral ramp base03 #002b36, base02 #073642, base01 #586e75, base00 #657b83, base0 #839496, base1 #93a1a1, base2 #eee8d5, base3 #fdf6e3, plus yellow #b58900, orange #cb4b16, red #dc322f, magenta #d33682, violet #6c71c4, blue #268bd2, cyan #2aa198, green #859900. Used here: base03 is --bg and, inverted, --on-accent (the */
:root[data-theme="solarized-dark"] {
  --bg:#002b36; --paper:#073642; --field:#00212b; --gray-100:#052f39; --gray-200:#04262e;
  --gray-300:#031e25; --ink:#eee8d5; --ink-2:#b7bdb6; --muted:#93a1a1; --on-accent:#002b36;
  --code-ink:#da6297; --line:#2b4f59; --line-2:#586e75; --accent-line:#1d71a7; --accent:#52a5e6;
  --accent-2:#8ac6f2; --accent-soft:#0b4058; --ok:#859900; --ok-soft:#0a2313; --ok-line:#4e6409;
  --ok-solid:#859900; --warn:#b58900; --warn-soft:#241b04; --warn-line:#745802; --warn-solid:#b58900;
  --bad:#f0736a; --bad-soft:#2a1011; --bad-line:#974642; --bad-solid:#f0736a; --info:#2aa198;
  --info-soft:#032025; --info-line:#186764; --nav-ink:#93a1a1; --nav-toggle-ink:#b7bdb6;
  --nav-toggle-line:#586e75; --row-hover:rgba(0,0,0,.25); --paper-veil:rgba(7,54,66,.97);
  --scrim:rgba(0,10,13,.78); --focus:rgba(82,165,230,.35); --focus-line:#8ac6f2;
  --focus-btn:rgba(82,165,230,.5); --data-f:#d06333; --data-u:#586e75; --mark-f:#df86a7;
  --shadow:0 .125rem .35rem rgba(0,0,0,.5), 0 0 0 1px rgba(147,161,161,.07);
  --shadow-lg:0 1rem 3rem rgba(0,0,0,.72), 0 0 0 1px rgba(147,161,161,.09);
  --shadow-hover:0 .5rem 1rem rgba(0,0,0,.55), 0 0 0 1px rgba(147,161,161,.11); --scheme:dark;
}

/* Nord - 北欧极夜. Nord by Arctic Ice Studio (Sven Greb), 2016 — sixteen swatches in four named groups. Polar Night (nord0 #2e3440, nord1 #3b4252, nord2 #434c5e, nord3 #4c566a) carries the surfaces and the lines: nord0 is the page and the inset form field, nord1 the card, nord2 the card/table hairline, nord3 the form border and the chart's connector lines. Snow Storm supplies the text: nord4 #d8dee9 is --ink, Nord's */
:root[data-theme="nord"] {
  --bg:#2e3440; --paper:#3b4252; --field:#2e3440; --gray-100:#2e3440; --gray-200:#272c36;
  --gray-300:#21252d; --ink:#d8dee9; --ink-2:#9eb5ce; --muted:#aab1bf; --on-accent:#2e3440;
  --code-ink:#b48ead; --line:#434c5e; --line-2:#4c566a; --accent-line:#5e81ac; --accent:#88c0d0;
  --accent-2:#a1ccd9; --accent-soft:#1c2539; --ok:#a3be8c; --ok-soft:#222b1a; --ok-line:#5c6f4f;
  --ok-solid:#a3be8c; --warn:#ebcb8b; --warn-soft:#302717; --warn-line:#806d4a; --warn-solid:#ebcb8b;
  --bad:#d6a8af; --bad-soft:#342226; --bad-line:#bf616a; --bad-solid:#e5a0a6; --info:#8fbcbb;
  --info-soft:#192b2e; --info-line:#4c6a6b; --nav-ink:#c7ceda; --nav-toggle-ink:#d8dee9;
  --nav-toggle-line:#81a1c1; --row-hover:rgba(0,0,0,.22); --paper-veil:rgba(59,66,82,.97);
  --scrim:rgba(9,11,15,.72); --focus:rgba(136,192,208,.35); --focus-line:#88c0d0;
  --focus-btn:rgba(136,192,208,.45); --data-f:#d08770; --data-u:#4c566a; --mark-f:#c4a9c1;
  --shadow:0 .125rem .35rem rgba(0,0,0,.55); --shadow-lg:0 1rem 3rem rgba(0,0,0,.8);
  --shadow-hover:0 .5rem 1.25rem rgba(0,0,0,.6); --scheme:dark;
}

/* Gruvbox Dark - Gruvbox 复古暗色. gruvbox, by Pavel Pertsev (morhetz), dark variant — the retro-groove palette published with the vim colourscheme. Swatches used by their published names: bg0_hard #1d2021 → --bg and --on-accent; bg0 #282828 → --paper and --paper-veil (the signature gruvbox surface, and the one most of this site's text actually sits on); bg0_soft #32302f → --gray-100; bg1 #3c3836 → --gray-200 and --line; bg2 #50494 */
:root[data-theme="gruvbox-dark"] {
  --bg:#1d2021; --paper:#282828; --field:#504945; --gray-100:#32302f; --gray-200:#3c3836;
  --gray-300:#504945; --ink:#ebdbb2; --ink-2:#bdae93; --muted:#a89984; --on-accent:#1d2021;
  --code-ink:#fe8019; --line:#3c3836; --line-2:#665c54; --accent-line:#458588; --accent:#83a598;
  --accent-2:#8ec07c; --accent-soft:#2e3c3d; --ok:#b8bb26; --ok-soft:#444424; --ok-line:#98971a;
  --ok-solid:#b8bb26; --warn:#fabd2f; --warn-soft:#594826; --warn-line:#d79921; --warn-solid:#fabd2f;
  --bad:#fc705f; --bad-soft:#4c2726; --bad-line:#fb4934; --bad-solid:#fc705f; --info:#8ec07c;
  --info-soft:#384538; --info-line:#689d6a; --nav-ink:#bdae93; --nav-toggle-ink:#d5c4a1;
  --nav-toggle-line:#928374; --row-hover:rgba(235,219,178,.08); --paper-veil:rgba(40,40,40,.97);
  --scrim:rgba(0,0,0,.72); --focus:rgba(131,165,152,.40); --focus-line:#83a598;
  --focus-btn:rgba(131,165,152,.55); --data-f:#fabd2f; --data-u:#7c6f64; --mark-f:#d3869b;
  --shadow:0 .25rem .75rem rgba(0,0,0,.6); --shadow-lg:0 1rem 3rem rgba(0,0,0,.85);
  --shadow-hover:0 .5rem 1.5rem rgba(0,0,0,.7); --scheme:dark;
}

/* Dracula - 德古拉. Dracula (draculatheme.com). The twelve named swatches are all used and all recognisable: Background #282a36 is the card surface (--paper) and the label on a filled button (--on-accent); Current Line / Selection #44475a is the hairline (--line) and the heaviest quiet fill (--gray-300); Foreground #f8f8f2 is body text (--ink); Comment #6272a4 is the structural colour it already is in the editor - ch */
:root[data-theme="dracula"] {
  --bg:#21222c; --paper:#282a36; --field:#191a21; --gray-100:#2e303d; --gray-200:#333644;
  --gray-300:#44475a; --ink:#f8f8f2; --ink-2:#b4bccf; --muted:#9aa4c1; --on-accent:#282a36;
  --code-ink:#ff79c6; --line:#44475a; --line-2:#6272a4; --accent-line:#8265c9; --accent:#bd93f9;
  --accent-2:#ff79c6; --accent-soft:#3c2f5e; --ok:#50fa7b; --ok-soft:#1c3b2a; --ok-line:#377a55;
  --ok-solid:#50fa7b; --warn:#ffb86c; --warn-soft:#40321c; --warn-line:#8a6a36; --warn-solid:#f1fa8c;
  --bad:#ff6e6e; --bad-soft:#452630; --bad-line:#9a4450; --bad-solid:#ff5555; --info:#8be9fd;
  --info-soft:#17323d; --info-line:#356b80; --nav-ink:#c4c9d7; --nav-toggle-ink:#dadde2;
  --nav-toggle-line:#7d8ab2; --row-hover:rgba(248,248,242,.07); --paper-veil:rgba(40,42,54,.97);
  --scrim:rgba(6,6,11,.78); --focus:rgba(189,147,249,.4); --focus-line:#d6acff;
  --focus-btn:rgba(189,147,249,.5); --data-f:#8be9fd; --data-u:#6272a4; --mark-f:#ff79c6;
  --shadow:0 .125rem .5rem rgba(0,0,0,.55), 0 0 0 1px rgba(248,248,242,.04);
  --shadow-lg:0 1rem 3rem rgba(0,0,0,.72), 0 0 0 1px rgba(248,248,242,.06);
  --shadow-hover:0 .5rem 1.25rem rgba(0,0,0,.65), 0 0 0 1px rgba(248,248,242,.07); --scheme:dark;
}

/* Catppuccin Mocha - 卡布奇诺猫·摩卡. Catppuccin Mocha, the darkest of the four Catppuccin flavours (github.com/catppuccin/catppuccin), taken from the brief's swatch list, which is the palette's own published hex. Surfaces use the flavour's elevation ladder in its documented order: crust #11111b < mantle #181825 < base #1e1e2e < surface0 #313244 < surface1 #45475a. The page is mantle, a card is base, and everything that is a well cut  */
:root[data-theme="catppuccin-mocha"] {
  --bg:#181825; --paper:#1e1e2e; --field:#11111b; --gray-100:#181825; --gray-200:#11111b;
  --gray-300:#45475a; --ink:#cdd6f4; --ink-2:#a6adc8; --muted:#9399b2; --on-accent:#11111b;
  --code-ink:#cba6f7; --line:#45475a; --line-2:#6c7086; --accent:#89b4fa; --accent-2:#b4befe;
  --accent-soft:#272e43; --ok:#a6e3a1; --ok-soft:#2f3b36; --ok-line:#547057; --ok-solid:#a6e3a1;
  --warn:#f9e2af; --warn-soft:#49433f; --warn-line:#796f5e; --warn-solid:#f9e2af; --bad:#f38ba8;
  --bad-soft:#3e2937; --bad-line:#77485a; --bad-solid:#f38ba8; --info:#74c7ec; --info-soft:#253545;
  --info-line:#3e6379; --nav-ink:#a6adc8; --nav-toggle-ink:#a6adc8; --nav-toggle-line:#6c7086;
  --row-hover:rgba(205,214,244,.07); --paper-veil:rgba(30,30,46,.97); --scrim:rgba(17,17,27,.78);
  --focus:rgba(137,180,250,.35); --focus-line:#89b4fa; --focus-btn:rgba(180,190,254,.5);
  --data-f:#fab387; --data-u:#7f849c; --mark-f:#f5c2e7;
  --shadow:0 .125rem .5rem rgba(0,0,0,.6), 0 0 0 1px rgba(205,214,244,.05);
  --shadow-lg:0 1rem 3rem rgba(0,0,0,.85), 0 0 0 1px rgba(205,214,244,.08);
  --shadow-hover:0 .5rem 1.25rem rgba(0,0,0,.7), 0 0 0 1px rgba(137,180,250,.25); --scheme:dark;
}

/* ---- 变更记录 -------------------------------------------------------------------------------
   The log of what has been done to a genealogy, on the admin's own page and in a short form on
   each person's. Set as a list of small records rather than a table: a change is a sentence with
   a diff under it, and the fields it touched vary from one row to the next. */
.histlist { list-style:none; margin:.4rem 0 0; padding:0 }
.histlist > li { border-top:1px solid var(--line); padding:.5rem 0 }
.histlist > li:first-child { border-top:0 }
/* An undone change stays on the page - the record reads forwards and nothing is deleted - but it
   is no longer what the genealogy says, so it is set back. */
.histlist > li.undone { opacity:.62 }
.histhead { display:flex; flex-wrap:wrap; gap:.4rem; align-items:baseline }
.histdiff { list-style:none; margin:.3rem 0 0 .2rem; padding:0; font-size:.9rem }
.histdiff li { display:flex; flex-wrap:wrap; gap:.35rem; align-items:baseline; padding:.1rem 0 }
.histdiff .hf { color:var(--muted); min-width:4.5rem }
/* The old value struck through and the new one plain: which way the change went should be legible
   without reading the arrow, and the arrow is there for when it is not. */
.histdiff .hfrom { text-decoration:line-through; color:var(--muted) }
.histdiff .harrow { color:var(--muted) }
.histdiff .hto { color:var(--ink) }
.histreason { margin:.25rem 0 0 }
.pager { margin:1rem 0 } .pager a, .pager b { padding:0 .35rem }

/* ---- 原书扫描页 ------------------------------------------------------------------------------
   One page of the scanned book, for checking a record against. The image IS the page, so it gets
   the width and the chrome stays out of its way. */
.scanbar { display:flex; flex-wrap:wrap; gap:.6rem; align-items:baseline;
  justify-content:space-between; margin:0 0 .6rem }
.scannav { display:flex; gap:.4rem; align-items:center; flex-wrap:wrap }
/* 跳至 N 前往. The label and the box read as one control, so they sit on one line with the arrows
   rather than below them; the input is only as wide as three digits need, because 166 is the
   largest thing anybody will type and a full-width box in a toolbar looks like a search. */
.scango { display:flex; align-items:center; gap:.35rem; margin:0 }
.scango label { margin:0; font-size:.85rem; color:var(--muted); font-weight:400 }
.scango input { width:4.5rem; margin:0; padding:.2rem .4rem; font-size:.85rem }
/* A box shadow rather than a border: the scan has its own paper edge, and a second line around it
   reads as a second sheet. max-width keeps a 1258px render from being blown up on a wide screen,
   where it would only be softer. */
.scanimg { display:block; width:100%; max-width:1000px; height:auto; margin:0 auto;
  background:#fff; box-shadow:0 1px 6px rgba(0,0,0,.18) }

/* 村落: two searches side by side on a wide screen and one above the other on a phone; each place a
   line of its own, its name, what it is and where it sits, and the surnames recorded for it as
   small chips that are themselves a 姓氏查村 search. */
.vsearch { display:grid; grid-template-columns:repeat(auto-fit, minmax(16rem, 1fr)); gap:0 1rem }
.vsearch .box, form.xingadd { margin:.5rem 0 }
.vsearch label, form.xingadd label { margin:0 0 .35rem }
.vrow { display:flex; gap:.5rem; margin-top:.4rem } .vrow input { flex:1; min-width:0; margin-top:0 } .vrow button { flex:none }
/* 台山村落's pages: the two searches as buttons at the top, each opening its form in a dialog
   (_village_search.php, the user asked, 2026-10-03). html.vs-js is set by a line of script before them,
   only where a dialog can be opened; without it the buttons are not shown and the dialogs are laid out
   where they stand, as the two boxes, so nothing is lost without script. */
.vsbtns { display:none; flex-wrap:wrap; gap:.5rem; margin:.4rem 0 .8rem }
html.vs-js .vsbtns { display:flex }
.vsbtns .vsterm { font-weight:600; margin-left:.25rem }
html:not(.vs-js) .vsdialog { display:block; position:static; margin:0; padding:0; border:0; background:none;
  color:inherit; width:auto; max-width:none }
html:not(.vs-js) .vsclose { display:none }
html.vs-js .vsdialogs { display:contents }
.vsdialog[open] { width:min(34rem, calc(100% - 2rem)); padding:0; border:0; border-radius:var(--radius); background:transparent;
  color:var(--ink); overflow:visible }
.vsdialog[open] .box { margin:0 }   /* the × beside its label's line, clear of the box under it */
.vsdialog::backdrop { background:rgba(20,16,12,.45) }
.vsclose { position:absolute; top:.35rem; right:.35rem; width:2.2rem; height:2.2rem; padding:0; border:0;
  background:none; color:var(--muted); font-size:1.5rem; line-height:1; cursor:pointer }
.vsclose:hover { color:var(--accent) }
@media (pointer:coarse) { .vsclose { width:2.75rem; height:2.75rem } }
ul.places { list-style:none; margin:.5rem 0 1rem; padding:0; border-top:1px solid var(--line) }
ul.places li { padding:.5rem .25rem; border-bottom:1px solid var(--line); display:flex; flex-wrap:wrap; align-items:baseline; gap:.2rem .6rem }
ul.places li > a b { color:var(--accent); font-weight:600 }
ul.places.towns { display:grid; grid-template-columns:repeat(auto-fill, minmax(12rem, 1fr)); border-top:0; gap:0 1rem }
.xings { display:inline-flex; flex-wrap:wrap; gap:.3rem }
.xing { display:inline-block; min-width:1.9em; text-align:center; padding:.05rem .45rem; border:1px solid var(--line-2);
  border-radius:var(--radius); font-family:var(--serif); color:var(--ink); background:var(--paper); text-decoration:none }
a.xing:hover { border-color:var(--accent); color:var(--accent) }
ul.xinglist { list-style:none; margin:.25rem 0 .75rem; padding:0 }
ul.xinglist li { display:flex; flex-wrap:wrap; align-items:baseline; gap:.2rem .6rem; padding:.3rem 0 }
ul.xinglist a.xing { font-size:1.15rem }
/* the review queue: each entry's approve / reject at the end of its line */
ul.places.review .verdict { margin-left:auto; display:inline-flex; gap:.35rem }
/* 村落审核记录: an entry that has been undone stays in the record, quietened */
ul.places.vlog li.undone > :not(.verdict) { opacity:.55 }
ul.places.vlog .vlog-act { font-weight:600 }
ul.places.vlog li:target { background:var(--row-hover) }
/* a surname in every form - 赵 趙 Zhào · Chiu, Chu, Jew · 粤拼 Ziu⁶ - where it heads a section or
   stands in a village's list */
.sforms { display:inline-flex; flex-wrap:wrap; align-items:baseline; gap:.1rem .45rem; font-weight:400 }
.sforms .strad { font-family:var(--serif); font-size:1.1em }
.sforms .spy { font-style:italic } .sforms .sroms, .sforms .sjp { font-size:.9em }
/* a place's name in pinyin beside its characters, on the English pages: 田稠村 Tianchou Cun */
.ppy { font-weight:400; font-style:italic }
/* 台山村落's 姓氏索引: the letters to jump to, then each letter's surnames in columns - the surname, its
   traditional form, its villages (+ those only the 1963 index has), and its spellings, those that put it
   under this letter in bold. Each kept whole in its column. */
.azbar { display:flex; flex-wrap:wrap; gap:.3rem; margin:.4rem 0 .6rem }
.azbar a { min-width:2rem; text-align:center; padding:.15rem .3rem; border:1px solid var(--line-2); border-radius:var(--radius);
  text-decoration:none; font-weight:600 }
.azbar a:hover { border-color:var(--accent) }
.azhead { margin:1.1rem 0 .35rem; padding-bottom:.15rem; border-bottom:1px solid var(--line);
  scroll-margin-top:calc(var(--headerh) + .75rem) }
ul.xingaz { list-style:none; margin:0; padding:0; columns:15rem; column-gap:2rem }
ul.xingaz li { break-inside:avoid; padding:.2rem 0; display:flex; flex-wrap:wrap; align-items:baseline; gap:.1rem .4rem }
ul.xingaz .strad { font-family:var(--serif); font-size:1.05em }
ul.xingaz .xn { font-variant-numeric:tabular-nums }
ul.xingaz .xsp { font-size:.85rem; flex-basis:100% } ul.xingaz .xsp b { color:var(--ink); font-weight:600 }
section.xinggroup { margin:1.25rem 0; padding-top:.25rem; border-top:2px solid var(--line) }
section.xinggroup h2 { margin:.4rem 0 }
ul.places.vindex li > b { color:var(--ink) }
/* a 1963 entry's guess at today's place, on a line of its own under the entry; on a place's page,
   the guesses at its old name under the entry it was matched to, if any */
.vguess { flex-basis:100% } ul.places.vindex .vguess a { font-weight:600 }
/* a place's genealogies (place_zupu, Places::genealogyMap), on a line of their own in its list item and
   in its map popup: 「族谱：田稠赵氏族谱」 */
.vzupu { flex-basis:100% } .vzupu a { color:var(--accent); font-weight:600 } .vmap-item .vzupu { margin:.2rem 0 }
.vguesshead { margin:.6rem 0 .1rem } .vindexbox .vguesshead:first-child { margin-top:0 }
/* the box a visitor never sees and only something filling in every field fills: off the page, not
   display:none, which some of them skip */
.hp { position:absolute; left:-10000px; top:auto; width:1px; height:1px; overflow:hidden }
/* a place's page: 添加一个姓氏 folded under the list, open only where there is no surname yet; the
   summary as wide as its words, so a click beside it does not open the form */
details.xingadd { margin:.75rem 0 0 } details.xingadd > summary { width:max-content; max-width:100% }
/* a place's page: the same spot on three maps */
.maplinks { margin:.25rem 0 .5rem }
/* 列表 | 地图 over a list of results, and the map (www/assets/villagemap.js, drawn by tzmap.js). The
   canvas is its own stacking context, so nothing in a map can ever draw over the sticky header. */
.vmapbar { display:inline-flex; margin:.25rem 0 .6rem }
.vmapbar[hidden] { display:none }
.vmapbar button { background:var(--paper); color:var(--accent); border-color:var(--accent); padding:.2rem .9rem; border-radius:0 }
.vmapbar button:first-child { border-radius:var(--radius) 0 0 var(--radius) }
.vmapbar button:last-child { border-radius:0 var(--radius) var(--radius) 0; margin-left:-1px }
.vmapbar button[aria-pressed="true"] { background:var(--accent); color:var(--on-accent) }
.vmap-canvas { height:min(70vh, 34rem); min-height:18rem; border:1px solid var(--line); border-radius:var(--radius);
  isolation:isolate; overflow:hidden; position:relative }
/* 台山村落's results open on the map, and from the first paint: html.vmap-first, set by a line of script in
   villages.php before the results, shows each map's box - the size the map will be, saying 正在载入地图… -
   with its buttons, and not the list, until villagemap.js marks the results .vready and takes over with
   their hidden attributes. A reader who chose the list never has the class: theirs opens as before.
   !important to outrank the site's own [hidden] { display:none !important }, more specifically. */
html.vmap-first .vresults:not(.vready) ul.places { display:none }
html.vmap-first .vresults:not(.vready) .vmap[hidden] { display:block !important }
html.vmap-first .vresults:not(.vready) .vmapbar[hidden] { display:inline-flex !important }
.vmap-loading { position:absolute; inset:0; margin:0; display:flex; align-items:center; justify-content:center;
  color:var(--muted); background:var(--paper) }
.vmap-pop { max-height:16rem; overflow:auto }
.vmap-item + .vmap-item { border-top:1px solid var(--line); margin-top:.45rem; padding-top:.45rem }
.vmap-item .xings { margin:.25rem 0 }
.vmap-open { margin-top:.15rem }
/* a 1963 village in its heung's popup, on the map of a list of them */
.vmap-old .xing { font-size:.85rem; padding:0 .35rem }

/* 中国历史年表图 (app/Chronology.php): years down the page, the dynasties in their columns - the line
   of the central plain in the accent's pale, what stood beside it in grey - and a genealogy's
   forebears as bars to the right, a faint line across to the dynasties at each one's first year.
   Every colour is the theme's, so a dark theme draws it in its own. Wider than its box, it scrolls
   sideways inside it rather than the page. On a phone a narrower drawing of it shows instead
   (Chronology::svg's $narrow - on an iPhone the forebears were off the screen to the right), as wide
   as the screen and never wider than drawn, its height following; never below three quarters of
   it, scrolling in its box past that, as the other does. */
.chrono-scroll { overflow-x:auto; margin:.75rem 0; -webkit-overflow-scrolling:touch }
.chrono-phone { display:none }
svg.chrono-narrow { width:100%; height:auto }
@media (max-width:640px) {
  .chrono-wide { display:none }
  .chrono-phone { display:block }
}
svg.chrono { display:block; font-family:var(--font) }
svg.chrono text { font-size:12px; fill:var(--ink) }
svg.chrono .chr-year { font-size:11px; fill:var(--muted) }
svg.chrono .chr-tick { stroke:var(--line); stroke-dasharray:2 4 }
/* no ring but on hover, and then 2px, half of it in the 2px gap Chronology::svg leaves between bars
   - so the next bar, drawn after this one, cannot cover it */
svg.chrono .chr-reg rect { stroke:none; stroke-width:2 }
/* each 朝代 its own colour: twelve round the wheel, handed out by Chronology::hue so two that meet
   on the chart are far apart - soft, so its name reads on it in dark ink whatever the theme; the
   swatches in the table under the chart are the key */
svg.chrono .chr-h0 rect { fill:hsl(0 62% 83%) } .chr-sw.chr-h0 { background:hsl(0 62% 83%) }
svg.chrono .chr-h1 rect { fill:hsl(30 62% 83%) } .chr-sw.chr-h1 { background:hsl(30 62% 83%) }
svg.chrono .chr-h2 rect { fill:hsl(60 62% 83%) } .chr-sw.chr-h2 { background:hsl(60 62% 83%) }
svg.chrono .chr-h3 rect { fill:hsl(90 62% 83%) } .chr-sw.chr-h3 { background:hsl(90 62% 83%) }
svg.chrono .chr-h4 rect { fill:hsl(120 62% 83%) } .chr-sw.chr-h4 { background:hsl(120 62% 83%) }
svg.chrono .chr-h5 rect { fill:hsl(150 62% 83%) } .chr-sw.chr-h5 { background:hsl(150 62% 83%) }
svg.chrono .chr-h6 rect { fill:hsl(180 62% 83%) } .chr-sw.chr-h6 { background:hsl(180 62% 83%) }
svg.chrono .chr-h7 rect { fill:hsl(210 62% 83%) } .chr-sw.chr-h7 { background:hsl(210 62% 83%) }
svg.chrono .chr-h8 rect { fill:hsl(240 62% 83%) } .chr-sw.chr-h8 { background:hsl(240 62% 83%) }
svg.chrono .chr-h9 rect { fill:hsl(270 62% 83%) } .chr-sw.chr-h9 { background:hsl(270 62% 83%) }
svg.chrono .chr-h10 rect { fill:hsl(300 62% 83%) } .chr-sw.chr-h10 { background:hsl(300 62% 83%) }
svg.chrono .chr-h11 rect { fill:hsl(330 62% 83%) } .chr-sw.chr-h11 { background:hsl(330 62% 83%) }
svg.chrono .chr-reg text { fill:#1f1d1a }
/* the tag of a bar too short to hold its name (Chronology::tags): its colour, a hairline round it
   so a pale one shows on the page, and a line to the bar; ringed with the bar when pointed at */
svg.chrono .chr-reg rect.chr-tag { stroke:rgba(0,0,0,.22); stroke-width:1 }
svg.chrono .chr-reg:hover rect.chr-tag { stroke:var(--accent); stroke-width:2 }
svg.chrono .chr-lead { stroke:var(--muted); stroke-width:1 }
table.chrono-list td.chr-len { white-space:nowrap }
.chr-sw { display:inline-block; width:.8em; height:.8em; margin-right:.45em; border-radius:2px; vertical-align:-.05em }
svg.chrono .chr-reg:hover rect { stroke:var(--accent) }
svg.chrono .chr-man rect, svg.chrono .chr-man circle { fill:var(--accent) }
svg.chrono .chr-guide { stroke:var(--accent); stroke-opacity:.45; stroke-dasharray:3 3 }
svg.chrono .chr-man text { font-weight:600 }
/* a forebear whose year is estimated (Chronology::estimate): a hollow dashed ring, a fainter line,
   a lighter label - a guess drawn as one */
svg.chrono .chr-est circle { fill:var(--paper); stroke:var(--accent); stroke-width:1.5; stroke-dasharray:2 2 }
svg.chrono .chr-est .chr-guide { stroke-opacity:.25 }
svg.chrono .chr-est text { font-weight:400; fill:var(--ink-2) }
table.chrono-list tr.beside td:first-child { padding-left:1.4rem; color:var(--ink-2) }
/* a man's 世系 against the chart: his year above it, and the table in two parts - the 族谱溯源's
   and the book's, each with its own numbering - with him last, in bold */
.pchrono-year { display:flex; flex-wrap:wrap; align-items:center; gap:.4rem .6rem; margin:.75rem 0 }
.pchrono-year input { width:7em }
.pchrono-year small { flex-basis:100% }
table.chrono-list tr.grp th { text-align:left; background:var(--gray-100); padding-top:.5rem }
table.chrono-list tr.self td { font-weight:600 }
/* the tables on a phone: a forebear's name kept whole, and in Chinese his years too (they broke at
   the dash, 1104— over 187) with 推算 under them, not beside - beside, 「约800 推算」 left his place a
   character a line; the place at least three characters wide, the dynasties wrapping round them.
   English years left to break, or 「c. 1125 (estimated)」 kept its tables wider than the screen; and
   a length free to break, or 「77 years so far」 kept the English list of dynasties wider than it */
@media (max-width:640px) {
  table.chrono-list td.chr-who, html:not([lang=en]) table.chrono-list td.chr-when { white-space:nowrap }
  html:not([lang=en]) table.chrono-list td.chr-when .tag { display:block; width:max-content; margin-top:.15rem }
  table.chrono-list td.chr-when .muted { white-space:normal }
  html:not([lang=en]) table.chrono-list td.chr-where { min-width:3em }
  table.chrono-list thead th { white-space:nowrap }
  table.chrono-list td.chr-len { white-space:normal }
}
p.obtns { display:flex; flex-wrap:wrap; gap:.5rem }
/* 家族迁徙路线 on the 族谱溯源 page (www/assets/originmap.js): the stops listed, then the map - its
   canvas the village map's (.vmap-canvas: its own stacking context under the sticky header) - with
   numbered stops in the accent, joined by a dashed line, each labelled with its year and place */
section.oroute { margin:1.5rem 0 }
ol.ostops { margin:.5rem 0 1rem; padding-left:1.6rem } ol.ostops li { margin:.25rem 0 }
.oroute-map { margin:.5rem 0 }
/* a numbered stop: its number centred whatever its size - half as large again on a touch screen
   (tzmap.js), and its number with it */
.rstop { box-sizing:border-box; display:flex; align-items:center; justify-content:center; border-radius:50%;
  background:var(--accent); color:var(--on-accent); font:600 .78rem/1 var(--font); border:2px solid #fff;
  box-shadow:0 1px 3px rgba(0,0,0,.35) }
@media (pointer:coarse) { .rstop { font-size:1rem } }
.tzmap-label.rtip { background:#fff; color:#1f1d1a; border:1px solid #c9c2b8; border-radius:3px; font-size:.8rem; padding:1px 6px }
/* A place's own maps (village.php): smaller than a search's, and each 自然村 labelled with its name and
   surnames - on white whatever the theme, as the tiles are light - the page's own in bold. */
.vplacemap { margin:.5rem 0 .75rem }
/* 签到 (_checkins.php, _checkin_rows.php): one line each, ruled off; 请求隐藏 small and quiet beside it */
.checkinlist { list-style:none; margin:.25rem 0 .5rem; padding:0 }
.checkinlist > li { padding:.45rem 0; border-bottom:1px solid var(--line) }
.checkinlist > li:last-child { border-bottom:0 }
.checkinmsg { margin:.15rem 0 0; overflow-wrap:anywhere }
.checkinmod { display:block; margin:.25rem 0 0 }
.checkinuses { margin:.4rem 0 0; font-size:.85rem }
/* 置顶 (the user asked, 2026-10-11): a line kept at the top, said so before its name */
.tag.checkinpin { background:var(--accent-soft); color:var(--accent); vertical-align:.1em }
/* the × after a line's time (the user asked, 2026-10-11): light grey, darker under the pointer */
.checkinx { display:inline; margin:0 }
.checkinx button { background:none; border:0; box-shadow:none; padding:0 .3rem; margin:0; min-height:0; font-size:1rem; line-height:1;
  color:var(--line); cursor:pointer; vertical-align:baseline }
.checkinx button:hover, .checkinx button:focus-visible { color:var(--muted); background:none }
.checkinplace { border:0; margin:.25rem 0; padding:0 }
.checkinplace legend { padding:0; margin:0 0 .2rem }
/* the emoji above the 留言 box: characters in the reader's own emoji font, as 123PhotoTool's stickers */
.checkinemoji { margin:.35rem 0 .1rem }
.checkinemoji button[data-e] { background:none; border:0; box-shadow:none; color:inherit; padding:.1rem .15rem; margin:0; min-width:0;
  font:1.3rem/1.2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif; cursor:pointer; border-radius:6px }
.checkinemoji button[data-e]:hover, .checkinemoji button[data-e]:focus-visible { background:var(--line) }
.checkinemoji .emojimore { margin-left:.25rem; vertical-align:middle }
.emojiall { margin:.25rem 0 0; padding:.35rem .5rem; border:1px solid var(--line); border-radius:var(--radius) }
.emojigroup { margin:.2rem 0 }
.emojigroup > .muted { display:inline-block; min-width:4.5em }
.checkinpick { display:inline-flex; flex-wrap:wrap; gap:.4rem; margin:.25rem 0 }
/* on a phone each choice of the place on its own row: 选择： under the place found, not after it (the user asked) */
@media (max-width:600px) { .checkinplace > label.inline { display:block; margin:.15rem 0 } }
.checkinpick select { width:auto; max-width:100% }
/* 申请关联族谱: what was said each way, in order (join.php, tree_admin.php) - the administrator's indented */
.jointhread { list-style:none; margin:.5rem 0; padding:0 }
.jointhread li { margin:.4rem 0; padding:.4rem .6rem; border-left:3px solid var(--line); background:var(--paper) }
.jointhread li.by-admin { margin-left:1.5rem; border-left-color:var(--accent) }
.joinreq .joinaccess { margin-right:.5rem }
.joinline { margin:.75rem 0 0 }
/* a heung in the list of 1963 villages, pressed to show it on the map (villagemap.js gives it the role) */
.vheung[role=button] { color:var(--accent); cursor:pointer; text-decoration:underline dotted; text-underline-offset:2px }
.vheung[role=button]:hover { color:var(--accent-2); text-decoration-style:solid }
.vplacemap .vmap-canvas { height:min(55vh, 24rem); min-height:15rem }
/* 台山's map and a 镇's (2026-10-13): many more places, as tall as a search's map, 台山 one zoom nearer for it */
.vplacemap.vcounty .vmap-canvas, .vplacemap.vtown .vmap-canvas { height:min(70vh, 34rem); min-height:18rem }
.vplacemap > .muted { margin:.3rem 0 0 }
.tzmap-label.vlabel { background:rgba(255,255,255,.88); color:#1f1d1a; border-radius:3px; font-size:.75rem; line-height:1.3; padding:0 4px }
.tzmap-label.vlabel.vhere { font-weight:700; font-size:.82rem; border:1px solid #1f1d1a; z-index:3 }
/* the page's own place (2026-10-13): the theme's colour ringed dark, over every other dot and label */
.tzmap-marker.vhere-dot { border-radius:50%; background:var(--accent); border:2.5px solid #1f1d1a; box-sizing:border-box;
  opacity:.95; z-index:2 }

/* tzmap (www/assets/tzmap.js): the site's own map - tiles, then the SVG of circles and lines, then
   markers and labels, then the popup and the controls, each over the one before. Everything is
   placed in pixels from the box's top left. touch-action:pan-x pan-y so that ONE finger scrolls the
   page, over the map as anywhere (the user asked, 2026-10-02), and the browser neither zooms the page
   at a pinch nor at a double tap: two fingers are the map's own (tzmap.js). The tiles are light
   whatever the theme, so the controls and labels on them are too; the popup is the page's own paper
   and ink. */
.tzmap { position:relative; overflow:hidden; touch-action:pan-x pan-y; background:var(--gray-200); cursor:grab;
  -webkit-user-select:none; user-select:none; font-size:.9rem; outline:none }
.tzmap:focus-visible { box-shadow:0 0 0 .2rem var(--focus-btn) }
.tzmap.tzmap-dragging { cursor:grabbing }
.tzmap-tiles, .tzmap-markers { position:absolute; left:0; top:0; width:100%; height:100% }
.tzmap-markers { pointer-events:none }
/* one zoom level's tiles, moved and scaled as a whole while the zoom glides (tzmap.js) */
.tzmap-level { position:absolute; left:0; top:0; transform-origin:0 0; will-change:transform }
img.tzmap-tile { position:absolute; width:256px; height:256px; max-width:none; border:0; -webkit-user-drag:none; pointer-events:none }
svg.tzmap-vec { position:absolute; left:0; top:0; pointer-events:none; overflow:visible }
svg.tzmap-vec .tzmap-circle { pointer-events:visiblePainted; cursor:pointer }
.tzmap-marker { position:absolute; pointer-events:auto; cursor:pointer }
.tzmap-label { position:absolute; transform:translateY(-50%); white-space:nowrap; pointer-events:none }
.tzmap-popup { position:absolute; z-index:3; cursor:auto; -webkit-user-select:text; user-select:text }
.tzmap-popup-inner { position:relative; background:var(--paper); color:var(--ink); border-radius:10px;
  box-shadow:0 3px 14px rgba(0,0,0,.3); padding:.6rem 36px .6rem .9rem; line-height:1.5 }
.tzmap-popup-content { overflow-wrap:anywhere }
.tzmap-popup-content a { color:var(--accent) } .tzmap-popup-content a.xing { color:var(--ink) }
.tzmap-popup-tip { width:14px; height:14px; margin:-8px auto 0; background:var(--paper); transform:rotate(45deg);
  box-shadow:3px 3px 6px rgba(0,0,0,.15) }
/* the × that closes it: a 36px square in the corner, 44px under a finger (the user asked for it
   bigger, 2026-10-02), the popup's words kept clear of it by its padding */
button.tzmap-close { position:absolute; top:0; right:0; display:flex; align-items:center; justify-content:center;
  width:36px; height:36px; margin:0; padding:0; background:none; border:0; border-radius:0 10px 0 6px;
  color:var(--muted); font-size:1.75rem; line-height:1 }
button.tzmap-close:hover { color:var(--ink); background:none }
@media (pointer:coarse) {
  .tzmap-popup-inner { padding-right:44px }
  button.tzmap-close { width:44px; height:44px; font-size:2.1rem }
}
/* the zoom buttons, in Google Maps' manner: a white box at the bottom right, over the attribution,
   two 40px squares - a finger's width - with a 30px rule between them, grey signs that darken when
   pointed at and go pale at the end of the zoom. White on every theme, as the map under it is. Every
   property the site's own `button` sets is set again here: its margin, border and colour are not
   wanted on a map. */
.tzmap-ctl { position:absolute; right:10px; bottom:26px; z-index:4; display:flex; flex-direction:column;
  align-items:center; width:40px; background:#fff; border-radius:2px; box-shadow:rgba(0,0,0,.3) 0 1px 4px -1px;
  user-select:none }
.tzmap-ctl button { display:flex; align-items:center; justify-content:center; width:40px; height:40px; margin:0;
  padding:0; border:0; border-radius:2px; background:none; color:#666; cursor:pointer; transition:color .15s }
.tzmap-ctl button:hover, .tzmap-ctl button:active { background:none; color:#333 }
.tzmap-ctl button:focus-visible { box-shadow:inset 0 0 0 2px var(--accent) }
.tzmap-ctl button[aria-disabled="true"], .tzmap-ctl button[aria-disabled="true"]:hover { color:#d0d0d0; cursor:default }
.tzmap-ctl svg { width:18px; height:18px; fill:none; stroke:currentColor; stroke-width:2 }
.tzmap-ctl-rule { width:30px; height:1px; background:#e6e6e6 }
.tzmap-attrib { position:absolute; right:0; bottom:0; z-index:4; padding:0 6px; background:rgba(255,255,255,.85);
  color:#333; font-size:11px; line-height:1.6 }
.tzmap-attrib a { color:#0a58ca }
/* told to one finger moving over the map, for a moment: a tip at the top centre, the map left as
   it is under it (the user asked, 2026-10-02 - it dimmed the whole map at first); over the tiles and
   the circles, catching no touch */
.tzmap-hint { position:absolute; top:10px; left:50%; z-index:5; transform:translateX(-50%); max-width:calc(100% - 20px);
  padding:.35rem .85rem; border-radius:999px; background:rgba(32,33,36,.85); color:#fff; font-size:.9rem;
  line-height:1.4; text-align:center; width:max-content; box-shadow:0 1px 4px rgba(0,0,0,.3);
  pointer-events:none; opacity:0; transition:opacity .25s }
.tzmap-hint.on { opacity:1 }
@media (prefers-reduced-motion:reduce) { .tzmap-hint { transition:none } }

/* ---- 开始建谱 (/start) ------------------------------------------------------------------------------
   The wizard's pages are the site's ordinary forms in a column wide enough for the steps above them:
   at the forms' own 520px the four names crowd on a desk, and nothing is gained by the column being
   narrower than the bar that heads it. The column stands in the middle of a wide screen, as the
   关于 page does (the user asked for both, 2026-10-01). */
.wiz { max-width:640px; margin:0 auto }
.wiz .box.narrow { max-width:none }
/* the steps (wizard_steps): four numbered circles joined by a line, done ones filled with a tick, the
   current one ringed. The line into each step is drawn by that step, from the middle of the one
   before it, so it needs no measuring and no script - and so it runs under the circle before it,
   which comes earlier in the page and would otherwise be painted over: the circles are lifted above
   every line. (中华族谱网 shipped without that and the line struck through 1, 2 and 3.) */
ol.wizsteps { display:flex; list-style:none; padding:0; margin:.4rem 0 1.25rem }
.wizstep { flex:1 1 0; position:relative; display:flex; flex-direction:column; align-items:center;
  text-align:center; font-size:.85rem; color:var(--muted) }
.wizstep::before { content:""; position:absolute; top:1rem; left:-50%; right:50%; height:2px; margin-top:-1px;
  background:var(--line-2); z-index:0 }
.wizstep:first-child::before { display:none }
.wizstep.is-done::before, .wizstep.is-now::before { background:var(--accent) }
.wizstep-no { position:relative; z-index:1; width:2rem; height:2rem; border-radius:50%; display:inline-flex;
  align-items:center; justify-content:center; border:2px solid var(--line-2); background:var(--paper);
  font-weight:600; margin-bottom:.3rem }
.wizstep.is-done .wizstep-no { background:var(--accent); border-color:var(--accent); color:var(--on-accent) }
.wizstep.is-now .wizstep-no { border-color:var(--accent); color:var(--accent) }
.wizstep.is-now { color:var(--ink); font-weight:600 }
.wizmine .btn { margin:.25rem .25rem 0 0 }
.wizcode { display:block; max-width:16rem; font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace; letter-spacing:.06em }
.wizgo { margin-top:1.25rem }
.wizname { font-family:var(--serif); font-size:1.35rem; margin:.2rem 0 .6rem }
.wizcta { margin:1rem 0 1.5rem } .wizcta .muted { margin-left:.5rem }
/* 接下来可以: what the finished wizard offers, one row a link, the whole row the target */
ul.wiznext { list-style:none; padding:0; margin:.5rem 0 1.5rem; border:1px solid var(--line);
  border-radius:var(--radius); background:var(--paper); overflow:hidden }
ul.wiznext li + li { border-top:1px solid var(--line) }
ul.wiznext a { display:block; padding:.75rem 1rem; color:var(--ink) }
ul.wiznext a:hover { background:var(--row-hover); text-decoration:none }
ul.wiznext a::after { content:"›"; float:right; color:var(--muted) }
/* 邀请族人: the invitation to paste into the family chat, big enough to read before copying */
.invite { margin-top:1rem }
.invite textarea { font-size:.95rem; line-height:1.55; resize:vertical }
.invite-do { margin:.5rem 0 0 }

/* A 镇's page: each 行政村 a <details>, open, its summary the 行政村's own line and its 自然村 flowing
   under it (village.php). The marker is drawn, not the browser's, so it sits in the line's own
   baseline; a 行政村 with none under it is indented as far, so the names stand in one column. */
ul.places li.hasnat { display:block }
ul.places li.nonat { padding-left:1.85rem }
details.nat { margin:0 }
details.nat > summary { list-style:none; color:inherit; font-weight:400; display:flex; flex-wrap:wrap;
  align-items:baseline; gap:.2rem .6rem }
details.nat > summary::-webkit-details-marker { display:none }
details.nat > summary::before { content:"\25B6"; flex:0 0 1rem; font-size:.7rem; color:var(--muted); transition:transform .15s }
details.nat[open] > summary::before { transform:rotate(90deg) }
ul.natlist { list-style:none; margin:.35rem 0 .15rem 1.6rem; padding:0; display:flex; flex-wrap:wrap; gap:.3rem 1.1rem }
ul.places ul.natlist li { padding:0; border:0; display:inline-flex; align-items:baseline; gap:.3rem }
.foldall { margin:.25rem 0 0 }
@media (prefers-reduced-motion:reduce) { details.nat > summary::before { transition:none } }
/* ...and after them, the 自然村 no source has put in a 行政村 (「未归入行政村」), a group of its own */
details.nat.loose { margin-top:1rem; padding-top:.5rem; border-top:1px solid var(--line) }
details.nat.loose > p { margin:.3rem 0 0 1.6rem }
/* ...in alphabetical order, a row to each letter: the letter in the left margin, its villages after it */
details.nat.loose .lgroup { display:flex; align-items:baseline; gap:.6rem; margin:.45rem 0 0 1.6rem }
details.nat.loose .lgroup > h3.letter { flex:0 0 1.1rem; margin:0; font-size:.95rem; font-weight:600; color:var(--accent) }
details.nat.loose .lgroup > ul.natlist { flex:1 1 auto; min-width:0; margin:0 }
/* 联系我们留言 on 系统管理 (_contact_messages.php): who and from where on a line, then the words, kept as typed */
.cmsg .cmhead { margin:0 } .cmsg .cmbody { margin:.5rem 0; overflow-wrap:anywhere }
/* 关于台山族谱网 - as wide as 用户协议 and 隐私政策, the page's whole column (the user asked, 2026-10-15; it
   was a 46rem column in the middle, as chinazupu.com/about is); its lead a size up; the cards at the
   end keep the home page's grid */
.about .lead { font-size:1.1rem } .about li { margin:.35rem 0 }
.about .cards { margin-top:.75rem; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)) }
/* The home page's banner. On a wide screen the words sit over the lower left of the picture, on the
   houses at the 碉楼's foot and the rice field, below the 碉楼 and clear of the children, on a light
   shade from the bottom and the left - the user's (2026-10-18), for a brighter picture; it was .8 at
   the foot and .5 at the left before, and the text's shadow does more of the work now. Narrower than 1024px - a phone,
   a tablet held upright - the picture is too short to carry them, so they go under it on the shade's
   own colour: the words keep their size as the picture shrinks, and at 900px they covered the 碉楼
   and, in English, the children. A dark picture is the same in every theme; the button is the theme's. */
.banner { position:relative; margin:0 0 1.25rem; border-radius:var(--radius); overflow:hidden; background:#241a12;
  box-shadow:var(--shadow) }
.banner img { display:block; width:100%; height:auto }
.banner-text { padding:1.1rem 1.1rem 1.3rem; color:#fff }
.banner-text .ph { display:inline-block }
/* Both lines in 楷书 (ZupuKai, declared by home.php), as chinazupu.com sets its banner; what the cut
   leaves out - the 简体 copy's ，。, which UKai centres as Taiwan does - falls to the serif. English
   has no 楷书 and keeps the serif and the page's own face. */
.banner-head { margin:0 0 .45rem; font-family:"ZupuKai", var(--serif); font-size:1.35rem; line-height:1.45; letter-spacing:.04em }
.banner-sub { margin:0 0 1.1rem; font-family:"ZupuKai", var(--serif); font-size:1.1rem; line-height:1.6; letter-spacing:.03em;
  color:rgba(255,255,255,.92) }
.banner-head:lang(en) { font-family:var(--serif); letter-spacing:.02em } .banner-sub:lang(en) { font-family:var(--font); letter-spacing:0 }
.btn.lg { padding:.55rem 1.75rem; font-size:1.15rem; border-radius:.5rem }
@media (min-width:1024px) {
  /* 2:1, a little off the picture's 16:9, from the top: the whole 碉楼 stays in and the words come
     down onto its foot; at 16:7 they covered its lower half */
  .banner { aspect-ratio:2/1 }
  .banner img { height:100%; object-fit:cover; object-position:50% 75% }
  .banner::after { content:""; position:absolute; inset:0; pointer-events:none;
    background:linear-gradient(to top, rgba(20,13,7,.1), rgba(20,13,7,.1) 40%, rgba(20,13,7,0) 64%),
               linear-gradient(to right, rgba(20,13,7,.3), rgba(20,13,7,0) 56%) }
  /* Every line ends short of the girl in white, half way across. 33rem breaks the Chinese second line
     after 那一片稻田 (at 36rem 那一座碉楼 ran on to her). English is wider, 34rem, and its heading a size
     down (below): at 33rem the heading took three lines and climbed to the 碉楼, and at 36rem 「and」
     joined its first line, which then reached her */
  .banner-text { position:absolute; left:0; bottom:0; z-index:1; max-width:33rem; padding:0 2.2rem 2rem;
    text-shadow:0 1px 3px rgba(0,0,0,.9) }
  .banner-text:lang(en) { max-width:34rem }
  .banner-text .btn { text-shadow:none }
  .banner-head { font-size:1.95rem }
  .banner-sub { font-size:1.25rem }
  /* English runs half as long again as the Chinese, and at the Chinese size climbed to the 碉楼; its
     heading at 1.6rem ran on to the girl in white at 1024px */
  .banner-head:lang(en) { font-size:1.5rem } .banner-sub:lang(en) { font-size:1.05rem }
}
/* 在线建族谱，全族一起编 (home.php, #promo): the theme's soft accent, three points each under an icon,
   and the three ways in - as chinazupu.com has it under its banner. */
.promo { margin:0 0 1.5rem; padding:1.4rem 1.25rem 1.5rem; border:1px solid var(--accent-line); border-radius:var(--radius);
  background:var(--accent-soft) }
.promo > h2 { margin:0 0 .45rem; font-family:var(--serif); font-size:1.5rem; color:var(--ink) }
.promo-lead { margin:0 0 1.25rem; font-size:1.05rem }
.promo-items { display:grid; gap:1.25rem; margin:0 0 1.4rem }
.promo-items h3 { margin:.45rem 0 .3rem; text-align:center; font-size:1.05rem; font-weight:600 }
.promo-items p { margin:0 }
.promo-icon { display:flex; align-items:center; justify-content:center; width:3.5rem; height:3.5rem; margin:0 auto;
  border-radius:50%; background:var(--paper); color:var(--accent); box-shadow:var(--shadow) }
.promo-icon svg { width:2.1rem; height:2.1rem }
.promo-go { display:flex; flex-wrap:wrap; justify-content:center; gap:.5rem; margin:0 }
@media (min-width:768px) {
  .promo { padding:1.75rem 2rem 1.9rem }
  .promo-items { grid-template-columns:repeat(3, 1fr); gap:1.75rem }
}
/* 使用教程 (/guide, guide.php): a reading column in the middle, like 关于; numbered steps, each with its
   picture. A phone screenshot is drawn at a phone's width even on a desk (guide_shot halves its
   pixels), framed so it reads as a screen rather than as part of this page. */
.guidepage { max-width:46rem; margin:0 auto }
.guidepage .lead { font-size:1.1rem }
.guidego { display:flex; flex-wrap:wrap; gap:.5rem }
.guidecards { grid-template-columns:repeat(auto-fill, minmax(260px, 1fr)); margin:1rem 0 1.5rem }
.guidecard { padding:0; overflow:hidden }
.guidecard img { display:block; width:100%; height:10rem; object-fit:cover; object-position:top center;
  border-bottom:1px solid var(--line) }
.guidecard h2 { margin:.8rem 1rem .3rem; font-size:1.1rem; color:var(--accent) }
.guidecard p { margin:0 1rem 1rem; font-size:.9rem }
.gstep { margin:0 0 2.25rem }
.gstep > h2 { display:flex; align-items:center; gap:.6rem; font-size:1.15rem }
.gno { flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center; width:1.9rem; height:1.9rem;
  border-radius:50%; background:var(--accent); color:var(--on-accent); font-size:1rem; font-weight:600 }
.shot { margin:1rem 0 0 }
.shot img { max-width:100%; height:auto; border:1px solid var(--line); border-radius:.6rem;
  box-shadow:0 .15rem .6rem rgba(0,0,0,.08); background:var(--paper) }
.shot figcaption { margin-top:.4rem; font-size:.85rem; color:var(--muted) }
.gtip { margin:1.5rem 0; padding:.8rem 1rem; background:var(--paper); border-left:4px solid var(--accent); border-radius:.3rem }
.gtip > :first-child { margin-top:0 } .gtip > :last-child { margin-bottom:0 }
.guidenav { display:grid; grid-template-columns:5fr 2fr 5fr; gap:.5rem; margin-top:3rem; padding-top:1rem;
  border-top:1px solid var(--line) }
.guidenav small { display:block; color:var(--muted) }
.guidenav-all { text-align:center } .guidenav-next { text-align:right }

/* ---- the English pages' face ----------------------------------------------------------------
   Manrope, as 123phototool.com sets its text (the user asked, 2026-10-05): a geometric sans, open in
   its curves, served from here like every other face (font-src 'self'; Google's servers do not
   answer in mainland China). The variable font, so 400, 600 and 700 are one file; latin, and
   latin-ext for pinyin's tone marks (Zhào's à is latin, ǎ and ō are not). Licence: fonts/Manrope-OFL.txt.
   Only where <html lang="en">, and only in front: every character Manrope lacks - every name in a
   genealogy - falls through to the same Chinese faces as before. Last in the file, because a theme
   sets --font of its own (族谱红 puts Inter first) and this has to win over all of them. */
@font-face { font-family:"Manrope"; font-style:normal; font-weight:200 800; font-display:swap;
  src:url("fonts/Manrope-latin-ext.woff2") format("woff2");
  unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329,
    U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF }
@font-face { font-family:"Manrope"; font-style:normal; font-weight:200 800; font-display:swap;
  src:url("fonts/Manrope-latin.woff2") format("woff2");
  unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329,
    U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD }
/* --serif too: on an English page what it sets in letters - the headings of the home page, the cards,
   the guides - is English, and 123phototool.com's headings are its text face. Its CHINESE stays in the
   serif behind Manrope, which has none: a 传记, a 字辈, a genealogy's name in a card read as before. */
:root:lang(en) {
  --font:"Manrope",system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue","Noto Sans SC","PingFang SC",
         "Microsoft YaHei","Noto Sans","Liberation Sans",Arial,sans-serif;
  --serif:"Manrope","Noto Serif SC","Songti SC","SimSun",serif;
}

/* 访问记录 (admin_request_log.php): a line a page answered. Its own scroller on a phone; the page and the
   browser under it wrap, the time, address, request and milliseconds do not. */
table.reqlog { font-size:.85rem }
table.reqlog td { white-space:nowrap; vertical-align:top }
table.reqlog td.reqlog-page { white-space:normal; word-break:break-all; min-width:14rem }
.reqlog-days { line-height:1.9 }

/* 相关视频 (village.php #videos): a card each, reading like the rest of the page and not like a video site. The
   player's box keeps 16:9 and holds 在本页播放 until it is pressed; nothing in it moves by itself. */
.vcards { list-style:none; margin:0; padding:0 }
.vcard { border-top:1px solid var(--line); padding:.9rem 0 .4rem }
.vcard:first-child { border-top:none; padding-top:.2rem }
.vcard h3 { margin:0 0 .35rem; font-size:1.05rem }
/* 乡村百科's introduction of a place, above the link to it (2026-10-18) */
.xcbk-intro { margin:.75rem 0 .25rem; line-height:1.75 }
.xcbk-intro p { margin:0 0 .6rem }
/* 置顶 (2026-10-18): first among a place's, and says so */
.vcard h3 .tag.vpin { background:var(--accent-soft); color:var(--accent); vertical-align:.1em }
.vcard p { margin:.3rem 0 }
.vmeta { color:var(--muted); font-size:.92rem }
.vplayer { position:relative; max-width:40rem; aspect-ratio:16/9; margin:.6rem 0; border-radius:var(--radius);
  overflow:hidden; background:var(--accent-soft) }
.vplayer iframe, .vplay { position:absolute; inset:0; width:100%; height:100%; border:0 }
.vplay { cursor:pointer; font:inherit; font-size:1rem; color:var(--accent); background:transparent }
.vplay:hover, .vplay:focus-visible { background:var(--paper) }
.vdesc, .vrel, .vreview .vlink a { overflow-wrap:anywhere }
.vform textarea { width:100% }
.vrules, .vcopy { margin:.4rem 0 }
.vtabs { line-height:1.9 }
