/* ============================================================
   Divinity - shared site theme
   Single source of truth for design tokens, base layout, the
   site chrome (header / nav / text-size / theme / mobile menu),
   and the common component vocabulary (cards, tables, pills,
   forms, buttons, messages) shared across every page.
   Page-specific rules live in each page's own <asp:Content
   ContentPlaceHolderID="Head"> block and override these.
   ============================================================ */

/* ---- design tokens ---- */
:root{
  --ground:#0b0d1a; --panel:#141731; --panel2:#1b1f3d; --edge:#2b2f55; --edge2:#3a3f6b;
  /* --faint WAS #726f98 and FAILED WCAG 1.4.3 on every surface it is used against:
     3.39:1 on --panel2, 3.71 on --panel, 4.09 on --ground, where small text needs 4.5.
     It is a pure text token (3,609 of its 3,663 uses across the .aspx files are
     `color:`, and every one in this file is too), and it is used ONLY for small
     secondary text -- meta labels, table column heads, the Compare row labels, the
     uppercase .68rem captions -- i.e. exactly the text the 4.5 bar is written for.
     #8785a7 is the least lightening that clears it: 4.54 on --panel2, 4.97 on
     --panel, 5.47 on --ground, hue and character preserved. w18-a11y-audit,
     2026-07-31. Reversible: restore #726f98 here and in [data-theme="dark"] below. */
  --ink:#eae7f3; --muted:#a7a5c4; --faint:#8785a7; --gold:#d8b45f; --gold-soft:#e6cd8f;
  /* --gold-bg is the accent used as a SOLID BACKGROUND FILL behind hardcoded dark
     text (#1a1508), on the primary button, active pager states and active tab
     states, roughly 60 rules across this file. --gold itself is fine there in
     THIS theme: #1a1508 on #d8b45f measures 9.19:1, comfortably over the 4.5 bar,
     so --gold-bg is simply --gold's own value here, no change needed.
     The failure is LIGHT theme only, where lightening --gold itself is not an
     option: it is text and border colour on every other surface, and no single
     value clears 4.5:1 as both a fill behind dark text and a text colour on panel
     at once (the same tension --faint and --shared above were rebased for, just
     with the fill and the text roles swapped). This is why a PAIRED token, the
     same shape as --shared/--shared-bg and --dark-c/--dark-bg, was needed rather
     than a hue change: see the light block below for the arithmetic. Missed by
     both July audits (w18-a11y-audit, w19-a11y-shared), which checked --gold as
     TEXT colour, where it is fine on every surface, and never as a fill behind
     text. Reversible: set --gold-bg equal to --gold in every block below and the
     site is back to its prior look. 2026-08-19. */
  /* --dark-c WAS #cf7089 and FAILED WCAG 1.4.3 in the DARK theme too, which is the half
     nobody expected: 4.15:1 on --panel2 once --dark-bg composites in, 4.55 on --panel,
     against a 4.5 bar. It is the "Debunked Myth" and "Rejected Here" badge colour at
     .66rem uppercase, i.e. small text by any reading, plus .msg.err and the danger
     button. Never a background fill anywhere (that is --dark-bg), and never a large
     display size, so LIGHTENING it can only help: it is `color:` or `border-color:` in
     every one of its uses. #d27a92 is the least lightening that clears 4.5 on all six
     surfaces (4.55 panel2+tint, 4.99 panel+tint, 5.30 panel2, 5.57 ground+tint, 5.80
     panel, 6.39 ground), hue and saturation held exactly, only HSL lightness moved.
     w19-a11y-shared, 2026-07-31. Reversible: restore #cf7089 here and in
     [data-theme="dark"] below. --shared needs no change in this theme; it measures
     5.76:1 worst case here and only fails in light. */
  --shared:#79bcb0; --shared-bg:rgba(111,178,168,.14); --dark-c:#d27a92; --dark-bg:rgba(181,84,106,.16);
  --gold-bg:#d8b45f;
  --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
  --sans:ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,Arial,sans-serif;
  --shadow:0 12px 34px -14px rgba(0,0,0,.6);
  /* The reading measure. In rem, not ch, because ch is relative to each element's own
     font-size -- .lead at 1.04rem and a header row at 1rem would have resolved the same
     ch count to different widths, and the action row would not have lined up with the
     text it sits above. ~78 characters at the body size. */
  --measure:40.5rem;
  /* THE PAGE ACTION ROW'S CONTROL SIZE, IN ONE PLACE.
     Every control in the shared bar is this size: the star, the follow bell, the
     Save To Collection picker, the read control, copy, share, and the phone
     kebab. They are declared here rather than in each block because the row had
     already forked once: .readbtn/.followbtn/.actsmore were a fixed 40px box
     while the injected .dspeak was 2.5em, so the reader's own A-/A+ control
     moved one control in the row and left the other five behind. Measured
     2026-08-02 on a findings page: 37.5px beside 40px at the default, and
     56.25px beside 40px at the reader's 150%.

     em, NOT px, because the project's accessibility rule is that a component
     scales with the reader's text size and a fixed px box opts it out. Every
     control that uses these carries font:inherit, so the em resolves against
     the row's own text size and all of them move together.

     2.6667em against the html{font-size:93.75%} base is 40.0px, and 2.9333em is
     44.0px, so NOTHING MOVES AT THE DEFAULT: this is a scaling fix, not a
     resize, which is what layout parity requires of a ported layout. The comment
     that used to sit in pageacts.css said 2.5em "lands on the same 40px .readbtn
     draws at the default" -- it does not, because the base is 15px and not 16px,
     and that arithmetic slip is what left the read control visibly small in its
     own row from the day the bar shipped. */
  --actbtn:2.6667em; --actbtn-touch:2.9333em; --actico:1.2em; --actgap:.5333em;
}
@media (prefers-color-scheme: light){:root{
  --ground:#f4efe3; --panel:#eee7d6; --panel2:#f8f4ea; --edge:#ddd2ba; --edge2:#cabfa3;
  /* --faint WAS #938b78, the worst contrast failure measured anywhere on the site:
     2.75:1 on --panel and 2.95 on --ground, below even the 3.0 large-text floor, on
     text that is never large. #6d6759 is the least darkening that clears 4.5 on all
     three surfaces (4.56 panel, 4.90 ground, 5.12 panel2) and keeps the warm grey.
     See the dark-theme note above. w18-a11y-audit, 2026-07-31. */
  --ink:#282540; --muted:#655f74; --faint:#6d6759; --gold:#9a7420; --gold-soft:#b6912f;
  /* --gold-bg is the fix. #1a1508 on --gold (#9a7420) measures 4.24:1 here, under
     the 4.5 bar, on the primary button, active pager states and active tab states
     (roughly 60 rules). Hue (41.3 degrees) and saturation (65.6%) held exactly,
     same method as --faint and --shared above, only HSL lightness moved: 36.5% to
     37.8%. #a07921 is the least lightening that clears it, 4.55:1. Only the ~60
     fill rules repoint from var(--gold) to var(--gold-bg); every `color:` and
     `border-color:` use of --gold is untouched, so links, focus rings, borders and
     headings keep their exact prior colour, and --gold-soft (already 6.13:1 on
     #1a1508 here) needs no change either, so hover states were left alone.
     w18-a11y-audit and w19-a11y-shared missed this because they read --gold as a
     TEXT colour, where it passes on every surface; the failure only shows up when
     it sits behind text as a fill, which neither audit checked. Reversible:
     restore --gold-bg:#9a7420 here and in [data-theme="light"] below. 2026-08-19. */
  /* --shared WAS #3c8578 and FAILED WCAG 1.4.3 on EVERY surface in this theme: 3.54:1
     on --panel, 3.10 once --shared-bg composites in, 3.80/3.32 on --ground, 3.97/3.45
     on --panel2, and 3.31 under the tint the Admin success message hard-codes. The
     large-text exemption does not apply to any of it: the .xtb-att "Well-attested"
     badge is .66rem uppercase, and .src-tier.tier1, .pill.on, .msg.ok, .cha-badge
     .ch-accepted and the Compare shared cell are all small text too. Like --faint
     above it is a pure text-and-border token: across this file and the .aspx pages it
     is only ever `color:` (62) or `border-color:` (27), plus one decorative meter
     gradient, and NEVER a background fill behind text, so darkening it strictly
     improves every one of its uses. #2f685e is the least darkening that clears 4.5 on
     all nine surfaces (4.58 panel+tint, 4.89, 4.90, 5.10, 5.22, 5.22, 5.43, 5.61,
     5.86), hue and saturation held exactly, only HSL lightness moved. --shared-bg is
     deliberately left on the old triple: it is a tint, not text, and re-basing it
     would darken the very surface the ratio is measured against. --dark-c fails here
     as well, at 4.33:1 on --panel with --dark-bg composited in, and #9c3f57 is its
     least darkening to 4.56; see the dark-theme note above for why lightening and
     darkening are both safe for it. w19-a11y-shared, 2026-07-31. Reversible: restore
     #3c8578 and #a2415a here and in [data-theme="light"] below. */
  --shared:#2f685e; --shared-bg:rgba(60,133,120,.12); --dark-c:#9c3f57; --dark-bg:rgba(162,65,90,.10);
  --gold-bg:#a07921;
  --shadow:0 14px 30px -18px rgba(60,48,20,.45);}}
:root[data-theme="dark"]{--ground:#0b0d1a;--panel:#141731;--panel2:#1b1f3d;--edge:#2b2f55;--edge2:#3a3f6b;--ink:#eae7f3;--muted:#a7a5c4;--faint:#8785a7;--gold:#d8b45f;--gold-soft:#e6cd8f;--gold-bg:#d8b45f;--shared:#79bcb0;--shared-bg:rgba(111,178,168,.14);--dark-c:#d27a92;--dark-bg:rgba(181,84,106,.16);--shadow:0 12px 34px -14px rgba(0,0,0,.6);}
:root[data-theme="light"]{--ground:#f4efe3;--panel:#eee7d6;--panel2:#f8f4ea;--edge:#ddd2ba;--edge2:#cabfa3;--ink:#282540;--muted:#655f74;--faint:#6d6759;--gold:#9a7420;--gold-soft:#b6912f;--gold-bg:#a07921;--shared:#2f685e;--shared-bg:rgba(60,133,120,.12);--dark-c:#9c3f57;--dark-bg:rgba(162,65,90,.10);--shadow:0 14px 30px -18px rgba(60,48,20,.45);}

/* ---- scrollbars, every one of them (owner, 2026-07-28) -------------------
   "Message scrollbar make navy color when in dark mode and gold in light mode.
   Make all scroll bars carry this style." So this is deliberately global rather
   than scoped to the message list: the page, the message pane, any overflow-x
   block on a table, the lot.

   Declared as its own pair of variables instead of reusing --edge2 and --gold
   directly, because a scrollbar thumb has a different job from a border. It sits
   on whatever surface it happens to overlay and still has to be findable, so the
   dark value is lifted well clear of the panel navies (#2b2f55, #3a3f6b) rather
   than blending into them. The light value IS --gold, which already carries
   enough weight against the parchment ground.

   The data-theme rules repeat the media query's values on purpose and sit after
   it, which is this file's standing pattern: the A11y theme toggle stamps
   data-theme on the root and must beat the OS preference in BOTH directions.

   12px, not the 6px that looks tidy in a screenshot. The owner uses this site
   with increased text size and a screen reader, and a hairline thumb is a
   pointer target nobody can hit. The 2px transparent border with background-clip
   keeps the visible thumb around 8px while leaving the grab area the full 12. */
:root{--scrollthumb:#3f4a8f;--scrollthumb-hi:#5b68b8}
@media (prefers-color-scheme: light){:root{--scrollthumb:var(--gold);--scrollthumb-hi:var(--gold-soft)}}
:root[data-theme="dark"]{--scrollthumb:#3f4a8f;--scrollthumb-hi:#5b68b8}
:root[data-theme="light"]{--scrollthumb:var(--gold);--scrollthumb-hi:var(--gold-soft)}

/* Firefox and modern standards path. scrollbar-color INHERITS, so declaring it on
   html reaches every scroll container on the page without a universal selector. */
html{scrollbar-width:thin;scrollbar-color:var(--scrollthumb) transparent}
/* WebKit's pseudo-elements do not inherit, so they do need the universal selector. */
*::-webkit-scrollbar{width:12px;height:12px}
*::-webkit-scrollbar-track{background:transparent}
*::-webkit-scrollbar-thumb{background:var(--scrollthumb);border-radius:99px;
  border:2px solid transparent;background-clip:padding-box}
*::-webkit-scrollbar-thumb:hover{background:var(--scrollthumb-hi);background-clip:padding-box}
*::-webkit-scrollbar-corner{background:transparent}

/* The standard back link: an arrow, above the page heading, outside whatever card
   the view is drawn in. Lived in Report.aspx's own <style> until Messages needed
   the same back link, same style, same
   position. A component used by two pages belongs in the shared sheet, or the
   second copy drifts from the first. Font-size in rem so it tracks A-/A+. */
/* Visually hidden, still read aloud. Added here 2026-07-30, when a SHARED renderer
   (AccountRender's starred list) began emitting it: until then the rule existed only
   inside the private <style> block of the two or three pages that used it, so a label
   emitted by shared code would have been visible text on every page that had not
   happened to declare it. Those page-level copies are identical and harmless; this is
   the one every surface can rely on. Clip rather than display:none or
   visibility:hidden, both of which take the element out of the accessibility tree
   entirely, which is the opposite of the point. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;
  clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* THE AJAX GRID'S BUSY STATE. js/ajaxgrid.js swaps a [data-ajaxgrid] region in
   place for a sort, a pager or a GET filter, and sets aria-busy on the wrapper for
   as long as the fetch is in flight. aria-busy is the half that carries the
   meaning, telling a screen reader not to read a list that is mid-replacement, and
   this rule is the sighted half of the same message. The first edition wrote it as
   host.style.opacity from inside the script; the no-inline-style rule forbids that here, so it is
   one rule keyed on the attribute the script was already setting and nothing in the
   script writes a style. Opacity only: no movement and no reflow, and the region
   keeps its size, so the page does not jump under a reader mid-row. */
[data-ajaxgrid][aria-busy="true"]{opacity:.55}

.back{display:inline-block;margin-bottom:16px;color:var(--muted);text-decoration:none;font-size:.85rem}
.back:hover,.back:focus-visible{color:var(--gold)}

/* ---- SKIP LINK (WCAG 2.4.1 Bypass Blocks, Level A) ------------------------
   Added 2026-07-31 by w18-a11y-audit. The site had none, and the header is not
   small: a wordmark link, a search field with its own button, four disclosure
   menus, three icon buttons and the profile menu. A keyboard-only user, a switch
   user and anyone driving the page by voice had to walk every one of those on
   EVERY page load before reaching the content. <main id="appmain"> already
   existed in Site.master, so this needed a target, not a restructure.

   Off-screen rather than display:none, so it stays in the tab order and in the
   accessibility tree, and it comes back on focus only -- the standard pattern.
   z-index beats header.top's 20 so it is not painted behind the sticky header.
   Sized in rem so the A-/A+ control owns it like everything else. */
/* THE PARK IS THE LINK'S OWN HEIGHT NOW, NOT A FIXED 100 PIXELS. Lane rp-a11y,
   2026-08-04, from lane vc-chrome's report that at 200 per cent the link revealed
   itself over the wordmark without anyone having focused it.

   The link is sized in rem, so its box grows with the reader's setting, while the
   hiding place it was parked in was a constant. A box taller than 100px is not hidden
   by top:-100px, it merely starts 100px higher, and the bottom of it stays on screen.
   translateY(-100%) hides the box in terms of ITSELF, so it cannot be outgrown at any
   text size, any font, or any padding anyone gives it later. The extra 8px is clearance
   for the focus ring, which is drawn outside the border box and would otherwise be the
   one thing still poking out.

   top:0 rather than top:-100px in the resting rule, with the transform doing the
   hiding, so :focus has one declaration to undo and there is no second measurement to
   keep in step with the first. A transform is also compositor-only, so revealing the
   link cannot reflow the header it sits over.

   Everything else is unchanged and deliberate: off screen rather than display:none, so
   it stays in the tab order and in the accessibility tree; z-index beats header.top's
   20 so it is not painted behind the sticky header; and it stays sized in rem so the
   A-/A+ control owns it like everything else. */
.skiplink{position:absolute;left:8px;top:0;z-index:400;
  transform:translateY(calc(-100% - 8px));
  padding:10px 16px;border-radius:0 0 10px 10px;
  background:var(--panel);border:1px solid var(--gold);border-top:0;
  color:var(--gold);font-size:.95rem;text-decoration:none}
.skiplink:focus{transform:none}

/* ---- REDUCED MOTION, SITE-WIDE (WCAG 2.3.3) -------------------------------
   Added 2026-07-31 by w18-a11y-audit. This file carried fifty `transition`
   declarations and four narrowly-scoped opt-outs (.addcard, the facts drawer,
   .divdock, .aztop); Compare.aspx and Entity.aspx each declared their own
   page-scoped kill switch. Everything else moved regardless of what the reader
   had asked the OS for. One global rule replaces the pattern of remembering to
   add a fifth, sixth and seventh scoped block, and the existing scoped ones are
   left in place because they are harmless and they document their own cases.

   transform and animation are included as well as transition: nothing on the
   site animates today, and this is what keeps that true for whatever is added
   next. 0.01ms rather than `none` so a transitionend/animationend listener still
   fires and nothing waits forever for an event that was cancelled. */
@media (prefers-reduced-motion: reduce){
  *,*::before,*::after{
    animation-duration:.01ms!important;
    animation-iteration-count:1!important;
    transition-duration:.01ms!important;
    scroll-behavior:auto!important}
}

/* ---- ANCHOR TARGETS CLEAR THE STICKY HEADER (WCAG 2.4.11) -----------------
   Added 2026-07-31 by w18-a11y-audit. header.top is position:sticky;top:0, so a
   fragment link scrolls its target to y=0 and the header then covers it. Measured
   on one two-way Compare page: 45 in-page citation links (href="#cfn-1" and the
   like) all landing underneath. Only ONE selector on the whole site declared a
   scroll-margin (.azpage .azsec), so every other in-page jump had the defect,
   including the footnote markers, which are the atlas's own evidence trail.

   --headh is measured from the real header by the master's chrome script; 68px
   is the same fallback the rest of this file uses. Applied to [id] so it covers
   every anchor target that exists now and every one added later. :target is not
   enough: it does not cover programmatic scrollIntoView, which the read-aloud
   highlighter uses. */
[id]{scroll-margin-top:calc(var(--headh,68px) + 12px)}

/* ---- member identity block -----------------------------------------------
   The glyph or photo on the left, the display name beside it, the username on
   its own line underneath with no parentheses. Approved by the owner on the
   public profile, then asked for across the site, so it moved out
   of PublicProfile.aspx's private style block and into here.

   Rendered by AvatarRender.IdentityBlock / IdentityMap, never hand-built, for the
   same reason the avatar markup itself is centralised: a second copy is how one
   surface silently stops matching the others.

   Centred rather than top-aligned: the text is two short lines against the mark,
   and flex-start hangs the name off the top and leaves the glyph looking dropped. */
/* TOP ALIGNED (owner, 2026-07-28, with a phone screenshot). Was centred, which was
   fine while the name was one line and looked wrong the moment it wrapped to three:
   the mark floated halfway down a tall text block instead of starting beside it. */
.pfhead{display:flex;align-items:flex-start;gap:14px;margin:0 0 4px}
.pfav{flex:0 0 auto;line-height:0}
/* min-width:0 or a long display name refuses to shrink and pushes its container
   wide. House rule: every flex row of text either wraps or gives its shrinkable
   member min-width:0. */
.pfid{min-width:0;display:flex;flex-direction:column}
/* break-word, NOT anywhere. "anywhere" permits a break at any character even when a
   whole word could have been moved to the next line, and it also lets the element
   report a min-content width of one character -- which is how "Interactive Lion"
   came out as "Intera / ctive / Lion" on a phone. break-word only splits a word that
   genuinely cannot fit on a line of its own, so ordinary names now wrap between
   words and only a truly unbreakable string is cut. */
.pfname{font-weight:600;overflow-wrap:break-word}
/* h2.page carries its own type scale from the page system; only kill the margin. */
.pfid h2.pfname{margin:0}
.pfname a{color:inherit;text-decoration:none}
.pfname a:hover,.pfname a:focus-visible{color:var(--gold);text-decoration:underline}
/* rem so it follows the A-/A+ control and OS text scaling. */
.pfuser{margin:2px 0 0;color:var(--muted);font-size:.9rem;overflow-wrap:break-word}
@media (max-width:480px){.pfhead{gap:10px}}
/* On a phone the avatar takes a fixed 6em and the name gets what is left, which at
   the page heading's own size is not enough for a word like "Interactive" -- it fit
   only by breaking mid-word. Stepping the heading down on narrow screens lets it wrap
   between words instead. In rem, so the A-/A+ control still moves it. */
@media (max-width:640px){
  .pfid h2.pfname{font-size:1.45rem;line-height:1.25}
  .pfuser{font-size:.85rem}
}

/* Facts group headings are flex rows so the read-aloud button js/panel-speak.js
   appends sits hard right (owner, 2026-07-28). .dspeak already carries
   margin-left:auto; without a flex row that does nothing and the button lands
   inline after the heading text. Both selectors on purpose: the entity page builds
   facts from .tf-grp-h and the category page from .factgrp-h, and the owner asked
   for the same treatment on both.
   gap in em so the space beside the button tracks the A-/A+ text control. */
.tf-grp-h,.factgrp-h{display:flex;align-items:center;gap:.6em}

/* ---- avatar cropper (owner, 2026-07-28) ---------------------------------- */
.cropwrap{margin:12px 0 0}
.cropwrap[hidden]{display:none}
.croparea{display:inline-block;line-height:0;border:1px solid var(--edge);border-radius:12px;overflow:hidden;background:var(--panel2)}
/* The canvas is a fixed pixel box on purpose: it is an image surface, not text, and
   its own coordinate system has to match what gets drawn. The controls beside it are
   in em and do scale. */
.croparea canvas{display:block;cursor:grab;touch-action:none}
.croparea canvas:active{cursor:grabbing}
.croparea canvas:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.croptools{display:flex;align-items:center;gap:.7em;margin:10px 0 0;flex-wrap:wrap}
.croptools label{color:var(--muted);font-size:.82rem}
.croptools input[type=range]{flex:1 1 12em;min-width:8em}
/* The chosen glyph, held but not yet saved. */
.glyphpick.on{border-color:var(--gold);color:var(--gold);background:var(--panel)}

/* The canonical range control lives with the other form controls further down
   this file (search for ".rangectl"), because it has to outrank the generic
   input rule there. */

/* ---- base ---- */
*{box-sizing:border-box} html,body{margin:0;max-width:100%}
/* The no-horizontal-scroll guard lives on HTML ONLY, never on BODY. HTML's overflow
   propagates to the viewport, so this still clips any stray horizontal overflow for
   the whole document -- but it leaves BODY a normal, non-scrolling box. Putting
   overflow-x:hidden on BODY as well makes BODY its own scroll container (a used
   overflow-x of hidden computes overflow-y to auto), and position:sticky then
   resolves against a box that never scrolls -- i.e. every sticky element on the site
   silently stops sticking. Header, .tray, .azjump and the entity facts drawer all
   depend on this rule staying off BODY. */
html{overflow-x:hidden}
/* HEADINGS BREAK RATHER THAN ESCAPE. A heading is the widest text on any page and
   is often one long word ("Denominations", "Correspondences", a transliterated
   deity name), and a single word cannot wrap at a space it does not have. With
   the clip above, a heading that overflows is not merely untidy: its right hand
   end is cut away with no scrollbar and no sign it is missing, which is exactly
   what was reported on a phone.

   break-word, not anywhere: this breaks a word ONLY when it genuinely cannot fit,
   so a normal multi word heading still wraps at its spaces instead of being split
   mid-word for no reason. Anything wanting different behaviour still overrides it. */
h1,h2,h3,h4,h5,h6{overflow-wrap:break-word}
/* BODY IS 1rem, NEVER px (owner, 2026-07-26). The A-/A+ control and OS text scaling
   both work by changing the ROOT font size; an absolute px here re-declared body copy
   at a fixed 15px forever, so headings, timestamps and captions scaled while the prose
   they labelled never moved -- inverted typography for exactly the reader who needs
   the control. The 93.75% base on html keeps the default rendering at the same 15px
   the site has always had. */
html{font-size:93.75%}
body{background:var(--ground);color:var(--ink);font-family:var(--sans);line-height:1.55;-webkit-font-smoothing:antialiased;font-size:1rem;background-image:radial-gradient(1200px 720px at 78% -10%, color-mix(in srgb,var(--gold) 9%,transparent), transparent 60%);min-height:100vh}
a{color:inherit}
.tnum{font-variant-numeric:tabular-nums}
:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:4px}

/* ---- site chrome: header / nav / controls ---- */
/* CAPPED font-size, and everything inside sized in em against it. The A-/A+ control
   scales the ROOT font from 85% to 200% (150% until 2026-08-05), which scaled the header's rem-sized wordmark
   and nav links right along with the page -- the row outgrew its width and .topin's
   flex-wrap dropped the control cluster onto a second line, spending a whole header's
   height on four right-aligned buttons and a band of empty space. min() lets the chrome
   grow with the reader's choice up to 18px (~112%) and then stop, while the page body
   keeps scaling the full way; the header simply never runs out of room. Text still
   responds to the setting, so this is a damper, not an accessibility opt-out.
   NOTE for anything added in here later: size it in em. A rem left inside the header
   goes on growing past the header that contains it. */
header.top{position:sticky;top:0;z-index:20;font-size:min(1rem,22px);background:color-mix(in srgb,var(--ground) 88%,transparent);backdrop-filter:blur(10px);border-bottom:1px solid var(--edge)}
.topin{max-width:none;margin:0 auto;padding:13px 34px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}
.brand{display:flex;align-items:center;gap:11px;text-decoration:none;color:inherit}
/* The wordmark's emblem. 2em is 30.0px against header.top's font-size:min(1rem,22px)
   and the html{font-size:93.75%} base, so it is unchanged at the default and grows with
   the wordmark beside it instead of standing still while the words get bigger. It was a
   flat 30px, and at the owner's stored 150 per cent the name had grown by half while the
   emblem had not, which is plain in a screenshot of the two sizes side by side. Lane
   rp-a11y, 2026-08-04, the same pass as the header icon cluster in chrome.css. */
/* AND IT TAKES NO POINTER EVENTS, WHICH IS WHY THE LOGO WAS NOT A LINK. Owner,
   2026-08-06: "the logo used to be clickable to bring you back to the home page.
   Someone forgot that during the port." The anchor was never lost; it is right
   there in Site.master with its href and its title, and clicking the WORDS has
   always worked. Clicking the EMBLEM did nothing, which is the half a reader
   actually aims at.

   The emblem is the house style, monochrome line art with fill="none", and an
   unfilled shape is not a hit target: the interior of a stroked circle is a hole
   the pointer falls through. So of a 30px emblem the only live parts were the 1px
   ring strokes and the 4px gold dot at the centre, and everything between them
   answered to nothing. Verified by hit-testing the live page: hovering the middle
   of the mark reports the centre dot, hovering a few pixels off it reports the svg
   itself, and a real press on either produced no click event at all while the same
   press on the wordmark navigated.

   pointer-events:none is the fix rather than a fill, because the emblem is
   aria-hidden decoration and the anchor behind it is already exactly the right
   shape: the whole brand box, emblem and both lines of text, becomes one target.
   It also cannot be undone by a future change to the artwork.

   THIS MATTERS MORE HERE THAN THE PIXELS SUGGEST. For a reader with reduced
   central vision, a small target that has to be hit precisely is the weakest
   interaction on any page, and a target that LOOKS solid while being mostly holes
   is worse than a small one: every miss reads as the site ignoring the reader. Anywhere
   else this pattern appears, decorative line art inside a link or a button, it has
   the same defect and the same one-line answer.

   OWNER REQUEST 990, 2026-08-20: color:var(--gold) added so every atlas's mark
   renders in that atlas's own accent. .brand{color:inherit} was letting the
   header's ink colour reach here uncontested, and Science's and Political's own
   logo.svg files (installed the same day for owner request 986) draw their
   strokes with stroke="currentColor" rather than a hardcoded var(--gold) the way
   Divinity's hardcoded compass does, so they were resolving to plain header ink
   instead of azure or violet. Setting color here gives currentColor something
   of this atlas's own to inherit, on every theme state, since --gold already
   resolves per atlas and per theme after owner request 989's toggle fix.
   Invisible on Divinity's own compass: its strokes name var(--gold) and
   var(--gold-soft) directly rather than currentColor, so this rule changes
   nothing it draws. */
.mark{width:2em;height:2em;flex:0 0 auto;pointer-events:none;color:var(--gold)}
/* SELECTOR CHANGED FROM h1 TO .brandname (p, not a heading), w-launchfix3-2241,
   2026-08-23 (board 1793/1812 reversal): the brand wordmark is no longer a
   heading element at all, since the page's own topic heading is the single
   correct h1. Same face, same size, same margin, same weight. */
.brand .brandname{font-family:var(--serif);font-size:1.16em;margin:0;font-weight:600}
.brand .sub{color:var(--faint);font-size:.68em;text-transform:uppercase;letter-spacing:.2em;margin-top:1px}
.spacer{flex:1 1 auto}
/* Header search. Sits between the nav and the display controls, and collapses before
   either of them does: on a narrow header the links and the theme toggle are the things
   a reader needs, and a search box squeezed to forty pixels is worse than none. The
   mobile menu carries its own copy. */
/* THE BUTTON TAKES ITS HEIGHT FROM THE ROW AND ITS WIDTH IN em: the search
   button was reported smaller than the search bar when scaled.
   The field sizes its text in em, so it grows with the reader's text size; the
   button was pinned at 34 by 34 with a 15 by 15 icon, so it did not. At the
   default the field happens to compute to about 34 and the two matched BY
   COINCIDENCE; at the owner's stored 150 the field stood visibly taller and the
   join read as a step rather than as one control.

   This is the project's own accessibility rule being broken in its own words,
   never a fixed px box around text that scales, and it matters more than it
   looks: for a reader with central-field loss, a small hover target is the
   weakest interaction and a button that refuses to grow is the worst element in
   the header to pin.

   THE FIX IS THREE DECLARATIONS. align-items:stretch makes the row's items take
   the row's height, which is the FIELD's height because the field is the taller
   of the two, so the button now equals it at every text size without either
   knowing the other's measurement. The button's own height declaration is gone
   rather than converted, because a value here would be a second opinion about a
   height the field already decides. font:inherit is load bearing: a button does
   NOT inherit font-size, so without it every em below would resolve against the
   UA's own 13.33px, which does not scale, and the fix would not work at all.

   2.2667em against header.top's font-size:min(1rem,22px) and the
   html{font-size:93.75%} base is 34.0px, and the icon's 1em is 15.0px, so
   NOTHING MOVES AT THE DEFAULT: this is a scaling fix, not a resize, which is
   what layout parity asks of a ported layout. Recorded and deliberately NOT
   changed: flex:0 1 240px below is a pixel basis that does not scale either,
   which is real and separate, and touching it moves the whole header row.

   The identical defect is in the first edition's site.css character for
   character. It is NOT fixed there: lanes are diffing served markup against that
   edition as the reference, and moving the reference mid-sweep corrupts the
   comparisons. */
.hdrsearch{display:flex;align-items:stretch;gap:0;flex:0 1 240px;min-width:0;margin:0 4px}
.hdrsearch input{flex:1 1 auto;min-width:0;background:var(--panel2);border:1px solid var(--edge);
  border-right:0;border-radius:9px 0 0 9px;color:var(--ink);font-size:.84em;padding:8px 11px;font-family:inherit}
.hdrsearch input::placeholder{color:var(--faint)}
.hdrsearch input:focus{outline:none;border-color:var(--gold)}
.hdrsearch button{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;
  font:inherit;width:2.2667em;background:var(--panel2);border:1px solid var(--edge);border-left:0;
  border-radius:0 9px 9px 0;color:var(--muted);cursor:pointer}
.hdrsearch button svg{width:1em;height:1em;flex:none}
.hdrsearch button:hover{color:var(--gold);border-color:var(--gold)}
/* Focus moves the gold to BOTH halves, so the control reads as one field. */
.hdrsearch:focus-within input,.hdrsearch:focus-within button{border-color:var(--gold)}
/* Hide the header search ONLY when the burger that carries the replacement
   search form exists. These two breakpoints were 900px and 760px, which left a
   761-900px band (tablet portrait, half-snapped windows, and crucially high
   browser zoom) with NO route to search at all: no header form, no burger, and
   no nav or footer Search link. If the row runs tight in the band, .topin wraps,
   which is the documented fallback. */
@media(max-width:760px){.hdrsearch{display:none}}
.navlinks{display:flex;gap:4px;align-items:center}
.navlinks a{font-size:.84em;color:var(--muted);text-decoration:none;padding:8px 12px;border-radius:9px;transition:.16s}
.navlinks a:hover{color:var(--ink);background:var(--panel2)}
/* Header group menus (Site.master, 2026-07-27). The header row stopped fitting once
   the site had seven content links plus Report, the profile menu, three icons and a
   search box, and every page added made it worse; the links are now two disclosures,
   so the row's width no longer grows with the site.

   Structurally the profile menu's twin, and for the same load-bearing reason: the
   panel is absolutely positioned inside a relative <details>, so OPENING one cannot
   reflow the header row. A menu that pushed the row wider on open would recreate the
   wrapping this exists to end. The summary is deliberately styled to match
   .navlinks a exactly, so a grouped item and a bare link are one visual family. */
.navlinks details.hdrmenu{position:relative;flex:0 0 auto}
.navlinks details.hdrmenu>summary{list-style:none;cursor:pointer;
  font-size:.84em;color:var(--muted);padding:8px 12px;border-radius:9px;transition:.16s;
  display:inline-flex;align-items:center;gap:5px}
.navlinks details.hdrmenu>summary::-webkit-details-marker{display:none}
/* The caret is a pseudo-element rather than the native marker, which cannot be
   positioned consistently across browsers and reads as a bullet to some readers.
   aria-hidden by construction: generated content is not in the accessibility tree. */
.navlinks details.hdrmenu>summary::after{content:"";width:.42em;height:.42em;
  border-right:1.5px solid currentColor;border-bottom:1.5px solid currentColor;
  transform:rotate(45deg) translate(-.1em,-.1em);opacity:.75;transition:transform .16s}
.navlinks details.hdrmenu[open]>summary::after{transform:rotate(225deg) translate(.05em,.05em)}
.navlinks details.hdrmenu>summary:hover{color:var(--ink);background:var(--panel2)}
.navlinks details.hdrmenu>summary:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.navlinks details.hdrmenu[open]>summary{color:var(--ink);background:var(--panel2)}
/* ANCHORED TO ITS RIGHT EDGE, NOT ITS LEFT, AND THAT IS THE TEXT-SIZE FIX. Lane
   textsize-200, 2026-08-05, raising the reader's ceiling from 150 to 200 per cent.
   The panel is min-width:12rem, so it grows with the setting: 180px at the default,
   270px at 200 per cent. Anchored left:0 it grew RIGHTWARDS out of a summary already
   sitting near the right end of the header, and measured on the shell at 200 per cent
   in a 1045px viewport the Interact panel ended 44px past the edge and the About panel
   148px past it. Nothing announced that. The shell is overflow:hidden and every framed
   page is html{overflow-x:hidden}, so the right-hand end of a menu was simply cut off,
   with no scrollbar and no way to reach the labels.
   Right-anchored it grows leftwards into the header it belongs to, which cannot leave
   the viewport at any setting, and .navlinks is display:none below 1024px so the panel
   never exists at a width narrow enough for 12rem itself to be the problem. */
.hdrmpanel{position:absolute;left:auto;right:0;top:calc(100% + 8px);z-index:220;min-width:12rem;
  display:flex;flex-direction:column;padding:6px;background:var(--panel);
  border:1px solid var(--edge2);border-radius:12px;box-shadow:var(--shadow)}
/* Inside the panel the links are full rows with a touch-sized hit area, not the
   compact header pills. min-width:0 + normal wrapping so a long label at 200% text
   wraps instead of being clipped by the panel edge. */
.navlinks .hdrmpanel a{display:block;min-width:0;padding:10px 13px;border-radius:8px;
  font-size:.92em;color:var(--ink);white-space:normal}
.navlinks .hdrmpanel a:hover{background:var(--panel2);color:var(--gold)}
/* Profile menu. A native disclosure, so it opens with no script. The panel is absolutely
   positioned, so opening it cannot reflow the header row - which is the point, since
   this exists because the row wrapped.

   ITS CONTAINING BLOCK IS THIS ELEMENT AGAIN, restored after a
   report that the menu stopped opening under its icon and opened at the far right of
   the screen instead. An earlier pass had removed position:relative here, the
   third of three header dropdowns in one pass, reasoning that right:0
   against a trigger is only safe while that trigger sits near the right end of the
   header, and that .topin wraps at 200 per cent text so it does not.

   The reasoning is sound and the side effect was not. .hdricons sits BETWEEN the nav
   and the search box, not at the end of the row, so anchoring to header.top does not
   put the panel under the icon at large text sizes: it puts it against the viewport
   edge at EVERY size, which is what was seen. A dropdown that never touches its
   trigger is a worse defect than one that might crowd an edge in a case nobody has hit.

   What still guards the case that pass worried about: chrome.css's max-width:760px
   block pins this panel to the viewport under the measured header, which is where a
   genuinely cramped row ends up.

   THAT SENTENCE WAS FALSE WHEN IT WAS WRITTEN AND IS TRUE NOW, corrected
   after a screenshot showed it. The phone block pinned .notifpanel alone; .profpanel
   was never named in it, so on an iPhone this panel ran off the LEFT edge with its first
   characters cut away, clipped silently by html{overflow-x:hidden}. The worry recorded
   above was the right worry, and the comment asserting it had already been handled is
   what stopped anybody measuring it. Read the long note beside the new rule in
   chrome.css. Above that width the panel is about 12em and
   opens leftward from an icon the brand and the nav both precede, so it has room. If a
   wrapped row is ever measured actually crowding it, fix where .hdricons sits in the
   row rather than detaching this panel from its trigger again. */
.profmenu{position:relative;flex:0 0 auto;margin-left:2px}
.profmenu>summary{list-style:none;cursor:pointer;display:inline-flex;align-items:center;justify-content:center}
.profmenu>summary::-webkit-details-marker{display:none}
/* THE AVATAR RING HAS THE STEPPER BUTTONS' EXACT DISEASE and is fixed with them: the
   ring was a flat 30px while the initial inside it is font-size:.9em and does grow, so
   the letter tightened against its own circle every time the reader enlarged the page.
   It is not in the five controls lane vc-chrome listed, and it is the same defect in the
   same cluster, so leaving it would make the header inconsistently right, which a reader
   cannot detect. 2.2222em is 30.0px against this element's OWN .9em text, which is why
   the number differs from .mark's 2em for the same 30 pixels. Lane rp-a11y, 2026-08-04. */
.profini{display:inline-flex;align-items:center;justify-content:center;width:2.2222em;height:2.2222em;
  border-radius:99px;background:var(--panel2);border:1px solid var(--edge2);color:var(--gold);
  font-family:var(--serif);font-size:.9em;font-weight:600;line-height:1;transition:.14s}
.profmenu>summary:hover .profini{border-color:var(--gold);color:var(--ink)}
.profmenu[open]>summary .profini{border-color:var(--gold);background:var(--panel)}
.profmenu>summary:focus-visible .profini{outline:2px solid var(--gold);outline-offset:2px}
/* right:0 against .profmenu, so the panel's right edge meets the icon's and it opens
   leftward underneath it. The 8px this carried while it was anchored to header.top was
   a gap from the VIEWPORT edge and is wrong against a trigger. min-width stays in em so
   it grows with the A-/A+ control, which is the half of the textsize-200 pass that was
   right and is kept. */
.profpanel{position:absolute;right:0;top:calc(100% + 8px);z-index:220;min-width:12.2667em;
  display:flex;flex-direction:column;padding:6px;background:var(--panel);
  border:1px solid var(--edge2);border-radius:12px;box-shadow:var(--shadow)}
/* The name lives in the panel, not the header row: it is the longest and least
   predictable string in the nav, and what made the row wrap for one member and not
   another. Ellipsised so a long display name cannot widen the panel indefinitely. */
/* 15.3333em is 230.0px at the default. em rather than px so the cap grows with the
   panel and the name is ellipsised at the same POINT in the string at every text size,
   rather than a few characters earlier on each step up (lane textsize-200, 2026-08-05).
   The nowrap stays: this is a single display name whose job is to identify the account,
   the panel's other rows are the navigation, and an ellipsis on one line is what stops
   an unpredictable string setting the panel's width. */
.profwho{padding:7px 10px 9px;margin-bottom:4px;border-bottom:1px solid var(--edge);
  color:var(--muted);font-size:.78em;max-width:15.3333em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.profpanel a{padding:8px 10px;border-radius:8px;color:var(--muted);font-size:.86em;text-decoration:none;white-space:nowrap}
.profpanel a:hover{background:var(--panel2);color:var(--ink)}
.profpanel a.profadmin{color:var(--gold-soft)}
.profpanel a.profout{margin-top:4px;border-top:1px solid var(--edge);border-radius:0;padding-top:10px}
/* The signed-out Sign In link in the header's .hdricons cluster. Declarations are
   .navlinks a's exactly (the first edition styled its Sign in link with that rule),
   restated under this class because the link cannot live inside .navlinks here: the
   nav is data and .navlinks hides at phone widths, where the sign-in
   affordance must survive the way the icon cluster does. em, not rem, because the
   header caps its own font-size and this must stop growing with it. */
.hdrsignin{font-size:.84em;color:var(--muted);text-decoration:none;padding:8px 12px;border-radius:9px;transition:.16s;white-space:nowrap;flex:0 0 auto}
.hdrsignin:hover{color:var(--ink);background:var(--panel2)}
.ghost{background:var(--panel);border:1px solid var(--edge);color:var(--muted);border-radius:9px;width:36px;height:36px;display:grid;place-items:center;cursor:pointer;font-size:1em}
.ghost:hover{color:var(--ink);border-color:var(--edge2)}
.ghost.xburger{display:none}

/* ---- display settings drawer (Site.master) ----
   The A-/A+ pair and the light/dark toggle that used to sit loose in the header, now
   behind one trigger in the .hdricons cluster beside the bell and the envelope. The
   trigger carries .hdricon and adds NO box of its own -- no border, no resting
   background -- so it is the same bare 38px glyph as its neighbours and the three
   controls read as one row. A native <details>, so it opens with no script and the
   panel is absolutely positioned against it: opening the drawer can never reflow the
   header row, which matters here more than anywhere, since the row wrapping is the
   whole reason this exists. The panel itself is the notification drawer's twin
   (.hdrnotif/.notifpanel in Site.master): right-aligned dropdown, same phone pinning. */
/* NOT position:relative, AND THAT IS DELIBERATE. Lane textsize-200, 2026-08-05.
   It was relative until then, which made the trigger the panel's containing block, so
   .disppanel's right:0 lined the panel's right edge up with this 38px glyph and the
   panel hung leftwards off it. That is fine while the trigger sits near the right end
   of the header and fails completely once it does not.

   What it cost, measured on the shell at 200 per cent in a 1045px viewport. The panel
   is width:16.6667em against header.top's capped font, so it grows with the reader:
   250px at the default, 367px at 200 per cent. At 200 per cent .topin has wrapped, the
   icon cluster starts row two, and the trigger's right edge is at x=259. 259 minus 367
   put the panel's left edge at -107: the whole A- step, "Decrease text size", sat at
   -94 to -38, entirely outside the viewport, and the System theme option with it. The
   shell is overflow:hidden, so there was no scrollbar and no sign anything was missing.
   THE READER WHO HAD JUST REACHED 200 PER CENT COULD NOT REACH THE CONTROL THAT TAKES
   HIM BACK DOWN, which is the worst thing raising the ceiling could have done.

   Static here makes header.top the containing block instead, so right:0 is the header's
   right edge and top:calc(100% + 8px) is the height of the real header including any
   wrapped rows. Both are correct at every text size with no measurement and no script,
   which the trigger anchor cannot be. Nothing else in this file positions anything
   against .hdrdisp. The cost is that the drawer now opens at the header's right edge
   rather than directly under its glyph at the default size; that is a position change,
   and an unreachable button is not. */
/* position:relative restored 2026-08-05 with .profmenu's, and for the same reason: the
   textsize-200 pass detached all three .hdricons dropdowns from their triggers, and
   because the cluster sits mid row rather than at the end of it, header.top anchoring
   put every one of them against the viewport edge at every text size. This one matters
   most of the three: it is the A-/A+ and theme drawer, so a reader enlarging the page
   is exactly the reader whose control had wandered off. The full reasoning is written
   out above .profmenu. */
.hdrdisp{position:relative;display:inline-flex;flex:0 0 auto}
.hdrdisp>summary{list-style:none;cursor:pointer}
.hdrdisp>summary::-webkit-details-marker{display:none}
/* Open reads like the bell's hover, one step further: gold glyph AND the panel2 wash,
   so the trigger stays visibly the source of the panel hanging off it. Two-part
   selector to outrank .hdricon:hover, which is defined later (Site.master's inline
   <style>) and would otherwise win on equal specificity. */
.hdrdisp[open]>.dispbtn{color:var(--gold);background:var(--panel2)}
.hdrdisp>summary:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* 16.6667em is 250.0px at the default. The panel is converted along with the controls
   inside it rather than as a separate improvement: the stepper buttons below now grow,
   and a growing pair of buttons inside a panel frozen at 250px is a defect this pass
   would have CAUSED. max-width keeps it inside a phone screen, which the fixed width
   used to do by accident and a growing one would not. */
.disppanel{position:absolute;right:0;top:calc(100% + 8px);z-index:220;width:16.6667em;
  max-width:calc(100vw - 16px);
  background:var(--panel);border:1px solid var(--edge2);border-radius:12px;box-shadow:var(--shadow)}
.dispsec{padding:11px 13px}
.dispsec+.dispsec{border-top:1px solid var(--edge)}
.disphd{color:var(--faint);font-size:.68em;text-transform:uppercase;letter-spacing:.14em;margin-bottom:9px}
.dispsize{display:flex;align-items:center;gap:8px}
/* THE TWO CONTROLS WHOSE WHOLE JOB IS TO MAKE THE PAGE BIGGER WERE THE TWO THAT GOT
   RELATIVELY TIGHTER EVERY TIME A READER PRESSED THEM, and that is why this one was fixed
   first of six. The box was a flat 38 by 34 while the letter inside it
   is font-size:.8em on one and 1.15em on the other and does grow, so at
   150 per cent the letter is about 25 pixels tall inside a 34 pixel box: most of
   the box, with the descender room gone.

   WHY THERE ARE THREE DIFFERENT em NUMBERS FOR ONE 38 BY 34 BOX, and it is not an
   oversight. An em on width resolves against the element's OWN font-size, and these two
   buttons deliberately carry different font-sizes, so one shared em box would have made
   A minus smaller than A plus, which is the opposite of the point. Each rule therefore
   carries the box arithmetic for its own text size:

       base, no modifier   15.00px text   2.5333em x 2.2667em   = 38.0 x 34.0
       .sm at .8em         12.00px text   3.1667em x 2.8333em   = 38.0 x 34.0
       .lg at 1.15em       17.25px text   2.2029em x 1.9710em   = 38.0 x 34.0

   So the pair is identical to the pixel at every text size, unchanged at the default,
   and both grow in step with their own letters. The header's font cap bounds the growth
   at 22px of base text, so the box tops out near 55.7 by 49.9 and the panel above,
   converted with it, still holds the row. Changing either font-size means recomputing
   that rule's two numbers; the base is 15px, the html 93.75% default. */
.dispstep{flex:0 0 auto;width:2.5333em;height:2.2667em;background:var(--panel2);border:1px solid var(--edge);
  color:var(--muted);border-radius:9px;cursor:pointer;font-family:var(--serif);line-height:1}
.dispstep.sm{font-size:.8em;width:3.1667em;height:2.8333em}
.dispstep.lg{font-size:1.15em;width:2.2029em;height:1.971em}
.dispstep:hover:not(:disabled){color:var(--gold);border-color:var(--gold)}
/* Disabled at the 85/150 clamp rather than silently swallowing the click, which is what
   the two bare header buttons did -- there was no readout to show nothing had happened. */
.dispstep:disabled{opacity:.38;cursor:default}
.dispval{flex:1 1 auto;text-align:center;color:var(--ink);font-size:.86em;font-variant-numeric:tabular-nums}
.dispreset{display:block;width:100%;margin-top:9px;padding:7px 10px;background:none;border:0;
  border-radius:8px;color:var(--gold);font:inherit;font-size:.78em;cursor:pointer;text-align:center}
.dispreset:hover:not(:disabled){background:var(--panel2)}
.dispreset:disabled{color:var(--faint);cursor:default}
.dispseg{display:flex;border:1px solid var(--edge);border-radius:9px;overflow:hidden}
.dispopt{flex:1 1 0;min-width:0;padding:8px 4px;background:var(--panel2);border:0;
  border-left:1px solid var(--edge);color:var(--muted);font:inherit;font-size:.76em;
  cursor:pointer;white-space:nowrap}
.dispopt:first-child{border-left:0}
.dispopt:hover{color:var(--ink)}
.dispopt.on{background:color-mix(in srgb,var(--gold) 16%,transparent);color:var(--gold);font-weight:650}

/* The header control cluster. It used to carry four buttons -- A-, A+, theme, burger --
   which had been direct children of .topin and wrapped one at a time as the text size
   grew; grouping them fixed which line they shared and margin-left:auto fixed where that
   line sat. Three of the four have since moved into the display drawer up in .hdricons,
   leaving only the burger, which is itself hidden above 760px. So the whole cluster is
   display:none on desktop rather than sitting there as a zero-width flex item still
   claiming a 14px gap at the end of the row. */
.hdrctl{display:none;align-items:center;gap:8px;flex:0 0 auto;margin-left:auto}
.brand{min-width:0}
/* The spacer exists only to push the desktop nav right. On phones the nav is hidden,
   and leaving a flexible element in the wrap made the button row's position depend on
   which line the spacer happened to land on. */
@media(max-width:760px){ .topin>.spacer{display:none} }
.xmobile{display:none;width:100%;border-top:1px solid var(--edge);background:color-mix(in srgb,var(--ground) 94%,transparent)}
/* Search sits first in the open menu: it is the reason most people open it. */
.xsearch{display:flex;gap:8px;padding:12px 16px;border-bottom:1px solid var(--edge)}
.xsearch input{flex:1 1 auto;min-width:0;background:var(--panel2);border:1px solid var(--edge);
  border-radius:9px;color:var(--ink);font-size:.92em;padding:10px 12px;font-family:inherit}
.xsearch input:focus{outline:none;border-color:var(--gold)}
.xsearch button{flex:0 0 auto;background:var(--gold-bg);color:var(--gold-bg-ink, #1a1508);border:0;border-radius:9px;
  padding:10px 16px;font-weight:650;font-size:.88em;cursor:pointer;font-family:inherit}
.xmobile a{display:block;padding:12px 20px;color:var(--ink);text-decoration:none;border-top:1px solid var(--edge)}
.xmobile a:first-child{border-top:none}
/* Group headings in the open mobile menu (2026-07-27, alongside the desktop header
   menus). The mobile menu stays FLAT -- a disclosure inside an already-open menu is
   just an extra tap -- so it takes the grouping as labels instead. A div and not a
   heading element on purpose: these caption a run of links inside a <nav>, and
   injecting h3s here would put four phantom entries in the page's heading outline
   that a screen-reader user navigating by heading would land on. */
.xmobile .xgrp{padding:14px 20px 5px;border-top:1px solid var(--edge);
  color:var(--faint);font-size:.7em;text-transform:uppercase;letter-spacing:.14em}
.xmobile .xgrp+a{border-top:none}
/* THE OPEN MENU IS ITS OWN SCROLL CONTAINER, and it has to be.
   Owner QA 2026-07-27: "when I expand the menu on mobile, it goes below the fold and
   I cannot scroll."

   The cause is structural, not a missing overflow: .xmobile lives INSIDE
   <header class="top">, which is position:sticky with top:0. A sticky element that
   is taller than the viewport pins the moment its top reaches 0 and then never moves
   again, so everything in it below the viewport's bottom edge is simply unreachable
   -- the page scrolls, the menu does not come with it. It was latent for as long as
   the menu was short enough to fit; adding Path Finder, A to Z, All Systems and the
   three group headings took it past a phone viewport and made it real.

   Capping the menu and letting it scroll fixes it at the level it breaks, and keeps
   working however many items the menu grows to, which is the whole point of having
   grouped the header in the first place.

   Two max-heights on purpose, and the fallback is deliberately the SHORTER one.
   dvh is the correct unit: it tracks the viewport as the mobile URL bar retracts and
   returns, so calc(100dvh - headh) is exactly the room below the header. Every
   browser since Safari 15.4 takes that line. The vh line above it is for the ones
   that do not, and it cannot be 100vh: vh is frozen at the URL-bar-RETRACTED height,
   so the menu's own scrollport would extend below the visible screen and its last
   items would be unreachable by scrolling the menu -- the identical failure this
   rule exists to fix, just harder to see. 85vh gives back roughly a URL bar. A
   little short is recoverable; too tall is the bug. (Same lie about vh the Messages
   thread scroller documents.)

   --headh is measured by the master's chrome script and already has the open menu's
   own height subtracted back out, so it is the header WITHOUT this menu, which is
   what makes the arithmetic stable rather than a feedback loop. */
.xmobile.open{display:block;
  max-height:calc(85vh - var(--headh,68px));
  max-height:calc(100dvh - var(--headh,68px));
  overflow-y:auto;
  /* Without this, flicking past the end of the menu scrolls the PAGE behind it, so
     the reader closes the menu and finds themselves somewhere else. */
  overscroll-behavior:contain;
  -webkit-overflow-scrolling:touch;
  scrollbar-width:thin;
  scrollbar-color:color-mix(in srgb,var(--gold) 45%,transparent) transparent}
.xmobile.open::-webkit-scrollbar{width:8px}
.xmobile.open::-webkit-scrollbar-track{background:transparent}
.xmobile.open::-webkit-scrollbar-thumb{border-radius:99px;border:2px solid transparent;
  background:color-mix(in srgb,var(--gold) 40%,transparent);background-clip:content-box}
/* THE NAV COLLAPSES INTO THE BURGER AT 1024px, NOT 760 (owner, 2026-07-28: "is there
   anything we can do about the top menu wrapping?").

   The header row is brand + five nav items + Report + the profile menu + three icons +
   a search box. Grouping the content links into two disclosures on 2026-07-27 bought
   back a lot of width, but between 761 and 1024 -- tablet portrait, a half-snapped
   window, and any desktop at high browser zoom -- it still did not fit, and .topin's
   flex-wrap dropped the cluster onto a second row. Wrapping is the documented
   fallback, but a fallback that fires on a common width is just the layout.

   Collapsing earlier is the honest fix: below 1024 the links live in the burger menu,
   which already carries every one of them plus its own search. The header search stays
   visible down to 760, so the 761-1024 band gains the burger WITHOUT losing search --
   the band that an earlier fix (see the .hdrsearch note above) was careful to keep a
   search route for. */
@media(max-width:1024px){
  .navlinks{display:none} .ghost.xburger{display:grid}
}
@media(max-width:760px){
  .navlinks{display:none} .ghost.xburger{display:grid}
  /* Phone header on ONE line: brand left, controls right. The brand used to be
     unshrinkable, so it claimed its full natural width and forced the button cluster
     onto a second row -- roughly 90px of header spent on nothing. Letting the brand
     shrink (min-width:0 on both the flex item and its inner text block) and trimming
     the controls buys enough room for both to share the row. At very large text sizes
     .topin still wraps, which is the right fallback: the cluster stays intact and
     drops as a unit rather than truncating the wordmark. */
  .topin{padding:10px 16px;gap:10px}
  .brand{flex:1 1 auto;min-width:0}
  .brand>div{min-width:0}
  .brand .brandname{font-size:1.05em}
  .brand .sub{font-size:.58em;letter-spacing:.1em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .hdrctl{display:flex;gap:6px}
  .ghost{width:32px;height:32px}
  /* No size override for the drawer trigger: it takes .hdricon's phone size along with
     the bell and the envelope it sits beside. */
  /* SiteUpdate 46, codex-finalqa-20260815: pinned under the measured header rather than
     hanging off the trigger, so the panel cannot run off a 360px screen -- the same
     treatment as the notification panel in Site.master, and now the SAME DECLARATION
     SHAPE, not just the same intent.

     THIS USED TO READ right:8px;left:auto WITH THE WIDTH LEFT AT 16.6667em, CAPPED ONLY
     BY max-width:calc(100vw - 16px). That leaves the box's LEFT edge computed indirectly
     (containerWidth - right - width), so a left overflow is only ever as safe as three
     separate values agreeing: the panel's own width, the max-width clamp, and whatever
     box "right:8px" actually resolves against. .notifpanel and .profpanel just above
     were already fixed onto a bulletproof shape after the owner's own iPhone screenshot
     caught them running off the LEFT edge for the identical reason (see the long note on
     .profpanel above); .disppanel was never brought into line with them, and the codex
     QA pass reproduced the exact same left-edge failure on it at 375x812, 200 per cent
     text: left edge near -144px, the decrease-text-size button entirely offscreen.

     left:8px;right:8px;width:auto makes BOTH edges explicit and drops the indirect
     arithmetic entirely: the box is pinned 8px in from both sides of whatever it is
     actually positioned against, so its left edge can never go negative, regardless of
     which box "right" was resolving against before. At this breakpoint the panel's
     natural width (16.6667em, capped at 22px of header font) already exceeds the
     available space, so the rendered box is numerically identical to before; only the
     ARITHMETIC that gets there is now safe by construction rather than by three values
     happening to agree. */
  .disppanel{position:fixed;left:8px;right:8px;top:calc(var(--headh,68px) + 8px);
    width:auto;max-width:calc(100vw - 16px)}
}

/* ---- page layout wrappers ---- */
/* EXTENDED: the PAGE is fluid so the card grids use the whole screen, but PROSE stays
   capped (see .dvpage .lead) because a line of text 1800px long is measurably harder to
   read -- the eye loses its place returning to the next line. Grids gain from width;
   paragraphs do not. Revert to the original by setting max-width:1080px here. */
.wrap{max-width:none;margin:0 auto;padding:0 34px 40px}
.authwrap{max-width:520px;margin:0 auto;padding:48px 20px}
.adminwrap{max-width:1120px;margin:0 auto;padding:28px 20px 70px}
/* A 320px phone cannot afford 68px of gutter: with the facts-handle gutter added on
   entity pages it left ~200px of prose. (Mobile audit, 2026-07-26, finding G3.) */
@media (max-width:560px){.wrap{padding:0 16px 32px}}

/* ---- typography helpers ---- */
.eyebrow{color:var(--gold);text-transform:uppercase;letter-spacing:.22em;font-size:.7rem;font-weight:600;margin-bottom:10px}
h2.page{font-family:var(--serif);font-size:2rem;margin:0 0 6px;font-weight:600}
.lede{color:var(--muted);font-size:.92rem;margin:0 0 8px}
.muted{color:var(--muted);font-size:.85rem}

/* ---- sub navigation (admin section tabs) ---- */
.subnav{display:flex;gap:6px;flex-wrap:wrap;margin:18px 0 24px}
.subnav a{font-size:.86rem;color:var(--muted);text-decoration:none;padding:9px 14px;border-radius:10px;border:1px solid var(--edge)}
.subnav a:hover{color:var(--ink);background:var(--panel2)}
.subnav a.on{color:var(--gold-bg-ink, #1a1508);background:var(--gold-bg);border-color:var(--gold);font-weight:650}
/* Admin nav dropdowns (owner, 2026-07-26): 21 links in one flat strip were unscannable,
   the more so with a vision impairment. Each group is a native <details> menu -- summary
   announces expanded/collapsed to screen readers with no ARIA wiring -- and the group
   holding the current page names it in the closed summary ("Commerce: Reports"), so
   where-am-I is answered without opening anything. Desktop floats the panel; phones lay
   it in-flow so an open menu pushes the page instead of covering it. */
.subnav details.snmenu{position:relative}
.subnav details.snmenu>summary{font-size:.86rem;color:var(--muted);cursor:pointer;padding:9px 14px;border-radius:10px;border:1px solid var(--edge);list-style:revert;display:inline-flex;align-items:center;gap:7px}
.subnav details.snmenu>summary:hover{color:var(--ink);background:var(--panel2)}
.subnav details.snmenu>summary:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.subnav details.snmenu>summary .snhere{color:var(--gold);font-weight:650}
.subnav details.snmenu.on>summary{border-color:var(--gold);color:var(--ink)}
.subnav details.snmenu[open]>summary{background:var(--panel2);color:var(--ink)}
.snpanel{position:absolute;left:0;top:calc(100% + 6px);z-index:30;min-width:13rem;background:var(--panel2);border:1px solid var(--edge2);border-radius:12px;padding:6px;box-shadow:var(--shadow);display:flex;flex-direction:column;gap:2px}
.subnav .snpanel a{display:block;border:none;border-radius:8px;padding:11px 14px;font-size:.9rem}
.subnav .snpanel a.on{color:var(--gold-bg-ink, #1a1508);background:var(--gold-bg);font-weight:650}
/* ---- per-surface privacy levels (AccountSettings, 2026-07-27) ------------
   Fifteen three-way radio groups: Private / Friends / Public, replacing
   fifteen checkboxes that could only say everyone-or-nobody.

   The real radio inputs are NOT display:none. They are clipped to a 1px box, which
   keeps them focusable and in the accessibility tree while the label span does the
   drawing; display:none would remove them from the tab order entirely and leave a
   keyboard user unable to change a privacy setting at all. The visible state comes
   off :checked and :focus-visible on the input via a sibling selector, so focus is
   always drawn. */
.vislist{display:flex;flex-direction:column;gap:6px;margin-top:10px}
.visrow{display:flex;flex-wrap:wrap;align-items:center;gap:8px 14px;
  border:0;margin:0;padding:8px 0;border-bottom:1px solid var(--edge)}
.visrow:last-child{border-bottom:0}
.vislabel{flex:1 1 10rem;min-width:0;padding:0;font-size:.92rem;color:var(--ink);
  overflow-wrap:anywhere}
.visopts{display:flex;flex:0 0 auto;gap:0;border:1px solid var(--edge2);
  border-radius:9px;overflow:hidden}
.visopt{position:relative;display:inline-flex}
/* Clipped, not hidden. See the note above. */
.visopt input{position:absolute;width:1px;height:1px;padding:0;margin:0;
  border:0;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden;white-space:nowrap}
.visopt span{display:inline-block;padding:.45em .85em;font-size:.82rem;
  color:var(--muted);cursor:pointer;transition:.12s;border-left:1px solid var(--edge)}
.visopt:first-child span{border-left:0}
.visopt span:hover{color:var(--ink);background:var(--panel2)}
.visopt input:checked+span{background:var(--gold-bg);color:var(--gold-bg-ink, #1a1508);font-weight:650}
.visopt input:focus-visible+span{outline:2px solid var(--gold);outline-offset:-2px}
/* Below this the label and the four options each want a full row, or the segmented
   control squeezes the surface name down to an ellipsis on a phone. */
@media(max-width:520px){
  .visrow{flex-direction:column;align-items:stretch;gap:6px}
  .vislabel{flex:1 1 auto}
  .visopts{width:100%}
  .visopt{flex:1 1 0}
  .visopt span{width:100%;text-align:center;padding:.6em .4em}
}

/* ---- friend groups inside a visibility row (2026-07-30) ------------------
   The group checkboxes that qualify a surface set to Friend Groups. They take a
   FULL ROW under the segmented control (flex-basis:100%) rather than sitting
   beside it: they belong to the option above them, and a wrapped tail of
   checkboxes hanging off the end of a segmented control reads as neither.

   Ordinary checkboxes, deliberately. The segmented control above is a choice of
   one and looks like one; this is a choice of several and has to look different,
   or somebody ticks a second group expecting the first to clear. */
/* The wrapper the group pickers were put inside on 2026-07-31, so one `hidden`
   attribute could reveal or hide the whole block when the radio changes.
   flex:1 1 100% IS THE WHOLE RULE AND IT IS NOT OPTIONAL. .visrow is a flex row,
   and .visgroups and .vishelp each carried their own flex:1 1 100% -- that basis is
   what put them on a line of their own UNDERNEATH the radios. Wrapping them in a
   plain div made the wrapper the flex item instead, with no basis of its own, so it
   sat inline to the RIGHT of the four buttons and squeezed them. The basis has to
   move out to whatever the fieldset's direct child is; the children keeping theirs
   is harmless, since they are laid out inside this box now. */
.vispanel{flex:1 1 100%;min-width:0}
.visgroups{flex:1 1 100%;display:flex;flex-wrap:wrap;gap:6px 14px;padding:2px 0 0}
.visgroup{display:inline-flex;align-items:center;gap:.4em;font-size:.85rem;
  color:var(--muted);cursor:pointer;min-width:0}
.visgroup span{overflow-wrap:anywhere}
.visgroup input:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.visgroup:hover{color:var(--ink)}
.vishelp{flex:1 1 100%;font-size:.8rem;color:var(--faint);margin:2px 0 0}

/* ---- avatar glyph picker (AccountProfile, 2026-07-27) --------------------
   A member may wear one of the atlas's own line-art symbols instead of uploading a
   photo. Everything here is em-based against the reader's text size: the grid's
   track width, the tile, the drawing. A fixed-px tile would have frozen the glyph
   at one size while the label under it grew, which is precisely the pattern the
   project's accessibility rules forbid. */
/* margin-BOTTOM as well as top (owner, 2026-07-30: "Or Choose A Glyph From The Atlas
   needs a space under it between the save"). Collapsed -- which is how the card opens
   -- the summary sat hard against the Save Avatar row below it and the two read as one
   control group. */
.glyphbox{margin:16px 0 18px;border-top:1px solid var(--edge);padding-top:12px}
.glyphbox>summary{cursor:pointer;font-weight:600;color:var(--gold);padding:4px 0}
.glyphbox>summary:focus-visible{outline:2px solid var(--gold);outline-offset:3px}
.glyphsearch{margin:10px 0 14px}
.glyphsearch label{display:block;color:var(--faint);font-size:.72rem;
  text-transform:uppercase;letter-spacing:.12em;margin-bottom:5px}
.glyphsrow{display:flex;gap:8px;flex-wrap:wrap}
.glyphsrow input{flex:1 1 14rem;min-width:0;background:var(--panel2);color:var(--ink);
  border:1px solid var(--edge2);border-radius:10px;padding:.6em .8em;font:inherit;font-size:.95rem}
.glyphsrow input:focus{outline:none;border-color:var(--gold)}
/* ROWS, NOT TILES (owner, 2026-07-28: "show Glyph on right side, same as main
   search"). This was a responsive grid of centred tiles with the drawing above a
   centred caption. It is now the same shape as a search result: name on the left,
   mark hard right, one per line, so scanning the catalogue is one vertical read
   instead of a wrapping field, and the two surfaces teach the same thing. */
.glyphgrid{display:flex;flex-direction:column;gap:6px}
/* A row is a real <button>, so it is focusable and keyboard-operable for free. */
.glyphpick{display:flex;flex-direction:row;align-items:center;gap:12px;
  padding:.55em .8em;background:var(--panel2);border:1px solid var(--edge);
  border-radius:10px;color:var(--ink);font:inherit;cursor:pointer;transition:.12s;
  text-align:left;width:100%}
.glyphpick:hover{border-color:var(--gold);color:var(--gold);background:var(--panel)}
.glyphpick:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* flex:0 0 auto and last in the row: the mark never shrinks, the label absorbs the
   slack. em-sized so it grows with the A-/A+ control like everything else here. */
/* INK, NOT GOLD, for the same reason the search row's mark changed on 2026-07-30: this
   row was built to be "the same shape as a search result", and a colour it does not
   share is the fastest way for the two to stop teaching the same thing. The row's own
   hover still turns gold, and the mark now follows it because it no longer pins its
   own colour. */
.glyphpick .gpart{flex:0 0 auto;width:2em;height:2em;display:block;color:inherit}
.glyphpick .gpart svg{width:100%;height:100%;display:block}
/* The label wraps rather than ellipsising: a glyph called "Eye Of Horus" reading as
   "Eye Of H..." makes the list unusable. min-width:0 is what lets it wrap at all
   inside a flex row rather than forcing the glyph off the end. */
.glyphpick .gplab{flex:1 1 auto;min-width:0;font-size:.82rem;line-height:1.3;
  text-align:left;color:var(--muted);overflow-wrap:anywhere}

/* Sign Out, at the foot of the account nav's Account menu. Set apart by a rule and a
   quieter colour because it is the one entry that is not a place to go: it ends the
   session. Added 2026-07-27 when Sign Out moved off the bottom of the overview page
   and into the nav (AccountNav). */
.subnav .snpanel a.snout{margin-top:4px;padding-top:11px;border-top:1px solid var(--edge);
  border-radius:0 0 8px 8px;color:var(--muted)}
.subnav .snpanel a.snout:hover{color:var(--ink)}
@media (max-width:640px){
  /* A GROUP BUTTON IS THE SAME SIZE AS A PLAIN ONE. These used to be
     flex:1 1 100% with a full-width summary, so on a phone the four dropdown
     groups became full-bleed bars while Your Atlas and Overview stayed as small
     pills beside each other: one nav, two sizes of button, and the bigger ones
     were the LESS important. They now wrap like any
     other pill; only the open PANEL goes full width, which is what wants the
     room. */
  .subnav details.snmenu{flex:0 1 auto;min-width:0}
  .subnav details.snmenu>summary{max-width:100%;box-sizing:border-box}
  /* In flow rather than floating, so an open menu pushes the page down instead
     of covering it, and full width because it is a list of destinations. */
  .snpanel{position:static;box-shadow:none;border-radius:10px;margin-top:4px;min-width:0;width:100%}
  .subnav details.snmenu[open]{flex:1 1 100%}
}

/* ---- quiz topic progress (AccountQuizzes, spec section 8.1) ----------------
   A row per topic: name, bar, figure. The bar is aria-hidden because the figure
   beside it says the same thing, and a screen reader announcing both hears the
   number twice. Every part is em/rem and the row WRAPS rather than clipping, so
   a long topic name at 200 percent text pushes the figure to a second line
   instead of vanishing off the right where html{overflow-x:hidden} would cut it
   away with no scrollbar. */
.qptopics{display:flex;flex-direction:column;gap:9px;margin-top:10px}
.qprow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-width:0}
.qprow .qpname{flex:1 1 9rem;min-width:0;font-size:.9rem;overflow-wrap:break-word}
.qprow .qptrack{flex:1 1 7rem;min-width:0;height:.5em;border-radius:999px;background:var(--panel2);
  border:1px solid var(--edge);overflow:hidden}
.qprow .qpbar{display:block;height:100%;background:var(--gold)}
.qprow .qpnum{flex:0 0 auto;color:var(--faint);font-size:.8rem;font-variant-numeric:tabular-nums}

/* ---- account section lists (shared by Account.aspx and the AccountXxx leaf
        pages; previously inline in Account.aspx) ---- */
.meta{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(100%,11rem),1fr));gap:14px}
.meta .k{color:var(--faint);font-size:.7rem;text-transform:uppercase;letter-spacing:.14em;margin-bottom:3px}
.meta .v{font-size:.95rem;overflow-wrap:anywhere}
.pklist{display:flex;flex-direction:column;gap:10px}
.pkitem{display:flex;align-items:center;gap:12px;padding:12px 14px;border:1px solid var(--edge);border-radius:10px;background:var(--panel2)}
.pkitem .pkinfo{flex:1 1 auto;min-width:0}
.pkitem .pkname{font-size:.95rem;font-weight:600}
.pkitem .pkname a{color:inherit;text-decoration:none}
.pkitem .pkname a:hover{text-decoration:underline}
.pkitem .pkmeta{color:var(--faint);font-size:.74rem;margin-top:2px}
.pkitem .btn.rm{padding:8px 12px;font-size:.8rem}
.pkempty{color:var(--muted);font-size:.88rem}
/* w2211-ratingspeek, 2026-08-17: the per-ROW fact a purchase or document
   carries when .frbtns rendered no action at all (no chart, a missing PDF
   file, no purchase on record), so the row is never silent AND actionless
   at once (AccountReports.aspx.vb NoActionNote). Same muted tone as
   .pkempty above, which is the identical message at the list level rather
   than the row level. Lives INSIDE .frbtns as of the same lane's later
   correction (App_Themes/Divinity/account.css's ".frbtns>p.pkrownote" gives
   it flex-basis:100% so it takes its own full line rather than squeezing
   between button-sized flex items); max-width here still caps the SENTENCE
   itself, so it reads as a line of prose rather than stretching edge to
   edge on a wide row. */
.pkrownote{color:var(--muted);font-size:.82rem;line-height:1.45;margin:0;
  max-width:22rem;min-width:0}
/* ---- drag-to-reorder on the Starred list (owner, 2026-07-30) --------------
   The grip is a hint, not a control: the row itself is what drags (a 1em target is
   not a target on a phone), and Move Up / Move Down are what a keyboard uses. It is
   aria-hidden in the markup for that reason, so nothing here needs a focus style.
   em-sized like everything else in the row, so it tracks the A-/A+ control. */
.pkgrip{flex:0 0 auto;color:var(--faint);font-size:1.1em;line-height:1;letter-spacing:-.18em;
  cursor:grab;user-select:none;padding-right:.15em}
.pkitem[draggable="true"]:active .pkgrip{cursor:grabbing}
/* The row being dragged. Opacity only: moving the others with a transform would fight
   the live reinsertion the script is already doing on dragover. */
.pkitem.dragging{opacity:.45;border-color:var(--gold)}
@media (prefers-reduced-motion: reduce){.pkitem.dragging{opacity:.6}}
.cmtsnip{color:var(--muted);font-size:.86rem;margin-top:3px;font-style:italic}
.chstatus{color:var(--faint);font-size:.7rem;text-transform:uppercase;letter-spacing:.1em;border:1px solid var(--edge);border-radius:999px;padding:2px 8px;margin-left:6px;vertical-align:middle}
/* .pkactions (added 2026-08-10, ruling 430): the right-hand column of a
   report row, replacing .frbtns as .pkitem's direct second flex child.
   .frbtns nests inside it holding every action button.
   w2211-ratingspeek, 2026-08-17, owner correction: the rating trigger and
   the "Use This Purchase" button now live INSIDE .frbtns as ordinary
   button-family flex children (App_Themes\Divinity\account.css owns their
   own rules, ".frbtns>details.ratepurchase" and its neighbours), not in a
   separate .pkratings block beneath it; that class and its markup are gone.
   .pkactions itself is unchanged: align-items:flex-end still right-aligns
   .frbtns as a column without floats. min-width:0/max-width:100% match
   every other flex row of text on this site at high text zoom. */
.pkactions{display:flex;flex-direction:column;align-items:flex-end;gap:0;
  flex-shrink:0;min-width:0;max-width:100%}
/* align-items:flex-start is load-bearing. The Share control is a <details>, and a flex
   row defaults to align-items:stretch, so opening it grew every sibling button to the
   height of the expanded share panel. flex-wrap keeps the row from overflowing a narrow
   card once a report has all four actions. */
.frbtns{display:flex;align-items:flex-start;flex-wrap:wrap;gap:6px;flex-shrink:0}
.frbtns .btn{padding:5px 10px;font-size:.75rem;line-height:1.35;text-decoration:none}
.frbtns .btn:hover{text-decoration:none}
.frbtns .sharebox>summary.btn{padding:5px 10px;font-size:.75rem;line-height:1.35}
/* .btn.rm is re-stated here because ".pkitem .btn.rm" above scores 0,3,0 and beats
   ".frbtns .btn" at 0,2,0 -- so the Delete button kept the old larger padding while every
   button beside it shrank, and the row read as one odd button out. Same 0,3,0 score, later
   in the file, so the row wins inside .frbtns and the older rule still applies elsewhere. */
.frbtns .btn.rm{padding:5px 10px;font-size:.75rem}
/* Read and Download are bare anchors and are flex items themselves. Share and Delete are
   wrapped in <details> and <form>, so the WRAPPER is the flex item and the button inside
   sits in a line box -- which reserves the font's descender space under it, standing those
   two a few pixels taller than the other two. Making the wrappers flex containers removes
   the line box, so all four controls are the same height. The disclosure marker is hidden
   for the same reason: it adds width and a baseline of its own. */
.frbtns>form,.frbtns>.sharebox{display:flex;margin:0}
.frbtns>.sharebox{flex-direction:column;align-items:flex-start}
.frbtns .sharebox>summary{display:block;list-style:none}
.frbtns .sharebox>summary::-webkit-details-marker{display:none}
.frbtns .sharebox>summary::marker{content:""}
/* Phones: the report row cannot hold a name AND four controls side by side, so the
   info takes the full width and the buttons drop underneath. The open share panel's
   inputs are also capped to the row, because a 340px textarea inside a flex-shrink:0
   button cluster was what pushed the whole card into horizontal scroll,
   reported as account/reports not rendering properly on mobile. */
@media(max-width:640px){
  .pkitem{flex-wrap:wrap}
  .pkitem .pkinfo{flex:1 1 100%}
  /* .pkactions (added 2026-08-10, ruling 430) is now the direct right-hand
     flex child carrying .frbtns; it needs the same full-width drop .frbtns
     used to get on its own, or it stays shrunk to its natural content width
     while .pkinfo already spans the row above it. */
  .pkactions{width:100%}
  .frbtns{width:100%}
  .frbtns>.sharebox{max-width:100%;flex:1 1 100%}
  .frbtns .sharebox input,.frbtns .sharebox select,.frbtns .sharebox textarea{
    width:100%;max-width:100%;box-sizing:border-box}
  /* Thumb-sized on the device that is all thumbs: 5px-padded 26px buttons sat well
     under the 44px touch minimum. (Mobile audit, 2026-07-26, finding G5.) */
  .frbtns .btn,.frbtns .btn.rm,.frbtns .sharebox>summary.btn,
  .frbtns>details.ratepurchase>summary{padding:10px 14px;font-size:.85rem}
}

/* ---- the add-ons step (Report.aspx?addons=1, 2026-07-30) -----------------
   One tick, one style picker and one price per add-on, then the totals. All of
   it in rem/em so it tracks the reader's text size, and every row wraps rather
   than truncating: a style name and a price forced onto one line is how "Large,
   18 Inches" becomes "Large, 18 In...". */
.addons .card{margin-bottom:16px}
.aoitem{padding:12px 0;border-bottom:1px solid var(--edge)}
.aoitem:last-child{border-bottom:0}
.aotick{display:flex;align-items:center;gap:.5em;cursor:pointer;font-size:.95rem}
.aotick .aoname{font-weight:600}
.aodesc{margin:6px 0 8px;color:var(--muted);font-size:.85rem}
.aostyle{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;margin:0 0 6px}
.aostyle label{flex:0 0 auto;font-size:.7rem;text-transform:uppercase;
  letter-spacing:.12em;color:var(--faint)}
/* The select carries the price, so it needs room to show one. min-width:0 keeps
   it shrinkable on a phone; max-width stops it running the width of a desktop. */
.aostyle select{flex:1 1 18rem;min-width:0;max-width:34rem;padding:.45em .6em;
  border:1px solid var(--edge);border-radius:8px;background:var(--panel);
  color:inherit;font-size:.9rem}
.aoline{display:flex;flex-wrap:wrap;justify-content:space-between;gap:4px 12px;
  font-size:.85rem;color:var(--muted)}
.aoline .aoamt{font-variant-numeric:tabular-nums;color:var(--ink)}
/* A price that could not be got is stated, not blank. Colour is not the only
   signal: the sentence itself says what is wrong. */
.aobad{color:var(--dark-c)}
.aototals .aorow{display:flex;flex-wrap:wrap;justify-content:space-between;
  gap:4px 12px;padding:7px 0;border-bottom:1px solid var(--edge);font-size:.9rem}
.aototals .aorow:last-child{border-bottom:0}
.aototals .aorow.sub{padding-left:1.2em;color:var(--muted);font-size:.82rem}
.aototals .aorow.tot{font-weight:700;font-size:1rem;border-top:1px solid var(--edge2);
  border-bottom:0;margin-top:4px}
.aototals .aoamt{font-variant-numeric:tabular-nums;white-space:nowrap}
/* One report's own total, and the whole basket's. The renderer emits sub-tot and
   grand (AddOnsSummary.Row); .tot above is a leftover from the first draft
   and matches nothing, which is why a per-report total read as an ordinary line. */
.aototals .aorow.sub-tot{font-weight:650;border-bottom:0;border-top:1px solid var(--edge2);
  margin-top:4px}
.aototals .aogroup{margin:0 0 14px}
.aototals .aogroup h3{margin:0 0 6px;font-family:var(--serif);font-size:1rem;font-weight:600}
.aorow.grand{display:flex;flex-wrap:wrap;justify-content:space-between;gap:4px 12px;
  padding:10px 0 0;margin-top:6px;border-top:2px solid var(--edge2);
  font-weight:700;font-size:1.05rem}
.aorow.grand .aoamt{font-variant-numeric:tabular-nums;white-space:nowrap}
/* ---- the delivery address, inside its own add-on -------------------------
   The address belongs to the item being sent, not to the page, and
   the form is condensed onto four rows rather than eight stacked boxes.

   EVERY ROW IS A WRAPPING FLEX ROW WITH min-width:0 MEMBERS, never a fixed grid.
   At 150% to 200% text, or on a phone, each row collapses to one field per line
   by itself instead of squeezing a postcode box down to three characters. That is
   the site's accessibility rule and it is the whole reason these are not
   grid-template-columns. */
/* NO PANEL FILL. This block sits inside a card that already has one, so a second
   filled box around it read as a slab of dark blue under the item rather than as
   a group of fields. The border and the legend do the grouping; the inputs keep
   their own --ground fill, which is what makes them read as inputs. */
.aoaddr{margin:10px 0 0;padding:12px 14px;border:1px solid var(--edge2);
  border-radius:12px;background:none;min-width:0}
/* The whole shipping section, revealed by ticking the item above it. */
.aoship[hidden]{display:none}
.aoaddr>legend{padding:0 6px;font-size:.7rem;text-transform:uppercase;
  letter-spacing:.14em;color:var(--faint)}
.aoaddr .fld{margin-bottom:10px}
.aosame{margin:0 0 10px}
.aoaddrbody[hidden],.aonewaddr[hidden]{display:none}
/* One, two and four across. flex:1 1 <basis> with min-width:0 is what makes them
   wrap rather than shrink; the basis is the width below which a field would be
   unusable, so that is where the row breaks. */
.aorow1,.aorow2,.aorow4{display:flex;flex-wrap:wrap;gap:0 12px;min-width:0}
.aorow1>.fld{flex:1 1 100%;min-width:0}
.aorow2>.fld{flex:1 1 14rem;min-width:0}
.aorow4>.fld{flex:1 1 9rem;min-width:0}

/* Calculate Shipping, and the sentence saying what it did. The sentence wraps and
   is never truncated: on a failure it carries the carrier's own reason, which is
   the one thing the buyer needs to read. */
.aocalc{display:flex;flex-wrap:wrap;align-items:center;gap:8px 12px;margin:10px 0 0}
.aocalcnote{flex:1 1 16rem;min-width:0;font-size:.82rem;color:var(--muted)}

/* js/addons.js HIDES the no-script Update Prices button rather than deleting it,
   and .btn sets display:inline-block, which beats the browser's own [hidden] rule.
   Without this line the button the owner asked to be gone stays on the page. The
   same trap is documented at .dvpage .modebar[hidden] below. */
.addons .btn[hidden]{display:none}

/* ---- a paid add-on waiting to be approved (account/reports, 2026-07-30) ----
   Sits UNDER the row's buttons, not among them: it is a decision about an object
   already bought rather than another thing to buy, and a primary button in a row
   of ghost ones is how those two get confused. All in rem/em so it tracks the
   reader's text size, and it wraps rather than truncating. */
.aoapprove{margin:10px 0 0;padding:12px 14px;border:1px solid var(--edge2);
  border-radius:12px;background:var(--ground)}
.aoapprovewhat{font-weight:650;font-size:.95rem;margin-bottom:4px}
.aoapprovenote{margin:0 0 10px;color:var(--muted);font-size:.85rem;max-width:70ch}

/* ---- quiz challenge results in a list row (account/quiz-challenges) ------
   The per-player scores under a finished challenge. A wrapping row of small
   score chips rather than a table: a two-column table of two rows is more
   structure than the content has, and it does not wrap on a phone.

   The winner is marked with a WORD as well as a colour. Colour alone would say
   nothing to a screen reader and nothing to a reader who cannot distinguish it,
   and "who won" is the entire point of the line. */
.qcresult{display:flex;flex-wrap:wrap;gap:6px 14px;margin-top:6px}
.qcscoreline{font-size:.8rem;color:var(--muted)}
.qcscoreline strong{color:var(--ink);font-variant-numeric:tabular-nums}
.qcscoreline.win{color:var(--ink)}
.qcwinner{font-size:.68rem;text-transform:uppercase;letter-spacing:.1em;color:var(--gold)}

/* ---- friend groups (account/people, 2026-07-30) --------------------------
   The Share and Manage panels inside a group row. Both live in the existing
   .sharebox <details>, so the summary is already a real disclosure button to a
   keyboard and a screen reader with no ARIA.

   min-width on the panel, not a fixed width: it has to be wide enough to hold a
   select and a button, and it has to collapse to the row on a phone (the media
   query at the end of the .frbtns block already caps .sharebox children to 100%).
   Everything is in em/rem so it tracks the reader's text size. */
.fgshare{margin:8px 0 0;min-width:16rem;max-width:100%}
.fgfield{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin:0 0 8px}
.fgfield label{flex:1 1 100%;font-size:.7rem;text-transform:uppercase;
  letter-spacing:.12em;color:var(--faint)}
.fgfield select,.fgfield input[type="text"]{flex:1 1 10rem;min-width:0;max-width:100%;
  padding:.45em .6em;border:1px solid var(--edge);border-radius:8px;
  background:var(--panel);color:inherit;font-size:.85rem}
.fghint{margin:0 0 8px;font-size:.75rem;color:var(--faint)}
/* The friend picker. A wrapping row of ordinary checkboxes, capped in height so a
   member with sixty friends gets a scrollable box inside the panel rather than a
   panel taller than the screen. overflow-y with its own scroller, never a clip. */
.fgpick{border:1px solid var(--edge);border-radius:9px;margin:0 0 8px;padding:8px 10px;
  max-height:14rem;overflow-y:auto}
.fgpick legend{font-size:.7rem;text-transform:uppercase;letter-spacing:.12em;
  color:var(--faint);padding:0 4px}
.fgpickitem{display:flex;align-items:center;gap:.4em;font-size:.85rem;
  color:var(--muted);cursor:pointer;padding:2px 0;min-width:0}
.fgpickitem span{overflow-wrap:anywhere}
.fgpickitem:hover{color:var(--ink)}
.fgpickitem input:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* ---- cards ---- */
.card{background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--edge);border-radius:16px;padding:20px 22px;box-shadow:var(--shadow);margin-bottom:20px;max-width:100%;overflow-x:auto}
.card h3{font-family:var(--serif);font-size:1.2rem;margin:0 0 14px;font-weight:600}

/* ---- stat tiles / date-range bar (admin dashboard) ---- */
.stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));gap:14px;margin-bottom:6px}
.stat{background:var(--panel2);border:1px solid var(--edge);border-radius:12px;padding:16px 18px}
.stat .n{font-family:var(--serif);font-size:1.9rem;font-weight:600}
.stat .l{color:var(--faint);font-size:.72rem;text-transform:uppercase;letter-spacing:.12em;margin-top:2px}
.stat .s{color:var(--muted);font-size:.78rem;margin-top:4px}
.rangebar{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-bottom:6px}
.presets{display:flex;gap:6px;flex-wrap:wrap}
.preset{font-size:.82rem;color:var(--muted);text-decoration:none;padding:7px 12px;border-radius:9px;border:1px solid var(--edge)}
.preset:hover{color:var(--ink);background:var(--panel2)}
.preset.on{color:var(--gold-bg-ink, #1a1508);background:var(--gold-bg);border-color:var(--gold);font-weight:650}
.customrange{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-left:auto}
.customrange label{color:var(--faint);font-size:.78rem;display:flex;gap:5px;align-items:center}
.customrange input[type=date]{background:var(--ground);border:1px solid var(--edge2);color:var(--ink);border-radius:9px;padding:7px 9px;font-size:.84rem;font-family:var(--sans)}
.rangenote{color:var(--faint);font-size:.8rem;margin:2px 2px 0}
.tools{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px}
.tool{display:block;background:var(--panel2);border:1px solid var(--edge);border-radius:14px;padding:18px 20px;text-decoration:none}
.tool:hover{border-color:var(--gold)}
.tool h4{font-family:var(--serif);font-size:1.1rem;margin:0 0 6px;font-weight:600;color:var(--ink)}
.tool p{color:var(--muted);font-size:.86rem;margin:0}

/* ---- data tables ---- */
table.grid{width:100%;border-collapse:collapse;font-size:.9rem}
table.grid th{text-align:left;color:var(--faint);font-size:.72rem;text-transform:uppercase;letter-spacing:.1em;padding:10px 10px;border-bottom:1px solid var(--edge2)}
table.grid td{padding:10px 10px;border-bottom:1px solid var(--edge);vertical-align:middle}

/* ---- status pills ---- */
.pill{display:inline-block;font-size:.68rem;border-radius:99px;padding:2px 9px;border:1px solid var(--edge2);color:var(--muted)}
.pill.on{color:var(--shared);border-color:var(--shared)}
.pill.off{color:var(--dark-c);border-color:var(--dark-c)}

/* ---- forms ---- */
.fld{display:flex;flex-direction:column;gap:6px;margin-bottom:16px}
label{font-size:.72rem;text-transform:uppercase;letter-spacing:.14em;color:var(--faint)}
/* A CHECKBOX IS AN INPUT, AND THE FIELD SKIN BELOW WAS EATING IT. The Custom
   report picker's checkboxes were reported as not rendering properly.
   They were never a report-page defect. This rule arrived with the original port
   and has painted every checkbox and radio on the site with a panel
   background, a 1px border, a 10px corner radius and eleven pixels of padding
   ever since, which turns a 13px control into a rounded slab with the tick
   floating off-centre inside it.

   The evidence it had been costing something all along is that three separate
   surfaces already carry their own private patch: account.css, collection.css and
   report.css each set width:auto on their own checkboxes, which restores the
   WIDTH and leaves the padding, the border, the background and the radius still
   applied. Only admin.css ever fixed it properly, and it did so by sizing the
   control rather than by fighting the skin. Rather than add a fourth patch, the
   exclusion goes here, at the one rule that caused all four.

   Sized in em, not px, deliberately: the A-/A+ control has to move a checkbox
   along with the label beside it, and a px box would opt that control out of the
   reader's own text size. accent-color is what makes the tick gold without
   rebuilding the control out of pseudo-elements, which is the version a screen
   reader loses. */
input:not([type=checkbox]):not([type=radio]),select,textarea{background:var(--ground);border:1px solid var(--edge2);color:var(--ink);border-radius:10px;padding:11px 12px;font-size:1rem;font-family:var(--sans)}
input[type=checkbox],input[type=radio]{width:1.05em;height:1.05em;flex:0 0 auto;accent-color:var(--gold)}
/* NO FIELD IS EVER WIDER THAN WHAT HOLDS IT. Found: the entry
   pickers on Edit Profile ran off the right of the phone screen. A <select> sizes
   itself to its WIDEST OPTION unless it is told not to, and those lists hold full
   entry names, so on a 390px screen the control was several hundred pixels wider
   than the viewport. The same trap applies to any input with a long value or
   placeholder.

   Two declarations, and both are needed. max-width:100% caps it against the
   container. min-width:0 is what lets it actually shrink INSIDE A FLEX ROW, where
   the default min-width:auto refuses to go below the content's own width and the
   cap is quietly ignored -- which is exactly the shape of the form that reported
   this. Anything wanting a fixed narrow width still sets it and still wins; this
   only ever removes overflow.

   It matters more here than on most sites: html{overflow-x:hidden} CLIPS overwide
   content with no scrollbar, so a field past the edge is not merely ugly, it is
   unreachable, and its right-hand end simply does not exist for the member. */
input,select,textarea{max-width:100%;min-width:0}
input:focus,select:focus,textarea:focus{outline:2px solid var(--gold);border-color:var(--gold)}
input[readonly]{color:var(--muted);border-style:dashed}

/* ---- the canonical range control, .rangectl ------------------------------
   Owner QA, 2026-08-10, on the spread map's timeline slider
   (w641-spreadmapfix): "the thumb does not reach the beginning or the end of
   its track. At the first position the thumb sits visibly inside the track
   rather than flush at its start." The value was at its extreme and the
   control was saying it was not.

   TWO CAUSES, AND THE SECOND IS THIS FILE'S OWN. First, a range left at the
   browser's own appearance insets its thumb from both ends and paints the
   accent fill only as far as the thumb's CENTRE, so a slider at maximum still
   shows unfilled track past it. Second, and larger: the generic form-control
   rule a few lines above gives every input that is not a checkbox or a radio
   an 11px by 12px padding and a border, and a range input inherits both, so
   the runnable track is inset from the element's own edges before the thumb
   even starts travelling. That rule is (0,2,1), which is why this block sits
   HERE, after it, rather than up beside the crop tools where it was first
   written: at equal specificity the later rule wins, and border:0 and
   padding:0 below are what actually let the track reach the ends.

   The site had already solved the first half once, in .dock-volrange further
   down, which sets its own appearance and thumb. Nothing here changes the
   dock, whose 24px thumb is a deliberate WCAG 2.5.8 target-size exception
   with its own comment; this is that same pattern made reusable and sized in
   em instead, so the A-/A+ control and 150 to 200 percent zoom both still
   reach it. Class census on the day: three live range inputs, the spread map
   timeline and the avatar crop zoom, which both carry this rule now, and the
   media dock volume, which does not need it.

   --range-fill is an OPT-IN two-tone progress fill, a percentage a script
   sets on the element as the value moves. A slider that never sets it shows a
   single-tone track, which is what the crop zoom does; js/spreadmap.js sets it
   on every tick so the fill empties and fills exactly when the thumb reaches
   an end. */
/* The element and attribute are in this selector for specificity, not for
   documentation: the generic input rule above is (0,2,1) and a bare .rangectl
   is (0,1,0), which loses to it wherever in the file it sits. Written as
   input[type=range].rangectl it ties at (0,2,1) and, being later, wins. The
   pseudo-element rules under it have no competitor and stay short. */
input[type=range].rangectl,.croptools input[type=range]{-webkit-appearance:none;appearance:none;
  background:transparent;cursor:pointer;height:1.5em;margin:0;border:0;padding:0;
  --range-track:linear-gradient(to right,var(--gold) 0 var(--range-fill,0%),var(--edge2) var(--range-fill,0%) 100%)}
.rangectl::-webkit-slider-runnable-track,.croptools input[type=range]::-webkit-slider-runnable-track{
  height:.35em;border-radius:99px;background:var(--range-track)}
.rangectl::-moz-range-track,.croptools input[type=range]::-moz-range-track{
  height:.35em;border-radius:99px;background:var(--edge2)}
.rangectl::-moz-range-progress,.croptools input[type=range]::-moz-range-progress{
  height:.35em;border-radius:99px;background:var(--gold)}
/* margin-top centres the thumb on a .35em track: half the difference between
   the two heights, negative because the thumb is the taller of the two. */
.rangectl::-webkit-slider-thumb,.croptools input[type=range]::-webkit-slider-thumb{
  -webkit-appearance:none;appearance:none;width:1.15em;height:1.15em;border-radius:99px;
  background:var(--gold);border:.14em solid var(--ground);box-sizing:border-box;
  margin-top:calc((.35em - 1.15em) / 2)}
.rangectl::-moz-range-thumb,.croptools input[type=range]::-moz-range-thumb{
  width:1.15em;height:1.15em;border-radius:99px;border:.14em solid var(--ground);
  background:var(--gold);box-sizing:border-box}
.rangectl:hover::-webkit-slider-thumb,.croptools input[type=range]:hover::-webkit-slider-thumb{background:var(--gold-soft)}
.rangectl:hover::-moz-range-thumb,.croptools input[type=range]:hover::-moz-range-thumb{background:var(--gold-soft)}
.rangectl:focus-visible,.croptools input[type=range]:focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:99px}

/* A picker row: the dropdown takes the room and the Add button keeps its size,
   with the select free to shrink to the width of the phone. */
.pickForm select{flex:1 1 12rem}
.actions{display:flex;gap:10px;align-items:center;margin-top:4px;flex-wrap:wrap}
.rowform{display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin:0}

/* ---- buttons ---- */
/* display, text-decoration and line-height are here because .btn is put on an <a> 327
   times across the site, not only on <button>. Without them an anchor button renders
   inline and underlined: the vertical padding does not hold the box open and the gold
   background bleeds across neighbouring lines. Twenty-one admin pages and Report.aspx
   had each pasted a local .btn with display:inline-block to work around it; the account
   pages had not, which is why the buttons on /account/reports looked wrong. Fixed once
   here. Only the missing properties are added, so nothing that already looked right
   changes size or colour. */
/* border is 1px TRANSPARENT, not none. The outlined variants (.ghostbtn, .ghost, .danger)
   all set a 1px border, so a primary button with no border stood exactly 2px shorter than
   every button beside it -- measured at 26.2px against 28.2px on the reports row. A
   transparent border of the same width makes every button the same box, whatever its
   colour, so a primary and a ghost can always sit in a row together. */
.btn{display:inline-block;vertical-align:middle;box-sizing:border-box;line-height:1.2;white-space:nowrap;text-decoration:none;background:var(--gold-bg);color:var(--gold-bg-ink, #1a1508);border:1px solid transparent;border-radius:10px;padding:10px 16px;font-weight:650;cursor:pointer;font-size:.9rem;font-family:var(--sans)}
.btn:hover{background:var(--gold-soft);text-decoration:none}
.btn.ghostbtn{background:var(--panel);color:var(--muted);border:1px solid var(--edge2)}
.btn.ghostbtn:hover{background:var(--panel2);color:var(--ink)}
/* ---- .btn.ghost: undo the ICON-BUTTON geometry ----------------------------
   Owner, 2026-07-27: "all of the action buttons like View, the text of the button
   goes outside of the button container." It did, on all thirty of them, across ten
   admin pages.

   The cause is a class-name collision, not a layout mistake. ".ghost" further up
   this file is the HEADER ICON button (the burger): a fixed 36px square with
   display:grid and place-items:center, made to hold one glyph. The admin grids
   write class="btn ghost" for TEXT buttons -- View, Unblock, Run, Approve.

   Both are single-class selectors, so they carry equal specificity and the later
   rule wins property by property: .btn (further down) takes back display, padding,
   background and the rest. But width:36px and height:36px exist ONLY in .ghost, so
   nothing overrode them. With box-sizing:border-box and .btn's 10px/16px padding
   that leaves a content box FOUR pixels wide, and white-space:nowrap then pushes
   the word straight out through the border. The button looked like a small empty
   square with text lying across the page beside it.

   Fixed here rather than by renaming the class in ten files: .btn.ghost is (0,2,0)
   against .ghost's (0,1,0), so it wins cleanly, and any future page that writes
   "btn ghost" is correct by default instead of inheriting the same trap. The
   icon button is untouched -- it is .ghost WITHOUT .btn. */
.btn.ghost{width:auto;height:auto;display:inline-block;place-items:normal}

/* ---- admin log pages: counters, view tabs, filter chips -------------------
   This whole block (.sstats/.stile/.stab(s)/
   .sfilters/.sfgroup/.sflabel/.schip plus the phone media query) was a
   byte-for-byte duplicate of the block admin.css already carries under its
   own "read-only log boards" comment, which states outright that it was
   COPIED here from this exact spot in site.css so admin chrome
   lives in one file, and was apparently never deleted from here afterward.
   Every consumer (Admin/Searches.aspx, Admin/Usage.aspx, Admin/
   ProductAnalytics.aspx) already loads admin.css, so removing the stale copy
   here changes nothing any page renders. admin.css is now the one copy. */

/* ---- account pager (AccountRender.Pager) -----------------------------------
   Moved here from account.css and profile.css,
   where it was declared byte-for-byte identically (account.css's own comment
   already flagged the duplicate for collapse). A third family, ReportItems.aspx's
   detail view, calls the same renderer for its review list (via a profile.css
   registration added the same day), so this is a cross-family
   component and belongs in the one sheet every page already loads, exactly
   like .btn and .card above it. */
.acctpager{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-top:16px}
.acctpager .pgoff{opacity:.45;pointer-events:none}
.acctpager .pgnow{font-weight:700}
.acctpager .pgellipsis{opacity:.55;padding:0 2px}

/* ---- collapsed "add" cards (AdminView.AddCard, 2026-07-27) ----------------
   Owner: "make all admin pages that have the add on the top collapsable and
   defaulted to collapsed like Create a Package is on Packages." Several admin
   pages opened on a blank creation form with the list the reader actually came for
   pushed below it; on Images that form is a dozen fields and the library started a
   full screen down.

   A native <details>, so it works with scripting off and is a real disclosure
   button to a screen reader with no ARIA. list-style:none removes the default
   triangle in favour of the drawn caret, and the ::-webkit-details-marker reset is
   what actually removes it in Safari, which ignores list-style here. */
.addcard>summary{cursor:pointer;list-style:none;display:flex;align-items:baseline;
  flex-wrap:wrap;gap:4px 10px;border-radius:10px;padding:2px 4px;margin:-2px -4px}
.addcard>summary::-webkit-details-marker{display:none}
.addcard>summary:hover{background:var(--panel2)}
.addcard>summary:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.addcard>summary h3{display:inline;margin:0}
/* The caret is drawn rather than a glyph, so it inherits the text colour in both
   themes and rotates to show state. aria-hidden by construction: generated content
   is not in the accessibility tree, and the summary already announces expanded. */
.addcard>summary::before{content:"";flex:0 0 auto;width:.45em;height:.45em;
  margin-right:2px;border-right:2px solid var(--gold);border-bottom:2px solid var(--gold);
  transform:rotate(-45deg);transition:transform .15s}
.addcard[open]>summary::before{transform:rotate(45deg)}
.addhint{color:var(--muted);font-size:.82rem}
.addbody{margin-top:14px}
/* The inline variant shares a card with a list, so it needs a rule under it and
   a little less air than a card-level one. */
.addinline{margin:0 0 14px;padding-bottom:12px;border-bottom:1px solid var(--edge)}
.addinline>summary h4{display:inline;margin:0;font-size:.95rem}
.addinline .addbody{margin-top:12px}
@media (prefers-reduced-motion: reduce){
  .addcard>summary::before{transition:none}
}
/* On a phone a long label ("Mark All as Read (12)") at large text is wider than the
   card; nowrap turned that into an inner scrollbar. Let labels break. */
@media (max-width:560px){.btn{white-space:normal}}

/* Admin button density and variants, consolidated.
   Every one of the 21 Admin/*.aspx pages carried its own pasted copy of these rules,
   byte-identical in 19 of them. That is why a button fix had to be made 21 times and
   never was: the account pages were simply never included in the paste. Scoped to
   .adminpage, a class added to the 21 admin wrappers for exactly this purpose. NOT
   .adminwrap: that is shared chrome and every Account*.aspx page uses it too, so scoping
   here made admin button sizing leak onto the account pages and left the reports row with
   two anchors at 8px/14px beside two controls at 5px/10px. */
.adminpage .btn{border-radius:9px;padding:8px 14px;font-size:.85rem}
.adminpage .btn.small{padding:5px 10px;font-size:.78rem}
.adminpage .btn.ghost,.adminpage .btn.rm{background:var(--panel);color:var(--muted);border:1px solid var(--edge2)}
.adminpage .btn.ghost:hover,.adminpage .btn.rm:hover{background:var(--panel2);color:var(--ink)}
/* Admin's ghostbtn is transparent where the public one sits on --panel. Kept as it was
   rather than unified, because that is a look, not a bug. */
.adminpage .btn.ghostbtn{background:transparent;color:var(--ink);border:1px solid var(--edge2)}
.adminpage .btn.ghostbtn:hover{border-color:var(--gold)}
.adminpage .btn.danger{background:transparent;color:var(--dark-c);border:1px solid var(--dark-c)}
.adminpage .btn.danger:hover{background:var(--dark-bg)}

/* ---- messages ---- */
.msg{border-radius:10px;padding:11px 13px;font-size:.88rem;margin-bottom:18px}
.msg.ok{color:var(--shared);border:1px solid var(--shared);background:var(--shared-bg)}
.msg.err{color:var(--dark-c);border:1px solid var(--dark-c);background:var(--dark-bg)}

/* ============================================================
   Detail / index page shell (.dvpage)
   ------------------------------------------------------------
   The application layout the Explorer SPA used, lifted out of
   Explorer.aspx so every server-rendered page shares one system
   instead of each keeping its own drifting copy. Entity.aspx and
   Category.aspx use it today; a server-rendered Compare page can
   opt in by putting "dvpage" on its own <article>.

   Structure: ONE flowing column that fills the 1080px .wrap --
   an identity header (.dvrail) above a stack of section cards.
   This deliberately mirrors the retired SPA's DETAIL view, which
   was a single wide column. An earlier revision split this into a
   sticky 290px rail beside the content, copied from the SPA's
   COMPARE view; that shape belongs to a side-by-side comparison
   and looked wrong here, stranding a mostly-empty card beside a
   squeezed content column. Everything below uses theme tokens.
   ============================================================ */
.dvpage{max-width:none;margin:0;padding:10px 0 24px}
/* Named .dvrail for continuity with the markup both pages already emit; it is a
   header card, not a rail. Kept as a card because the identity block reads better
   set off from the prose that follows it. */
/* The identity block sits ON THE PAGE, not in a card. It used to carry the panel
   gradient, border, radius and shadow -- a blue box the home page has no equivalent of.
   The home page states a heading straight on the ground with a thin rule under it, and
   that is the look this page is meant to match. Only the entries themselves are cards;
   putting the title in one too made the page read as a stack of boxes. */
.dvrail{min-width:0;margin:0 0 20px;padding:0 2px}
/* TWO ROWS, at every width. Row one is .dvtitle -- name, pronunciation and the "Also
   known as" line -- spanning the FULL measure so none of it is squeezed into a narrow
   column. Row two carries the category tag on the left and the action buttons (star /
   read-aloud / copy / share) on the right, bottom edges level.
   A GRID, not a flex row, because a flex row cannot hold both of those at once: keeping
   the buttons beside the title meant squeezing the title column, which broke "Also known
   as Dge bshes . spiritual friend" into a four-line stack; giving the title the whole row
   instead pushed the buttons down onto a THIRD line below the tag, with a dead gap above
   them. The fix that makes both possible is structural and lives in Entity.aspx.vb: the
   badge is no longer the last child of .dvtitle, it is a SIBLING of .entacts, so the grid
   can place the two of them in the same row.
   Capped to the same measure as the prose, so the buttons land on the right edge of the
   TEXT COLUMN rather than the right edge of the screen. Floated to the viewport they read
   as page chrome; floated to the measure they read as belonging to the entry. */
/* [data-pagehead] IS PAIRED WITH .dvpage THROUGHOUT THIS BLOCK, and for the same
   reason [data-pageacts] is paired with it on the action row further down: these
   rules were scoped to .dvpage because the entity and category pages were the only
   two with a heading of this shape. App_Code/View/PageActions.vb now builds the same
   block for /trails and /trail/{slug} (owner, 2026-07-30, after the row first shipped
   in the wrong place there), and those pages use .authwrap. Opting in by attribute
   rather than dropping .dvpage keeps the specificity the two existing pages already
   compute against, and keeps an unrelated .dvhead elsewhere from picking these up. */
.dvpage .dvhead,[data-pagehead]{display:grid;grid-template-columns:minmax(0,1fr) auto;column-gap:18px;
  max-width:var(--measure)}
/* Row one is now SHARED: title left, action bar right,
   the bar top-aligned so it levels with the heading line rather than the bottom of
   a tall title block. The badge capsule moves to a tighter second row. */
.dvpage .dvtitle,[data-pagehead]>.dvtitle{min-width:0;grid-column:1;grid-row:1}
/* align-self:end is what levels a ~24px pill against a 40px button. The two bottom
   margins are deliberately EQUAL so the bottom edges land on the same pixel, and both
   children already collapse to display:none when empty (.badges:empty / .entacts:empty),
   so an entity with no tag, or one with no action bar, leaves no orphaned row and no gap.
   The action bar stops at the right edge of the MEASURE, which on a phone is the page
   gutter in from the viewport edge; the fixed gold FACTS handle (.factdrawer-handle, below)
   lives in that gutter, pinned to the viewport's right edge at 50% height. On a short
   screen the handle's vertical band does cross this row, but it clears the last button
   horizontally by the width of the gutter, so nothing is ever covered. Measured: 6px of
   clear space between the button and the handle at 320-375px. */
.dvpage .dvhead>.badges,[data-pagehead]>.badges{grid-column:1/-1;align-self:start;margin:2px 0 10px;min-width:0}
.dvpage .dvhead>.entacts,[data-pagehead]>.entacts{grid-column:2;grid-row:1;justify-self:end;align-self:start;margin:2px 0 0}
/* On a phone the tag track is what is left of the measure after four 44px touch targets,
   which is narrow enough that a long label has to wrap inside its pill. Let it: min-width:0
   plus break-word keep a long label CONTAINED in its own column, so a tag like
   "Ceremonial & Ritual Objects" wraps to two or three lines of pill instead of sliding out
   from under its track and running beneath the buttons. The tighter column gap below hands
   the tag back a few pixels where they matter most. */
.dvpage .dvhead>.badges .badge{min-width:0;white-space:nowrap}
/* On a phone the tag can no longer share a row with four touch targets without being
   crushed into a multi-line pill (a pill radius on a wrapped box reads as an ugly
   vertical OVAL, and it gets worse the larger the reader sets the font). Below 560px
   the header becomes a single column and STACKS: title, then the tag on its own
   full-width row where it stays one clean line, then the action bar. */
@media(max-width:560px){
  .dvpage .dvhead,[data-pagehead]{grid-template-columns:1fr;column-gap:0}
  .dvpage .dvhead>.badges,[data-pagehead]>.badges{grid-column:1;align-self:start;margin:0 0 11px}
  /* grid-row:auto IS LOAD BEARING. .entacts carries an explicit grid-row:1 from
     the desktop rule above (needed there to sit beside .dvtitle on the same
     row), and this media query used to change only grid-column, leaving that
     row:1 in place. In the single-column mobile grid that puts .entacts in the
     SAME CELL as .dvtitle (also row:1, col:1), and because both are explicitly
     positioned the grid lets them overlap rather than routing one around the
     other: the action row (speak, copy, share, each a bordered panel box)
     paints straight on top of the heading instead of stacking under it. Found
     2026-08-09 reproducing the owner's book-chapter screenshot (a "detached"
     speak icon): the true failure is this collision, briefly visible on any
     [data-pagehead] page in the window between paint and js/entitypage.js
     adding .actsenh (which supplies its OWN explicit row and sidesteps this),
     and permanently on any load where that script does not run in time.
     auto returns .entacts to normal flow so it takes the next open row after
     .dvtitle (and .badges, when one is rendered), which is the plain stacked
     shape the comment below already describes as the intended no-JS shape. */
  .dvpage .dvhead>.entacts,[data-pagehead]>.entacts{grid-column:1;grid-row:auto;justify-self:start;align-self:start;margin:0 0 13px}
}
/* Phone-width overflow toggle: with script on
   (.actsenh, added by the page script that also reveals the button) the action row
   cannot share the heading row on a phone, so it collapses behind a kebab that sits
   beside the title; tapping it drops the full row in below the badges. With script
   off the block above keeps today's stacked, always-visible row. Desktop never
   shows the kebab. */
/* font:inherit IS LOAD BEARING HERE and was missing. A <button> does not inherit
   its font, so its own font-size was the UA's 13.333px; every other control in
   this row carries font:inherit already. Without it the em box below would have
   drawn the kebab at 35.5px next to 40px buttons, which is the same fork the
   tokens exist to close, and it is the reason this line is not merely tidiness. */
.dvpage .actsmore,[data-pagehead]>.actsmore{display:none;background:var(--panel);border:1px solid var(--edge2);
  color:var(--ink);border-radius:10px;font:inherit;line-height:1;
  width:var(--actbtn);height:var(--actbtn);place-items:center;cursor:pointer;padding:0}
.dvpage .actsmore svg,[data-pagehead]>.actsmore svg{width:var(--actico);height:var(--actico)}
.dvpage .actsmore:hover,.dvpage .dvhead.actsopen .actsmore,
[data-pagehead]>.actsmore:hover,[data-pagehead].actsopen>.actsmore{border-color:var(--gold);color:var(--gold)}
@media(max-width:560px){
  .dvpage .dvhead.actsenh,[data-pagehead].actsenh{grid-template-columns:minmax(0,1fr) auto;column-gap:12px}
  .dvpage .dvhead.actsenh>.actsmore,[data-pagehead].actsenh>.actsmore{display:grid;grid-column:2;grid-row:1;justify-self:end;align-self:start;margin:2px 0 0}
  .dvpage .dvhead.actsenh>.badges,[data-pagehead].actsenh>.badges{grid-column:1/-1}
  .dvpage .dvhead.actsenh>.entacts,[data-pagehead].actsenh>.entacts{display:none}
  .dvpage .dvhead.actsenh.actsopen>.entacts,[data-pagehead].actsenh.actsopen>.entacts{display:flex;grid-column:1/-1;grid-row:3;justify-self:start;align-self:start;margin:0 0 10px}
}
/* ---- collapsible entry-grid groups ----
   Major Arcana / Minor Arcana and each suit beneath fold like every other
   disclosure: the .grouphd is the summary, marker absolute in its left padding
   (never a flex item -- flex wrap precedes shrink and stacks it on phones). The
   existing .grouphd rules (flex row, border-top, counts) apply unchanged. */
.dvpage details.egroup>summary.grouphd{cursor:pointer;list-style:none}
.dvpage details.egroup>summary.grouphd::-webkit-details-marker{display:none}
/* The marker is a BASELINE-ALIGNED flex item, not an absolutely pinned glyph:
   .grouphd is a no-wrap flex row with align-items:baseline, so the +/- sits on
   the label's own baseline at every heading size (the pinned version floated
   above or below depending on each variant's padding). Short labels never wrap,
   so the wrap-precedes-shrink stacking that ruled this out for the source and
   deck folds cannot happen here. */
.dvpage details.egroup>summary.grouphd::before{content:"+";color:var(--gold);font-family:var(--serif);
  font-size:1rem;line-height:1;width:1ch;flex:none;transition:.15s}
.dvpage details.egroup[open]>summary.grouphd::before{content:"\2013"}
.dvpage details.egroup>summary.grouphd:hover h3{color:var(--gold)}
/* NESTED groups (the suits under Minor Arcana): the details wrapper now sits
   between .subgroups and the heading/grid, so the child indent and the
   first-child trim re-target the wrapper -- and the category page's own
   higher-specificity .subhd rule zeroed the fold's marker padding, which left
   the +/- overlapping the label and the suits reading as plain headings. */
.dvpage .subgroups>details.egroup{margin-left:14px}
.dvpage .subgroups>details.egroup:first-child>summary.grouphd{margin-top:6px;padding-top:0;border-top:0}
@media (max-width:560px){.dvpage .subgroups>details.egroup{margin-left:0}}
@media print{.dvpage details.egroup>summary.grouphd::before{display:none}}
/* Below ~375px the sums stop working: the measure is the viewport less the 34px page
   gutter and the 15px rail gutter on each side, so a 320px phone has 222px of row to
   spend, and four 44px touch targets with 8px between them eat 200 of it. Left as it is
   the tag is squeezed into a 21px column and breaks one letter to a line. The buttons are
   the thing that folds, not the tag: capping the action track at two targets wide
   (44 + 8 + 44) turns the bar into a 2x2 block and hands the tag back ~116px, which is
   enough to read on one line. The row is still ONE row, tag left, buttons right, bottom
   edges level. 374px is the ceiling because 375px -- the narrowest common iOS width --
   still has room for the bar on a single line, and that is the layout the owner signed
   off on. */
@media(max-width:374px){
  .dvpage .dvhead{grid-template-columns:1fr}
}


/* ---- rail contents ---- */
.dvrail .crumbline{color:var(--muted);font-size:.76rem;margin:0 0 11px;line-height:1.5}
.dvrail .crumbline a{color:var(--muted);text-decoration:none}
.dvrail .crumbline a:hover{color:var(--gold)}
.dvrail .sepc{opacity:.5;margin:0 6px}
.dvrail .kick{color:var(--gold);text-transform:uppercase;letter-spacing:.15em;font-size:.68rem;font-weight:600}
/* text-wrap:balance REMOVED here, w-launchfix1-2148, 2026-08-23 (launch
   readiness audit item 14; full reasoning in App_Themes\Divinity\home.css
   at its own .homepage .fcard h4 rule, ruling 309 prevalence sweep). This
   is every entity page's own topic heading. */
/* SELECTOR RESTORED TO h1 AND MADE CLASS-BASED, w-launchfix3-2241,
   2026-08-23 (board 1793/1812 reversal): the previous lane demoted
   Entity.aspx's own topic heading from h1 to h2 to avoid a duplicate
   level-1 heading with Site.master's own brand h1, the wrong direction
   (the audit had it backwards: the page SUBJECT is the correct h1, not
   the brand). Restored to h1 with a stable class="page" so a future
   heading-level change never again requires renaming every selector that
   styles it; matches the family's own established h1.page convention
   already used by campaign/embed/today/trail. */
.dvrail .page{font-family:var(--serif);font-size:1.72rem;margin:5px 0 10px;font-weight:600;
  line-height:1.14;overflow-wrap:break-word}
.dvrail .pron{color:var(--muted);font-size:.9rem;font-style:italic;margin:-5px 0 11px}
/* The respelling's own speak control: a reader who only
   wants to hear how a name is said should not have to play the whole article.
   js/entitypage.js injects it as a .dspeak through the one panel-speak pipeline;
   the chrome.css base is a fixed 30px circle sized in px, overridden here in em
   (font:inherit first, since a button does not inherit font on its own) so the
   target and its gap track the A-/A+ control and the reader's text size. It
   stays INLINE after the respelling text on purpose: .pron is a plain block in
   a min-width:0 title column, so at 150%-200% text the line wraps normally and
   the button wraps with it instead of clipping or squeezing the respelling.
   Quieter than the h1 by design, opacity up on hover, focus and while reading;
   the pause companion panel-speak parks beside it inherits the same sizing. */
.dvrail .pron .dspeak{font:inherit;margin-left:.4em;width:auto;height:auto;
  min-width:2.1em;min-height:2.1em;padding:.3em;opacity:.7}
.dvrail .pron .dspeak svg{width:1.1em;height:1.1em}
.dvrail .pron .dspeak:hover,.dvrail .pron .dspeak:focus-visible,
.dvrail .pron .dspeak.on{opacity:1}
/* Alternate names, prominent at the top: a muted subtitle from the alias records under
   the name (owner policy 2026-07-22). Each label ("Also known as" / "Original term") is
   its own line; the aliases themselves read in the ink colour so the acknowledgement is
   visible, not buried. */
.dvrail .entaka{color:var(--muted);font-size:.92rem;line-height:1.5;margin:-2px 0 12px;max-width:64ch}
.dvrail .entaka .akaseg{display:block}
.dvrail .entaka .akalab{text-transform:uppercase;letter-spacing:.04em;font-size:.7rem;font-weight:600;color:var(--faint);margin-right:.4em}
.dvrail .entaka .akaname{color:var(--ink);font-style:italic}
.dvrail .entaka .akalang{opacity:.85}
.dvrail .entaka .akasep{color:var(--faint);margin:0 .1em}
/* The alias's own respelling (2026-07-29), from the alias-linked rows in
   the pronunciation records. Upright against the italic name beside it, em-sized so the A-/A+
   control still governs it, and no fixed box: it is text, and it is read aloud. */
.dvrail .entaka .akapron{color:var(--faint);font-style:normal;font-size:.88em}
/* The alias gloss (2026-07-28). The alias note text reached no code path at all
   until now, so 168 rows of real explanation were unreadable. Inline and wrapping
   rather than a fixed box, and rem-sized, so the A-/A+ control still governs it. */
.dvrail .entaka .akanote{color:var(--faint);font-size:.82rem;font-style:normal}
/* The one-sentence subtitle under the title -- the SPA's .lede paragraph, which said
   how big the category is and what clicking an entry does. It sits inside the header
   card with the title it belongs to; the action buttons stay outside it, because they
   are chrome and the card is the identity block. */
.dvrail .lede{margin:0;max-width:62ch}
.dvrail .badges{display:flex;gap:7px;flex-wrap:wrap;margin:0 0 14px}
.dvrail .badges:empty{display:none}
.dvrail .badge{font-size:.7rem;padding:3px 10px;border-radius:99px;background:var(--panel);
  border:1px solid var(--edge);color:var(--muted)}
.dvrail .badge.dark{color:var(--dark-c);border-color:var(--dark-c);background:var(--dark-bg)}
.dvrail a.badge{text-decoration:none;transition:color .14s,border-color .14s}
.dvrail a.badge:hover{color:var(--gold);border-color:var(--gold)}
.dvrail a.badge:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.dvrail .cta{display:inline-flex;align-items:center;gap:8px;background:var(--gold-bg);color:var(--gold-bg-ink, #1a1508);
  border:1px solid var(--gold);border-radius:11px;padding:9px 16px;font-size:.86rem;font-weight:650;
  text-decoration:none;margin:0;transition:.15s}
.dvrail .cta:hover{background:var(--gold-soft);transform:translateY(-1px)}

/* ---- action row + read-aloud button (ported from the SPA's .entacts/.readbtn) ----
   The button is server-rendered hidden and revealed by script only where
   speechSynthesis exists, so these rules must not assume it is visible.

   [data-pageacts] IS PAIRED WITH .dvpage IN EVERY SELECTOR HERE, and that is the
   whole point of it. These rules were scoped under .dvpage because the only two
   pages with an action row were the entity and category pages, which are both
   .dvpage. The row is now also emitted by App_Code/View/PageActions.vb onto /trails
   and /trail/{slug} (owner, 2026-07-30), and those use .authwrap -- so under the old
   selectors the buttons would have rendered as bare unstyled boxes.

   Exactly the trap the .followbtn block below documents having avoided, and the same
   one the .grouphd rules fell into before it: a component scoped to the first page
   that happened to use it, discovered by the third page that uses it. Rather than
   drop .dvpage (which would change specificity for the two existing pages and could
   let an unrelated .entacts elsewhere pick these up), the shared row opts IN by its
   own attribute. */
.dvpage .entacts,.entacts[data-pageacts]{display:flex;align-items:center;gap:var(--actgap);flex-wrap:wrap;margin:0 0 13px}
.dvpage .entacts:empty,.entacts[data-pageacts]:empty{display:none}
.dvpage .readbtn,[data-pageacts] .readbtn{background:var(--panel);border:1px solid var(--edge2);color:var(--ink);border-radius:10px;
  width:var(--actbtn);height:var(--actbtn);padding:0;font:inherit;line-height:1;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;transition:.14s}
.dvpage .readbtn[hidden],[data-pageacts] .readbtn[hidden]{display:none}
.dvpage .readbtn svg,[data-pageacts] .readbtn svg{width:var(--actico);height:var(--actico);display:block}
.dvpage .readbtn:hover,.dvpage .readbtn.on,
[data-pageacts] .readbtn:hover,[data-pageacts] .readbtn.on{border-color:var(--gold);color:var(--gold)}
.dvpage .readbtn:focus-visible,[data-pageacts] .readbtn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* ---- the box js/panel-speak.js's injected .dspeak takes in this row ----------
   FOLDED IN FROM App_Themes/Divinity/pageacts.css, the one move still owed
   after that file existed only because site.css was held elsewhere on the
   night this shipped; PageActions.Assets() no longer emits a stylesheet
   link and the file is gone, so no page has to remember a link at all. The two
   scoped restatements that predated it are gone with it: entity.css's
   .dvpage .entacts .dspeak, and trail.css's .trailpage .entacts .dspeak, which
   had already collapsed onto the shared file.

   WHY THE ROW NEEDS IT. chrome.css draws a .dspeak as a 30px borderless circle
   dulled to 55% with margin-left:auto -- right for the inline hosts it was
   written for (a fact panel, a Learn-more summary) and wrong in a row of icon
   buttons, where the auto margin pushes the control away from copy and share and
   the dulling reads as no control at all. It is not optional for any surface
   with a read control: panel-speak injects the read button itself where the bar
   asked for SpeakMode.Host, and the pause/resume companion arrives as a .dspeak
   on EVERY page that runs a reading, whichever SpeakMode the bar uses.

   Specificity, not load order, is what makes this win: [data-pageacts] .dspeak
   is (0,2,0) against chrome.css's (0,1,0), so it holds even though chrome.css
   loads last. Nothing in chrome.css is touched and no copy of panel-speak is
   made. */
[data-pageacts] .speakbar{display:inline-flex;align-items:center;min-width:0}
/* width/height auto with a min box, rather than a fixed box: the injected button
   carries a text label span, and although that span is taken out of flow below,
   a min box cannot clip a control whose contents outgrow it. */
[data-pageacts] .dspeak{margin-left:0;width:auto;height:auto;min-width:var(--actbtn);min-height:var(--actbtn);
  padding:0;background:var(--panel);border:1px solid var(--edge2);border-radius:10px;
  color:var(--ink);font:inherit;line-height:1;opacity:1}
[data-pageacts] .dspeak svg{width:var(--actico);height:var(--actico)}
[data-pageacts] .dspeak:hover,[data-pageacts] .dspeak.on{border-color:var(--gold);color:var(--gold);
  background:var(--panel)}
[data-pageacts] .dspeak:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* The pause/resume companion keeps its gap from the button it parks beside.
   chrome.css sets margin-left:8px on .dpause for exactly this and the rule
   above would otherwise flatten it to 0 along with the auto margin. */
[data-pageacts] .dspeak.dpause{margin-left:var(--actgap)}
/* THE TEXT LABEL IS HIDDEN, NOT REMOVED, an icon-only decision rendered in CSS.
   panel-speak fills an "entity" host with a labelled button, which is right on a
   page where the control stands alone and wrong in an icon row, where the words
   break the row. The span stays in the document and the button keeps its title
   and aria-label, so a screen reader is told exactly what the control is; only
   the eye stops seeing the label twice. The owner was offered a visible label on
   every page, was told what it would cost, and chose one consistent icon
   position and size across all surfaces. Do not restore the label on one family:
   it returns everywhere at once or not at all. */
[data-pageacts] .dspeak .dspeak-t{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
/* Star / favorite: reuses .readbtn, but is VISIBLE (not script-revealed) because it
   works without JS -- a POST form for a member, a sign-in link for a guest. The form
   wrapper must not add its own box, and the anchor variant drops the link underline. */
.dvpage .starform{margin:0;padding:0;display:inline-flex}
.dvpage a.starbtn,.dvpage a.starbtn:hover{text-decoration:none}

/* ---- follow button ----
   The same 40px icon control as .readbtn above, and DELIBERATELY NOT scoped under
   .dvpage: FollowRender puts this control on the entity page, the community page,
   the forum room and the open questions page, and only the first of those is a
   .dvpage. Scoping it would leave three surfaces with an unstyled button, which is
   the same trap the .grouphd rules fell into.

   Sized from --actbtn, the SAME token .readbtn above uses, because the two sit
   side by side on an entity page and either one drifting out of line with the
   other as the reader scales text is the defect. This block used to say "sized
   in px to match .readbtn exactly ... a rem-sized one would drift out of line
   with it", which diagnosed the risk correctly and then answered it by opting
   BOTH controls out of the reader's text size. The token answers it the other
   way: they move together, and they still match, because there is now one number
   for the whole row. The SVG inside carries the meaning and scales with the box. */
.followbtn{background:var(--panel);border:1px solid var(--edge2);color:var(--ink);border-radius:10px;
  width:var(--actbtn);height:var(--actbtn);padding:0;font:inherit;line-height:1;cursor:pointer;
  display:inline-flex;align-items:center;justify-content:center;transition:.14s;flex:none}
.followbtn svg{width:var(--actico);height:var(--actico);display:block}
.followbtn:hover{border-color:var(--gold);color:var(--gold)}
/* Following: gold and filled, the same on-state the star uses two controls away. */
.followbtn.on{border-color:var(--gold);color:var(--gold)}
.followbtn:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* The anchor variant (signed out) must not carry a link underline, and the form
   wrapper must not add a box of its own. */
a.followbtn,a.followbtn:hover{text-decoration:none}
.followform{margin:0;padding:0;display:inline-flex}

/* ---- content column ---- */
.dvpage .lead{font-size:1.04rem;line-height:1.72;color:var(--ink);max-width:var(--measure);margin:0 0 14px}
.dvpage .factline,.dvpage .countline{margin:0 0 16px;color:var(--muted);font-size:.9rem}
.dvpage .factline .fl,.dvpage .countline .fl{color:var(--gold);text-transform:uppercase;
  letter-spacing:.12em;font-size:.66rem;font-weight:600;margin-right:9px}
/* Sections are cards, not run-on prose blocks -- this is what turns the page from a
   document into a detail view. */
.dvpage .sec{background:var(--panel);border:1px solid var(--edge);border-radius:14px;
  padding:15px 18px 17px;margin:0 0 16px}
/* A section that holds CARDS is not itself a card. The entry list was a panel with a
   grid of panels inside it -- boxes in boxes, and the blue slab behind everything that
   the home page has no equivalent of. The home page states a heading on the page ground
   and puts the tiles straight beneath it; that is the look these pages match. Sections
   holding PROSE or PILLS keep the panel, because there the card is the only thing
   separating them from the page. */
.dvpage .sec.oncanvas{background:none;border:0;border-radius:0;padding:0;margin:0 0 8px}
.dvpage .sec>h2{font-family:var(--serif);color:var(--gold);font-size:1.12rem;margin:0 0 12px;font-weight:600}
.dvpage .sec>.lead{margin-bottom:0}

/* A relation group is a HEADING with its pills beneath it -- the same shape as a card
   group heading, so the two read as one system. It used to be a two-column grid with the
   label in a 132px rail on the left, which indented every chip row and made the label a
   caption rather than a heading. The pills stay inside their section panel; only the
   head came out of the rail. */
.dvpage .relgroup{display:block;margin:20px 0 0}
.dvpage .sec>h2+.relgroup{margin-top:0}
.dvpage .relgroup .rl{font-family:var(--serif);font-size:.98rem;font-weight:600;
  color:var(--ink);text-transform:capitalize;letter-spacing:0;
  margin:0 0 10px;padding:0 0 7px;border-bottom:1px solid var(--edge)}
/* .relgroup is a native <details> fold now (Entity.aspx BuildRelations /
   BuildCrossTradition): the .rl heading became its <summary>. The open state is
   visually unchanged; these rules only add the disclosure affordance, reusing the
   +/- marker scheme of the .lf folds. Site.master already lays .rl out as a
   flex row for the read-aloud button, so the marker slots in as a flex item. */
.dvpage details.relgroup>summary.rl{cursor:pointer;list-style:none}
.dvpage details.relgroup>summary.rl::-webkit-details-marker{display:none}
.dvpage details.relgroup>summary.rl::before{content:"+";color:var(--gold);font-family:var(--serif);
  font-size:1.1rem;line-height:1;width:1ch;flex:none;transition:.15s}
.dvpage details.relgroup[open]>summary.rl::before{content:"\2013"}
.dvpage details.relgroup>summary.rl:hover{color:var(--gold)}
/* The group label is a REAL heading element inside its <summary> now
   (Entity.aspx.vb BuildCrossTradition, 2026-07-30). It must look exactly as the bare
   summary text did, so it inherits everything and drops the UA margin: the styling
   still belongs to .rl, the h3/h4 is there purely to give the section a heading
   outline a screen reader can skim. min-width:0 because .rl is a flex row and this
   is its shrinkable member; without it a long label cannot wrap. */
.dvpage .rl>.rlh{font:inherit;color:inherit;letter-spacing:inherit;text-transform:inherit;
  margin:0;padding:0;min-width:0;flex:1 1 auto;display:flex;align-items:baseline;
  gap:.5em;flex-wrap:wrap}
/* Cross-tradition label groups split by target entity type: a nested fold per type,
   so "Objects And Practices Of This Tradition" becomes Doctrines, Rituals, Sacred
   Sites and so on. Indented a little and quieter than its parent, so the nesting is
   legible; em not rem throughout so the whole block tracks the A-/A+ control. */
.dvpage .xtsubs{display:flex;flex-direction:column;gap:.15em}
.dvpage details.relgroup.xtsub{margin:0;padding-left:.75em;border-left:1px solid var(--edge)}
.dvpage details.relgroup.xtsub>summary.rl{font-size:.9em;margin-bottom:0;padding-bottom:.45em}
.dvpage details.relgroup.xtsub[open]>summary.rl{margin-bottom:.6em}
/* The count behind a fold. A pill, but a text-sized one: no fixed height and no
   fixed width, so it grows with the text instead of clipping it at 200%. */
.dvpage .rl .xtn{font-family:var(--sans);font-size:.72em;font-weight:600;color:var(--faint);
  background:var(--panel2);border:1px solid var(--edge);border-radius:99px;
  padding:.1em .55em;letter-spacing:.02em;flex:none}

/* ---- section heading ----
   A section's title sits ON THE PAGE with a rule under it, and the panel holding its
   content starts below. It used to be the first child INSIDE the panel, which made
   every section a titled box; the group headings within them already read as
   heading-then-rule-then-content, so the section titles now match. The divider belongs
   here, not under the page's own h1 -- the h1 names the page, it does not open a
   section.

   ,.ridoc .sechd ADDED: ReportItems.aspx's detail view carries
   "legal ripage ridoc" on its own root, never "dvpage" -- unlike the book chapter
   reader and every Page.aspx prose page (About, the FAQ, the essay pages), which
   all wrap their content in a "dvpage" div for exactly this rule. Missing it, the
   report detail page's h2.sechd headings fell back to the browser default heading
   style: no gold, no serif, no 1.12rem weight, only the margin page.css's own
   ".legal .sec>h2.sechd" rule still supplied. Widened here rather than copied so
   there is still one declaration of what a section heading looks like. */
.dvpage .sechd,.ridoc .sechd{font-family:var(--serif);color:var(--gold);font-size:1.12rem;font-weight:600;
  margin:26px 2px 11px;padding:0}

/* A section heading that also hosts a per-section speak button is a FLEX ROW,
   title first then the button pushed hard right -- the same composition
   already built three separate times (.casespage .sec>h2.sechd in cases.css,
   .chapdoc/.ridoc .sec>h2.sechd in page.css), centralised here so it applies
   once to every .dvpage surface (which, per the "cmppage dvpage" and "smpage
   dvpage" dual-class convention, includes Compare and the spread map) instead
   of being re-derived a fourth time.

   FIXES w942-pastyear, 2026-08-12, an owner sighting on the lords-of-the-night
   category page that widened into a class: the point-in-time banner, the year
   picker and seven headings on Compare (Point In Time, Not Yet Adopted, Side
   by Side, Sources, Attributes, Somewhere to Start, Recent Comparisons) all
   built their speak button by wrapping BOTH the heading and the button in a
   div carrying the sechd class, instead of putting the class on the heading
   itself. The div picked up this rule's font-size, color and weight; the h2
   inside it matched no heading rule at all, so it rendered at the browser's
   own bold ~1.5em default with its own un-overridden top and bottom margin
   stacking against the section's -- oversized text and doubled vertical gap
   at once, exactly what was reported. sechd now sits on the h2 in all nine
   places, matching the contract BookDocument.vb, the case docket and the
   chapter readers already use; .chaphd is that same contract's shrinkable
   text member, needed here because "Not Yet Adopted In -44" is not a fixed
   short string. min-width:0/flex:1 1 auto on it and flex:0 0 auto/
   margin-left:auto on the button match the min-width:0 rule every other flex
   row of text on this site already carries for 200% text. */
.dvpage .sec>h2.sechd{display:flex;align-items:center;gap:.5em}
.dvpage .sec>h2.sechd>.chaphd{min-width:0;flex:1 1 auto}
.dvpage .sec>h2.sechd>.speakhost{flex:0 0 auto;margin-left:auto;display:inline-flex}

/* First child now that the h2 has moved out, so the adjacency rules that used to key
   off "h2 + x" key off :first-child instead. */
.dvpage .sec>.relgroup:first-child{margin-top:0}
.dvpage .sec>.grouphd:first-child{margin-top:0;padding-top:0;border-top:0}

/* ---- collapsible AUXILIARY sections (owner QA round 3, 2026-07-24; round 3b,
   2026-07-24) ----
   Correspondences & relations, cross-tradition connections, in the texts, this
   card in the decks, denominations/childsec, referenced by and sources are each a
   native <details class="sec ..."> whose <summary class="sechd"> carries the same
   heading text and styling the h2 above used to -- collapsible, and (round 3b)
   DEFAULT COLLAPSED, so a reader lands on a page of headings and opens only what
   they want to read. The nested .relgroup folds inside Correspondences and
   Cross-Tradition start collapsed too, with no exceptions now (round 3b removed
   the "open unless reference-grade" split; DisplayLabels.CollapsedByDefault is
   unused but left in place). History & origin, learn more, community and
   leadership & clergy stay plain h2 + section: primary descriptive content, never
   collapsed.
   Round 3b also dropped the .oncanvas class from decksec, psgsec and childsec: a
   collapsed <details class="sec"> reads as an empty control until it is opened, so
   every one of these auxiliary sections now keeps the SAME card background as
   Correspondences / Cross-Tradition / Referenced By / Sources -- .oncanvas (which
   strips the panel for content that already flows on the page ground, like History
   and Learn More) no longer belongs on a section whose whole first-paint state is
   its own summary line. See .dvpage .sec / .dvpage .sec.oncanvas above.
   The marker and cursor mirror the .lf / .relgroup folds already on this page.
   display:flex is the "minimal summary flex CSS" the panel-speak read-aloud button
   needs: .dspeak (Site.master) is margin-left:auto, so it only lands at the TOP
   RIGHT of the row when its flex parent actually is one -- .sechd alone (as a
   plain h2) never needed this, so it did not have it until now. */
.dvpage details.sec>summary.sechd{cursor:pointer;list-style:none;display:flex;align-items:center;gap:9px}
.dvpage details.sec>summary.sechd::-webkit-details-marker{display:none}
.dvpage details.sec>summary.sechd::before{content:"+";color:var(--gold);font-family:var(--serif);
  font-size:1.1rem;line-height:1;width:1ch;flex:none;transition:.15s}
.dvpage details.sec[open]>summary.sechd::before{content:"\2013"}
.dvpage details.sec>summary.sechd:hover{color:var(--gold)}
/* The heading is now a SUMMARY, not an h2, so it is the section's first child and
   its immediately-following group needs the same top-margin reset the old "h2 + x"
   rules gave it (see the comment above) -- the general :first-child rules above no
   longer match once the summary retakes that slot. */
.dvpage details.sec>summary+.relgroup{margin-top:0}
.dvpage details.sec>summary+.grouphd{margin-top:0;padding-top:0;border-top:0}

/* ---- TAROT PILOT (v2.1 facts/layout redesign, 2026-07-25): title OUTSIDE the
   card -----------------------------------------------------------------------
   ADDITIVE ONLY: new selectors, existing tokens, nothing above this block is
   touched. Entity.aspx.vb's TarotSection() is the only thing that emits
   <details class="tsec">, and only for a pilot typeKey (tarot today). Its
   <summary class="sechd"> carries the SAME sechd class as every section
   heading on the page -- the base color/font/size rule at ".dvpage .sechd"
   above already applies unchanged, so the heading looks exactly like today's
   Learn More heading. What is new here is only the fold affordance (marker,
   cursor, flex layout for the read-aloud slot panel-speak.js would add to any
   OTHER details summary -- suppressed at the title level here via the
   data-nospeak attribute the markup carries, see js/panel-speak.js), mirroring
   ".dvpage details.sec>summary.sechd" above one-for-one but scoped to ".tsec"
   so the existing rule for the OTHER (non-pilot) collapsible sections is never
   touched. The card itself -- ".sec", unmodified -- is TarotSection's nested
   <section>, a normal child in the details' content, so it already gets the
   card's background/border/padding from the untouched ".dvpage .sec" rule
   above and simply is not part of the accessibility tree until expanded. */
.dvpage details.tsec>summary.sechd{cursor:pointer;list-style:none;display:flex;align-items:center;gap:9px}
.dvpage details.tsec>summary.sechd::-webkit-details-marker{display:none}
.dvpage details.tsec>summary.sechd::before{content:"+";color:var(--gold);font-family:var(--serif);
  font-size:1.1rem;line-height:1;width:1ch;flex:none;transition:.15s}
.dvpage details.tsec[open]>summary.sechd::before{content:"\2013"}
.dvpage details.tsec>summary.sechd:hover{color:var(--gold)}

/* ---- the Browse/Compare explorer needs MORE than the standing section gap
   above it (w555-entityspacing, owner sighting from his phone: the collapsed
   Learn More disclosure and the Browse/Compare toggle beneath it "sit nearly
   flush ... there should be a clear gap between the two as they serve
   different functions"). Learn More opens THIS PAGE'S OWN prose; Denominations
   (or whichever child group) hosts CompareIsland's Browse/Compare switch,
   which drives a different surface, the related-content explorer. Every other
   section-to-section seam on this page shares one rhythm unit, the base
   ".dvpage .sechd" rule's 26px top / 11px bottom margin, and that unit is what
   read as flush: it is a FIXED PX value that never grows with the A-/A+
   text-size control, so at 150-200% zoom, the size the owner actually reads
   at daily, the gap shrinks to nothing relative to the enlarged text around
   it. That base rule stays untouched everywhere else on the page, so every
   ordinary section-to-section transition keeps its existing look; only the
   explorer's OWN heading gets pulled further down, in rem so it tracks the
   text-size control instead of freezing in place. Sized to roughly double the
   page's own inter-section unit (26px is 1.7333rem at this page's 15px root,
   ".dvpage details.tsec" child summary reads that as
   "roughly double" rather than a made-up figure), so the two controls read as
   separate regions rather than one compound bar, at any viewport width and at
   any text scale.

   RETARGETED FROM ":has(>.childsec)" TO A PLAIN CLASS (w621-mobilepolish,
   2026-08-10). The :has() version above was live and, on paper, correct: the
   selector matched the real served markup, nothing later in this file or in
   any other loaded stylesheet overrode it, and no width-scoping fenced it
   off, all confirmed by reading the served CSS and HTML directly. The owner's
   phone still showed no gap. Rather than leave a fix standing on reasoning
   that could not be confirmed against his actual device, EntityMarkup.vb's
   section shell (App_Code/View/EntityMarkup.vb) now stamps
   "tsec-gapbefore" onto the DETAILS element itself, server side, whenever a
   section is built with "childsec" among its own classes, which the
   Denominations-style explorer always is. A plain class needs no relational
   pseudo-class support and cannot silently fail to match the way a :has()
   combinator chain, in principle, still could on some engine or version this
   review had no way to test against. Same rhythm, same rem sizing, same
   "everywhere the explorer follows another section, not fenced to a width"
   scope; only the selector mechanism changed.

   NOT WIDTH-SCOPED, on purpose. Measured against the served markup: the base
   ".dvpage .sechd" margin carries no @media override anywhere in this file,
   so the tight gap is identical in the DOM and in the CSS at every viewport;
   the crowding just reads worse on a phone because entitypage.js's autoExpand
   only opens every top-level fold at 1000px and up, so a narrow viewport is
   far more likely to show two bare summary bars stacked with nothing else
   between them. Scoping this rule to a narrow-width media query would leave
   the identical desktop seam unfixed and would still fail the moment a
   desktop reader leaves a section collapsed, so the extra gap applies
   everywhere the explorer follows another section, not only under 760px. */
.dvpage details.tsec.tsec-gapbefore>summary.sechd{margin-top:3.5rem}

/* ---- chips (the SPA's .echip, as real anchors) ---- */
.dvpage .chips{display:flex;gap:8px;flex-wrap:wrap}
/* flex-wrap:wrap IS AN ACCESSIBILITY FIX, NOT A LAYOUT CHOICE (2026-08-02).
   A chip is a flex ROW holding the name, the .et type tag (flex:none), the .cq
   qualifier and, on Compare, an .xtb status or stance badge that compare.css
   also pins with flex:none. Two flex:none children on a single-line row cannot
   shrink and .xtb carries white-space:nowrap, so once their content is wider
   than the chip the row simply runs out past max-width:100% -- and what is
   outside is CLIPPED by html{overflow-x:hidden} with no scrollbar and no sign
   anything is missing. The reader does not see a squeezed badge; they see no
   badge at all, and the chip reads as an unqualified claim.

   MEASURED on Compare at a 390px phone width, 2026-08-02: clean at 100%, TWO
   "Well-attested" badges gone at 150%, and THIRTY-FOUR elements gone at 200%,
   one of them 138px outside the viewport. It gets worse the larger the reader
   sets the text, which is to say it only appears for the reader it hurts.

   Nothing moves at ordinary sizes: a flex line wraps only when its content will
   not fit, and at the default nothing does. This is a repair to a case that was
   destroying content, not a redesign of a ported layout. */
.dvpage .chip{background:var(--panel2);border:1px solid var(--edge);border-radius:99px;
  padding:6px 13px;font-size:.86rem;text-decoration:none;color:var(--ink);max-width:100%;
  display:inline-flex;gap:7px;align-items:center;flex-wrap:wrap;overflow-wrap:anywhere;transition:.13s}
.dvpage .chip:hover{border-color:var(--gold);transform:translateY(-1px)}
.dvpage .chip .et{color:var(--faint);font-size:.7rem;text-transform:uppercase;letter-spacing:.05em;flex:none}
/* The chip QUALIFIER: the few words that tell two same-named chips apart, printed
   only on a chip whose name collides inside its own group (Entity.aspx.vb,
   ChipQuals). Four rows named "Bishop" become Roman Catholicism / Eastern
   Orthodoxy / Anglicanism / Christianity Roles.
   Quieter than the name and NOT uppercased: this is a proper noun, often a
   tradition, so .et's letter-spaced caps treatment would be wrong for it.
   em, not rem, so it tracks the chip's own .86rem and stays proportionally
   smaller when the A+ control scales the root -- and min-width:0 because .chip is
   a flex row and this is its shrinkable member. No flex:none here, unlike .et:
   a tradition name is long enough to need to give way, and nowrap is what turns a
   name into three characters and an ellipsis on a phone. */
.dvpage .chip .cq{color:var(--faint);font-size:.82em;min-width:0;overflow-wrap:anywhere}
.dvpage .chip .cq::before{content:"\00b7";margin-right:.45em;color:var(--edge2)}
.dvpage .chip.dk{border-color:color-mix(in srgb,var(--dark-c) 45%,var(--edge))}
.dvpage .chip.dk .et{color:var(--dark-c)}

/* ---- prose auto-links (ProseAutoLinker) ----
   The first in-prose mention of another entity inside a Learn-more article body is
   wrapped in <a class="plink">. Styled subtle-but-distinct: the gold accent plus a
   faint underline sets it apart from body text without shouting the way the
   correspondence chips do, and it does NOT restyle ordinary links. Theme-aware for
   free -- --gold / --gold-soft are defined for both light and dark. Rolling the
   feature back is removing this rule (or the one server call); nothing else styles
   .plink. */
.dvpage .lfb a.plink,a.plink{
  color:var(--gold);text-decoration:none;
  border-bottom:1px solid color-mix(in srgb,var(--gold) 42%,transparent);transition:.13s}
.dvpage .lfb a.plink:hover,a.plink:hover{color:var(--gold-soft);border-bottom-color:var(--gold)}

/* ---- sub-group headings inside a long entry list ----
   Lived in a <style> block in Category.aspx while this file was owned by another
   change; they belong beside .chips, which is what they head, so they are here now.
   Rules off the top so each group reads as its own block, except for the first,
   which already has the section's own h2 above it. */
.dvpage .grouphd{display:flex;align-items:baseline;gap:9px;
  margin:18px 0 10px;padding-top:12px;border-top:1px solid var(--edge)}
.dvpage .sec>h2+.grouphd{margin-top:0;padding-top:0;border-top:0}
.dvpage .grouphd h3{font-family:var(--serif);font-size:.98rem;font-weight:600;color:var(--ink);margin:0}
.dvpage .grouphd .gc{color:var(--faint);font-size:.72rem;font-variant-numeric:tabular-nums;
  border:1px solid var(--edge);border-radius:99px;padding:1px 8px}

/* ---- the "Group By" / "Browse By" facet bar ----
   Shared by Category.aspx's Group By switcher and OpenQuestions.aspx's Browse By.
   Both emit .chips>.chip already, so all this adds is the label and the ACTIVE
   state, which had no rule at all: the selected pill was rendering identically to
   the unselected ones on every faceted page. Matched to .schip.on rather than
   invented, so a selected chip looks the same wherever one appears. */
.dvpage .facetbar{align-items:baseline;margin:14px 0 6px}
.dvpage .facetlbl{color:var(--faint);font-size:.72rem;text-transform:uppercase;letter-spacing:.07em}
.dvpage .chip.on{color:var(--gold);border-color:var(--gold);
  background:color-mix(in srgb,var(--gold) 10%,transparent);font-weight:600}
.dvpage .axisnote{color:var(--faint);font-size:.82rem;margin:0 0 4px;max-width:var(--measure)}

/* ---- Open Questions: the contributor-facing index ----
   Deliberately HERE and not in a <style> block on OpenQuestions.aspx. The page
   groups with details.egroup>summary.grouphd, chips for the browse bar and the
   specialty tags, and .factline/.fl for the metadata rows -- all of which are
   defined above and are shared with the entity and category pages, so the look
   stays coherent and there is one place to change it. Only what is genuinely
   unique to this page is below. (Same reasoning as the .grouphd note above, which
   records these being moved out of Category.aspx's own <style> block.) */
/* A question uses the shared .lf disclosure card. Only the two-line summary is
   particular to it: the entity it hangs off, then the question itself. */
.dvpage .oqanchor{display:block;color:var(--gold-soft);font-size:.72rem;
  text-transform:uppercase;letter-spacing:.07em;margin:0 0 2px;font-family:var(--sans);
  font-weight:600}
.dvpage .oqt{display:block;font-family:var(--serif);font-size:1.02rem;line-height:1.45;
  max-width:var(--measure)}
.dvpage .lfb .factline{margin:0 0 8px}
.dvpage .oqsrc{font-size:.82rem;color:var(--faint);margin:8px 0 0}
/* The specialty checkbox panel. auto-fill minmax in rem so the
   columns collapse to one as the text size grows rather than at a fixed width. */
.dvpage .lfb fieldset{border:0;margin:0;padding:0;min-width:0}
.dvpage .lfb legend{color:var(--faint);font-size:.72rem;text-transform:uppercase;
  letter-spacing:.07em;padding:0 0 6px}
.dvpage .spgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(15rem,1fr));gap:4px 14px}
.dvpage .spgrid label{display:flex;gap:7px;align-items:baseline;font-size:.9rem;
  color:var(--muted);line-height:1.5}
.dvpage .spgrid label:hover{color:var(--ink)}
.dvpage .spgrid input{margin:0;flex:0 0 auto}
.dvpage .spgrid .c{color:var(--faint);font-size:.8rem;font-variant-numeric:tabular-nums}
.dvpage .oqtahead{margin:11px 0 8px;display:flex;gap:8px;flex-wrap:wrap;align-items:baseline}
.dvpage .oqtahead input[type=search]{font:inherit;font-size:.9rem;padding:5px 9px;
  border:1px solid var(--edge);border-radius:6px;background:var(--panel2);color:var(--ink);
  min-width:0;flex:1 1 12rem}
.dvpage .oqacts{margin:14px 0 0;display:flex;gap:9px;flex-wrap:wrap;align-items:center}
.dvpage .oqactive{margin:14px 0 0;font-size:.88rem;color:var(--muted)}
.dvpage .oqactive b{color:var(--ink)}
.dvpage .oqempty{border:1px solid var(--edge);border-radius:8px;padding:18px 18px 16px;margin:22px 0 0}
.dvpage .oqempty p{margin:0 0 8px;color:var(--muted);line-height:1.66}
.dvpage .oqempty p:last-child{margin:0}

/* ---- page eyebrow outside the identity card ----
   .kick was defined ONLY as .dvrail .kick, so an index page that wants the eyebrow
   without the full entity header card had to redeclare it. Three pages had their
   own copy with identical values. Same declaration, one selector wider. */
.dvpage .kick{color:var(--gold);text-transform:uppercase;letter-spacing:.15em;
  font-size:.68rem;font-weight:600;margin:0}
/* text-wrap:balance REMOVED here, w-launchfix1-2148, 2026-08-23 (launch
   readiness audit item 14; full reasoning in App_Themes\Divinity\home.css
   at its own .homepage .fcard h4 rule, ruling 309 prevalence sweep). */
/* SELECTOR RESTORED TO h1 AND MADE CLASS-BASED, w-launchfix3-2241,
   2026-08-23 (board 1793/1812 reversal): see .dvrail .page above for the
   full reasoning. Every category/index page rendering inside .dvpage
   keeps its own topic heading as h1, class="page". */
.dvpage>.page{font-family:var(--serif);font-size:1.72rem;margin:5px 0 10px;font-weight:600;
  line-height:1.14;overflow-wrap:break-word}
.dvpage>.lede{color:var(--muted);font-size:1.02rem;line-height:1.7;margin:0 0 8px;
  max-width:var(--measure)}

/* ---- Connections: the crawlable index of every typed relation ----
   Moved out of Connections.aspx's own <style> block. Its header rules were a
   character-for-character copy of .dvrail's, and its separators used var(--rule),
   a variable DEFINED NOWHERE in this codebase -- so every border-top on the page
   was falling back to currentColor instead of the hairline it was meant to be.
   That is the cost of a per-page stylesheet: nobody diffs it against the real one. */
.dvpage .cx ul.types,.dvpage .cx ul.pairs{list-style:none;margin:22px 0 0;padding:0}
.dvpage .cx ul.types li{border-top:1px solid var(--edge);padding:10px 0;
  display:flex;flex-wrap:wrap;gap:8px;align-items:baseline}
.dvpage .cx ul.pairs li{border-top:1px solid var(--edge);padding:9px 0;line-height:1.6}
.dvpage .cx ul.types li:last-child,.dvpage .cx ul.pairs li:last-child{border-bottom:1px solid var(--edge)}
.dvpage .cx ul.types a{font-family:var(--serif);font-size:1.06rem;color:var(--ink);
  text-decoration:none;border-bottom:1px solid var(--gold-soft)}
.dvpage .cx ul.pairs a{color:var(--ink);text-decoration:none;border-bottom:1px solid var(--gold-soft)}
.dvpage .cx ul.types a:hover,.dvpage .cx ul.types a:focus-visible,
.dvpage .cx ul.pairs a:hover,.dvpage .cx ul.pairs a:focus-visible{color:var(--gold)}
.dvpage .cx ul.types .n{color:var(--faint);font-size:.85rem}
.dvpage .cx ul.pairs .rel{color:var(--gold-soft);font-size:.82rem;text-transform:uppercase;
  letter-spacing:.07em;margin:0 6px}
.dvpage .cx .pager{margin:20px 0 0;display:flex;gap:14px}
.dvpage .cx .pager a,.dvpage .cx .backlink a{color:var(--gold-soft)}
.dvpage .cx .backlink{margin:14px 0 0;font-size:.9rem}

/* ---- the category-page group heading, matched to the home page ----
   The home page's .dgroup .gh (Default.aspx): serif title, a rule UNDER it rather
   than over, and the count as a bordered badge off at the right that is a LINK --
   there, into the section; here, into the group's own page. The owner compared the
   two side by side and asked for one heading, so this is that heading, scoped to
   .catpage so the entity page's .grouphd (a different file, and not this change's
   to move) keeps the styling it has.

   Three things are deliberate:

   * --gold, not --gold-soft. The home page uses --gold-soft as the heading colour
     and it is a bug there: in the LIGHT palette --gold-soft is LIGHTER than --gold,
     so it washes out on a panel. Same rule as the compare block below -- see the
     note there. Copied forward it would only spread.

   * the h3 GROWS rather than the badge taking margin-left:auto. The home page has
     two children in this row and can push the second one over; this row has three,
     because the select-all button lives here too, and two auto margins would split
     the free space and strand the button in the middle. Letting the title take the
     slack pins the badge to the right edge and parks the button just inside it.

   * the badge is last in the markup and stays last. The select-all is hidden in
     Browse mode, so had it been emitted after the badge, flipping the mode switch
     would have slid the count sideways every time. */
.dvpage.catpage .grouphd{align-items:baseline;gap:10px;
  margin:22px 2px 11px;padding:0 0 7px;border-top:0;border-bottom:1px solid var(--edge)}
.dvpage.catpage .sec>h2+.grouphd{margin-top:0}
.dvpage.catpage .grouphd h3{font-family:var(--serif);font-size:1.1rem;font-weight:600;
  color:var(--gold);margin:0;flex:1 1 auto;min-width:0}
/* On the domain page the category name is itself the link to the category. */
.dvpage.catpage .grouphd h3 a{color:inherit;text-decoration:none}
.dvpage.catpage .grouphd h3 a:hover{text-decoration:underline}
/* A HARD height AROUND rem TEXT, WHICH IS THE SAME DEFECT AS THE REPORT PAGE'S
   ?-CIRCLE AND WAS FOUND BY THE SAME SWEEP (2026-08-03). The count inside this
   pill is .92rem, so it grows with A+, while height:32px did not: the box was a
   ceiling rather than a floor, and at raised text sizes the digits met the
   border. min-height instead of height is the whole fix, and the sizes are em so
   the pill tracks the count it holds. 2.32em against this rule's own .92rem
   (13.8px at the 93.75% base) is 32.0px, so nothing moves at the default and
   this stays a scaling fix rather than a resize, which is what layout parity
   asks of a ported layout. white-space:nowrap is kept: this is a number, not prose,
   and the pill now grows instead of clipping it. */
.dvpage.catpage .grouphd .gc{background:var(--panel);border:1px solid var(--edge2);
  color:var(--muted);border-radius:9px;min-width:2.32em;min-height:2.32em;padding:0 .58em;
  font:inherit;font-size:.92rem;line-height:1;display:inline-flex;align-items:center;
  justify-content:center;flex:none;white-space:nowrap;cursor:pointer;text-decoration:none;
  transition:.14s;font-variant-numeric:tabular-nums}
.dvpage.catpage .grouphd a.gc:hover{border-color:var(--gold);color:var(--gold)}
.dvpage.catpage .grouphd a.gc:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* the title has the slack now, so the button does not need to claim it */
.dvpage.catpage .grouphd .gsel{margin-left:0}

/* ---- the second level: groups inside a group ----
   Minor Arcana's four suits, under the Minor Arcana heading. The wrapper is NOT a
   styling hook and must not be flattened away: js/dvcompare.js resolves a
   select-all's cards as btn.parentNode.nextElementSibling, so the parent .grouphd
   has to be followed by ONE element holding all 56 cards. It carries no box of its
   own -- the indent below is what says "inside" -- so removing it would look
   identical and would quietly break the parent's select-all.

   The nested heading is the same .grouphd, dialled down: smaller, muted rather than
   gold, and a dashed rule instead of a solid one, so the eye still reads the gold
   solid-ruled heading above it as the one that owns the block. Indented rather than
   merely restyled, because two headings that differ only in weight read as a list of
   sibling headings; one that starts further in reads as a child. */
.dvpage .subgroups{display:block}
.dvpage .subgroups>.grouphd,.dvpage .subgroups>.dvgrid{margin-left:14px}
.dvpage .grouphd.subhd h3{font-size:.9rem;color:var(--muted)}
.dvpage.catpage .grouphd.subhd{margin:16px 2px 10px 14px;padding:0 0 6px;
  border-top:0;border-bottom:1px dashed var(--edge)}
.dvpage.catpage .grouphd.subhd h3{font-size:.95rem;color:var(--muted)}
/* The first suit sits directly under "Minor Arcana", which has just drawn its own
   rule; a second rule 16px below it is a double line rather than a heading. */
.dvpage .subgroups>.grouphd:first-child{margin-top:6px;padding-top:0;border-top:0}
@media (max-width:560px){
  .dvpage .subgroups>.grouphd,.dvpage .subgroups>.dvgrid{margin-left:0}
  .dvpage.catpage .grouphd.subhd{margin-left:2px}
}

/* ---- the domain page's map: each category, then the sub-groups under it ---- */
.dvpage .catlist{display:flex;flex-direction:column;gap:2px}
.dvpage .catblock .subrow{display:flex;flex-wrap:wrap;gap:8px;margin:0 2px 14px}
/* A sub-group chip carries its own count, so the domain page reads as the outline
   the owner sketched -- category, then its parts, each with a number. */
.dvpage .subchip .c{margin-left:7px;padding-left:7px;border-left:1px solid var(--edge2);
  color:var(--faint);font-size:.74rem;font-variant-numeric:tabular-nums}
.dvpage .subchip:hover .c{border-left-color:var(--gold)}
/* A category with no sub-groups says so, in place of the chip row. Same margins as
   .subrow so the column's rhythm does not change between a block that divides and
   one that does not.

   No box, no border, no chip: it is a sentence, and the moment it acquires a pill
   around it a reader will try to click it as a sub-group. Muted and one step down
   from body copy so it reads as a note about the heading rather than as content
   under it.

   rem, not px, and no height of any kind: this line sits directly under a heading
   the A+ control scales, so a fixed box around it would tear at 150%. Being an
   ordinary flowed paragraph it simply wraps to two lines at 200% and the block
   grows to fit. max-width is in ch so it tracks the text size rather than fighting
   it, and it is a MAXIMUM, so nothing is clipped when the text outgrows it. */
.dvpage .catblock .catflat{margin:0 2px 14px;color:var(--faint);font-size:.82rem;
  line-height:1.5;max-width:60ch}

/* The "Compare all N" offer under the countline. A link, not a button: it goes to a
   real page, so it should look and behave like the destination it is. */
.dvpage .cmpall{margin:0 0 16px}
.dvpage .cmpall[hidden]{display:none}
.dvpage .cmpall a{display:inline-flex;align-items:center;gap:8px;
  background:var(--panel);border:1px solid var(--edge2);border-radius:11px;
  padding:8px 15px;font-size:.86rem;color:var(--ink);text-decoration:none;transition:.14s}
.dvpage .cmpall a:hover{border-color:var(--gold);color:var(--gold)}
.dvpage .cmpall a:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* ============================================================
   Browse / Compare mode, restored from the retired Explorer SPA
   ------------------------------------------------------------
   The SPA's .seg mode switch, its per-group "Compare all N"
   button and its .tray / .selchip / .clearall selection bar,
   re-tokenised. Selection is the one thing on these pages that
   genuinely belongs to the client, so it is the one thing here
   that script owns.

   Every control below is server-rendered [hidden] and revealed
   by script, exactly as the .readbtn row above already is: a
   visitor without JS meets Browse mode and nothing else, with
   every chip still a plain working link. That is also why each
   hidden state is spelled out -- these are flex boxes, and a
   display value in a base rule beats the UA's [hidden] rule.

   Ink on gold is the hardcoded #1a1508 the .btn and .dvrail .cta
   conventions already use: neither palette has a token dark
   enough to sit on --gold in both. Everything else is a token,
   and --gold-soft is never used as text (it is LIGHTER than
   --gold in the light palette, so it washes out on a panel).
   ============================================================ */
.dvpage .modebar{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:0 0 14px}
.dvpage .modebar[hidden]{display:none}
.dvpage .seg{display:inline-flex;background:var(--panel);border:1px solid var(--edge);
  border-radius:10px;padding:3px}
.dvpage .seg button{background:none;border:none;color:var(--muted);font:inherit;font-size:.83rem;
  padding:6px 13px;border-radius:7px;cursor:pointer;transition:.14s}
.dvpage .seg button:hover{color:var(--ink)}
.dvpage .seg button[aria-pressed="true"]{background:var(--gold-bg);color:var(--gold-bg-ink, #1a1508);font-weight:650}
.dvpage .modehint{color:var(--faint);font-size:.82rem}

/* ---- select-all, on every group heading and above an ungrouped list ----
   This is the SPA's compareAllBtn() on .grouphd: the whole point of grouping is
   that "every Christian denomination" is one gesture rather than fifteen. */
.dvpage .gselbar{margin:0 0 10px}
.dvpage .gselbar[hidden]{display:none}
.dvpage .gsel{margin-left:auto;background:var(--panel2);border:1px solid var(--edge2);
  color:var(--gold);border-radius:99px;padding:5px 13px;font:inherit;font-size:.78rem;
  cursor:pointer;white-space:nowrap;transition:.14s}
.dvpage .gsel[hidden]{display:none}
.dvpage .gsel:hover{border-color:var(--gold);background:var(--panel)}
/* on its own bar there is no heading to push it away from, so it leads instead */
.dvpage .gselbar .gsel{margin-left:0}

/* ---- the selection tray ----
   IT STAYS PUT. Owner, 2026-07-30: "the banner covers content as you scroll the page
   and prevents you from seeing what you want to read. It needs to stay in a fixed
   position." It was sticky, pinned under the site header, which is itself sticky --
   so the two together held a band across the top of the window for the whole length
   of a 300-entry category, and that band grows with every press of A+ because both
   the header and the tray's own wrapped rows get taller with the text.

   The reason it was sticky no longer exists. It was written when the tray held the
   selection CHIPS, which you had to be able to see while ticking cards; the
   2026-07-25 redesign moved un-choosing onto the card itself and named every chosen
   entry in its own comparison column, and the chips went with it. What is left is a
   label, a count, Clear All and a permanent link -- none of which has to be on screen
   while you read.

   What you lose while scrolled away is the running count and Clear All. What you keep
   is the part that matters mid-scroll: every chosen card carries its own tick, and a
   group's select-all button reads "Clear these 6" once you have taken the group, so
   the state is legible from the cards themselves. The count and the comparison are
   both at the top of the page, which is where you are going to read the comparison
   anyway.

   No box-shadow now either. The shadow existed to lift the panel off the content
   sliding under it; nothing slides under it any more, and a drop shadow on a panel
   sitting in the flow just reads as a smudge. --headh is no longer referenced here,
   which is one fewer thing depending on that measurement being right. */
.dvpage .tray{position:static;
  background:var(--panel);border:1px dashed var(--edge2);border-radius:13px;
  padding:11px 13px;margin:0 0 16px}
.dvpage .tray[hidden]{display:none}
.dvpage .trayin{display:flex;align-items:center;gap:9px;flex-wrap:wrap;min-height:34px}
.dvpage .tray .lab{color:var(--faint);font-size:.74rem;text-transform:uppercase;letter-spacing:.13em}
.dvpage .tray .hint{color:var(--faint);font-size:.82rem}
/* the live region: empty most of the time, so it must not reserve a line */
.dvpage .traymsg{margin:7px 0 0;color:var(--muted);font-size:.78rem}
.dvpage .traymsg:empty{display:none}

.dvpage .selchip{display:inline-flex;align-items:center;gap:6px;
  background:color-mix(in srgb,var(--gold) 16%,var(--panel2));border:1px solid var(--gold);
  color:var(--ink);border-radius:99px;padding:3px 5px 3px 11px;font-size:.84rem}
.dvpage .selchip .nm{font:inherit;color:inherit;background:none;border:none;padding:0}
.dvpage .selchip button.nm{cursor:pointer;text-decoration:underline dotted}
.dvpage .selchip .rm{background:none;border:none;color:var(--muted);cursor:pointer;
  font-size:1rem;line-height:1;padding:0 4px}
.dvpage .selchip .rm:hover{color:var(--dark-c)}
/* the pivot: with three or more entries the first is what the rest are measured
   against, so it is marked as such rather than merely being first in the row */
.dvpage .selchip.anchored{background:var(--gold-bg);color:var(--gold-bg-ink, #1a1508);font-weight:650}
.dvpage .selchip.anchored .rm{color:#5a4a1a}
.dvpage .selchip .pin{font-size:.8rem}

.dvpage .clearall{margin-left:auto;background:none;border:none;color:var(--faint);
  cursor:pointer;font:inherit;font-size:.78rem;text-decoration:underline}
.dvpage .clearall:hover{color:var(--ink)}
.dvpage .tray .go{display:inline-flex;align-items:center;gap:7px;background:var(--gold-bg);
  color:var(--gold-bg-ink, #1a1508);border:1px solid var(--gold);border-radius:10px;padding:8px 15px;
  font-size:.85rem;font-weight:650;text-decoration:none;transition:.15s}
.dvpage .tray .go:hover{background:var(--gold-soft)}
/* fewer than two chosen: the action is shown but inert, so the tray never changes
   shape as entries go in and out */
.dvpage .tray span.go{background:var(--panel2);color:var(--muted);border-color:var(--edge2);font-weight:400}
/* nothing chosen at all: the Clear All button above is what pushes everything
   after it to the right edge, and with an empty selection there is no Clear All,
   so the button sat a third of the way across a very wide bar with the rest of
   it blank. The first edition sets this margin inline from its script; ruling
   107 forbids inline style here, so js/dvcompare-island.js adds the class and
   the declaration lives with the rest of the tray. */
.dvpage .tray .go.alone{margin-left:auto}
/* Compare More, js/dvcompare-island.js buildTray(). Same secondary look as
   .clearall above (its sibling in the row), because this is a navigational aid
   back to the grid and not the primary action the gold .go pill is. It carries
   no margin-left of its own: js/dvcompare-island.js appends it last, after .go,
   so it always trails whichever element already owns the row's one auto-margin
   push to the right edge, and never opens a second gap of its own. */
.dvpage .cmpmore{background:none;border:none;color:var(--faint);cursor:pointer;
  font:inherit;font-size:.78rem;text-decoration:underline}
.dvpage .cmpmore:hover{color:var(--ink)}

/* ---- a chip in compare mode ----
   The same server-rendered anchor, wearing role="button" and aria-pressed. It stays
   an anchor so that turning compare mode off (or never turning it on) leaves a
   working link behind. */
.dvpage .cmpmode .chip{cursor:pointer}
.dvpage .chip[aria-pressed="true"]{background:color-mix(in srgb,var(--gold) 16%,var(--panel2));
  border-color:var(--gold)}
.dvpage .chip[aria-pressed="true"]::after{content:"\2713";color:var(--gold);font-size:.8rem;flex:none}

/* ---- card grid (the SPA's .dirgrid/.dircard). .dvgrid/.dvcard are the names a new
   page should use; .catgrid/.catcard are the ones Category.aspx already emits. ---- */
/* THE TRACK FLOOR IS rem, AND IT WAS 190px. Lane textsize-200, 2026-08-05, raising
   the reader's ceiling from 150 to 200 per cent. 12.6667rem is 190.0px against the
   html{font-size:93.75%} base, so nothing moves at the default; this is a scaling
   fix and not a resize.
   A px floor is a fixed box around scaling text, and on an auto-fill grid it is the
   worst kind, because the floor is what decides how many columns fit: the card stayed
   190px while its title went from 15px to 22.5px, so the reader who most needs the
   setting got the fewest characters per line and the break-word rule below fired on
   names that break at no size the control cannot reach. In rem the card grows with
   the reader and the column count drops instead of the legibility.
   min() rather than the bare length, on this and every track floor changed in the
   same pass: a floor is a MINIMUM and a grid track will not shrink below it, so
   12.6667rem at 200 per cent is 285px and would overflow a 320px phone column once
   padding is taken off. min(...,100%) lets it fall back to the container, and
   html{overflow-x:hidden} means the overflow it prevents would have been clipped
   silently rather than shown. */
.dvpage .dvgrid,.dvpage .catgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(min(12.6667rem,100%),1fr));gap:12px}
.dvpage .dvcard,.dvpage .catcard{display:flex;flex-direction:column;gap:3px;
  background:linear-gradient(180deg,var(--panel2),var(--panel));border:1px solid var(--edge);
  border-radius:13px;padding:14px 15px;text-decoration:none;color:var(--ink);transition:.14s}
.dvpage .dvcard:hover,.dvpage .catcard:hover{border-color:var(--gold);transform:translateY(-2px);box-shadow:var(--shadow)}
/* A LONG SINGLE WORD BREAKS RATHER THAN LEAVING THE CARD. Found:
   "Denominations" ran out of its tile on a phone. The track is
   minmax(190px,1fr), so a word wider than the column cannot widen it and simply
   painted over the border and off the screen, where html{overflow-x:hidden} cuts
   it off with no scrollbar. "Scriptures & Texts" was fine only because it had a
   space to break at.

   break-word rather than anywhere: it breaks ONLY when the word genuinely cannot
   fit, so ordinary two word titles still wrap at the space and do not get split
   mid-word for no reason. */
.dvpage .dvcard .n,.dvpage .catcard .n{font-family:var(--serif);font-size:1.06rem;font-weight:600;line-height:1.25;
  overflow-wrap:break-word;min-width:0}
.dvpage .dvcard .m,.dvpage .catcard .m{color:var(--faint);font-size:.74rem;text-transform:uppercase;
  letter-spacing:.06em;font-variant-numeric:tabular-nums}
.dvpage .dvcard.dk,.dvpage .catcard.dk{border-color:color-mix(in srgb,var(--dark-c) 45%,var(--edge))}
.dvpage .dvcard.dk .m,.dvpage .catcard.dk .m{color:var(--dark-c)}

/* ---- entry card (the SPA's .card) ----
   .dvcard above is the SPA's .dircard: a directory TILE, a name and a count. An
   ENTRY says more -- a thumbnail, its classification, how many things it connects to,
   and in compare mode whether it is chosen. So .entcard extends .dvcard instead of
   starting a third card vocabulary: same surface, border, hover and .n heading, plus
   only the parts an entry actually adds. The track widens to the SPA's 220px, because
   a card carrying a floated thumbnail and a row of pills needs room the 190px
   directory tile does not. */
/* 14.6667rem is 220.0px at the default. See the .dvgrid floor above for why it is rem
   and why it is wrapped in min(). */
.dvpage .entgrid{grid-template-columns:repeat(auto-fill,minmax(min(14.6667rem,100%),1fr))}
/* A listing's own slice notice, Types.aspx.vb's #entryList > .more -- same
   job, same treatment as .cmpresult .more and .peersec .more above: says
   something is not on THIS page, so it stays in rem at .9rem rather than the
   .78rem faint micro-text .mut uses, and the A-/A+ control keeps reach of it.
   Only ever rendered when the listing needed more than one page (real
   pagination) or, on a future surface this page's
   pager does not reach, when it was truncated instead. */
.dvpage #entryList>.more{color:var(--muted);font-size:.9rem;margin:12px 0 0;max-width:62ch}
/* The pager itself, Types.aspx.vb BuildPager. Components in EM per the
   accessibility rule (rem is for body copy), so the whole control scales with
   the A-/A+ text control rather than pinning a px box around growing text.
   min-height/min-width give every control a real touch target (2.75em, ~44px
   at the base size) even though its printed content is one or two glyphs; a
   number that small is exactly the tiny hover-only target the reduced-vision
   accessibility rule exists to prevent. flex-wrap so a six-plus-link row
   folds onto a second line rather than overflowing sideways at 150-200% text,
   where html{overflow-x:hidden} elsewhere on the site would clip it silently
   with no scrollbar. Colors match the .cx .pager above: same gold-soft link,
   same site link language, a different scope because that one requires a
   .cx ancestor this page does not carry. */
.dvpage #entryList .pager{display:flex;flex-wrap:wrap;align-items:center;gap:.5em;
  margin:16px 0 0;max-width:100%}
.dvpage #entryList .pager a.pg,.dvpage #entryList .pager span.pg{
  display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;
  min-width:2.75em;min-height:2.75em;padding:.3em .7em;border:1px solid var(--edge);
  border-radius:8px;font-size:.92rem;line-height:1.2}
.dvpage #entryList .pager a.pg{color:var(--gold-soft);text-decoration:none}
.dvpage #entryList .pager a.pg:hover,.dvpage #entryList .pager a.pg:focus-visible{
  color:var(--gold);border-color:var(--gold-soft)}
.dvpage #entryList .pager span.pg.on{color:var(--ink);font-weight:700;border-color:var(--gold)}
.dvpage #entryList .pager .pgdots{color:var(--faint);padding:0 .2em}
.dvpage #entryList .pager a.pgprev,.dvpage #entryList .pager a.pgnext{font-size:.88rem;padding:.3em 1em}
/* Block, not the flex column .dvcard uses: the thumbnail floats out of the text flow
   the way the SPA's did, and float does nothing to a flex item. */
.dvpage .entcard{display:block;position:relative}
.dvpage .entcard .n{margin:0 0 3px}
.dvpage .entcard .meta{color:var(--muted);font-size:.78rem}
.dvpage .entcard .tags{display:flex;gap:5px;flex-wrap:wrap;margin-top:9px}
.dvpage .entcard .pill{background:var(--panel);border-color:var(--edge);color:var(--faint);
  font-variant-numeric:tabular-nums}
.dvpage .entcard .pill.dark{color:var(--dark-c);border-color:var(--dark-c);background:var(--dark-bg)}
.dvpage .entthumb{float:right;width:46px;height:46px;margin:0 0 6px 10px;
  border:1px solid var(--edge);border-radius:8px;background:var(--panel);padding:4px;
  display:grid;place-items:center;overflow:hidden}
.dvpage .entthumb svg,.dvpage .entthumb img{max-width:100%;max-height:100%;height:auto;display:block}
/* A type emblem stands in for an image the entity does not have, so it is drawn back
   to read as a stand-in -- the same treatment .entart.isEmblem already gets. */
.dvpage .entthumb.isEmblem{color:var(--faint);opacity:.55}

/* ---- an entry card in compare mode ----
   Same contract as the chip above: the anchor stays an anchor and only puts on
   role="button" + aria-pressed, so with compare off (or with no script at all) it is
   still a working link. The mark is server-rendered empty on every card and revealed
   only in compare mode, and the tick itself is CSS -- so the selection script never
   has to reach inside a card and rewrite it. */
.dvpage .entcard .selmark{display:none}
.dvpage .cmpmode .entcard{cursor:pointer}
.dvpage .cmpmode .entcard .selmark{display:grid;place-items:center;position:absolute;
  top:9px;right:9px;width:19px;height:19px;border-radius:50%;border:1px solid var(--edge2);
  background:var(--panel);color:var(--faint);font-size:.66rem}
/* The mark and the card's glyph share the top-right corner; in compare mode the
   glyph steps LEFT, clear of the 19px mark at right:9px (owner QA 2026-07-25). */
.dvpage .cmpmode .entcard .entthumb{margin-right:26px}
.dvpage .entcard[aria-pressed="true"]{border-color:var(--gold);box-shadow:inset 0 0 0 1px var(--gold)}
.dvpage .cmpmode .entcard[aria-pressed="true"] .selmark{background:var(--gold-bg);
  border-color:var(--gold);color:var(--gold-bg-ink, #1a1508)}
.dvpage .cmpmode .entcard[aria-pressed="true"] .selmark::after{content:"\2713"}

/* ============================================================
   The comparison, IN PLACE
   ------------------------------------------------------------
   The SPA's .cfilter / .result / .matchcard / .ring / .tok, and
   Compare.aspx's verdict and pairing rows, drawn on the page you
   are picking from instead of on a page you had to travel to.
   Everything is a theme token, so one copy reads correctly in
   both palettes -- and --gold-soft is never used as text, since
   it is LIGHTER than --gold in the light palette and would wash
   out on a panel.

   .result is two columns INSIDE the comparison block. That is
   not the two-column page shell .dvpage deliberately does not
   have: the page is still one flowing column, and this is one
   component within it that happens to put a dial beside a list.
   ============================================================ */
.dvpage .cfilter{margin:0 0 14px}
.dvpage .cfilter[hidden]{display:none}
.dvpage .cfilter input{width:100%;max-width:360px;background:var(--panel);border:1px solid var(--edge);
  color:var(--ink);border-radius:9px;padding:9px 13px;font:inherit;font-size:.9rem;outline:none}
.dvpage .cfilter input:focus{border-color:var(--gold)}

.dvpage .cmpresult{margin:0 0 18px}
.dvpage .cmpresult[hidden]{display:none}
.dvpage .result{display:grid;grid-template-columns:250px minmax(0,1fr);gap:20px;align-items:start}
@media(max-width:820px){.dvpage .result{grid-template-columns:1fr}}

/* the verdict: the match ring and what the number means in words */
.dvpage .matchcard{background:linear-gradient(180deg,var(--panel2),var(--panel));
  border:1px solid var(--edge);border-radius:16px;padding:18px;text-align:center}
.dvpage .matchcard .ring{width:148px;height:148px;display:block;margin:0 auto 6px}
.dvpage .matchcard .plab{font-family:var(--serif);font-size:1.02rem;font-weight:600;color:var(--gold)}
.dvpage .matchcard .vdesc{color:var(--muted);font-size:.82rem;margin-top:8px;text-align:left}

/* the pivot's pairings. A BUTTON, not a link: opening one focuses that two-way
   comparison in place, which is the whole point -- /compare has the anchor version. */
.dvpage .pairs{margin-top:14px;display:flex;flex-direction:column;gap:6px;text-align:left}
.dvpage .pairhint{color:var(--faint);font-size:.76rem;margin:0 0 4px}
.dvpage .pairhint b{color:var(--ink)}
.dvpage .pairrow{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 10px;width:100%;
  background:var(--panel);border:1px solid var(--edge);border-radius:9px;padding:7px 10px;
  font:inherit;color:var(--ink);text-align:left;cursor:pointer;transition:.13s}
.dvpage .pairrow:hover{border-color:var(--gold)}
.dvpage .pairrow.active{border-color:var(--shared);background:var(--shared-bg)}
.dvpage .pairrow .pn{overflow-wrap:anywhere;font-size:.88rem}
.dvpage .pairrow .tnum{font-variant-numeric:tabular-nums;color:var(--gold);font-weight:600;font-size:.85rem}
.dvpage .pairrow .bar{grid-column:1/-1;height:5px;border-radius:99px;background:var(--ground);overflow:hidden}
.dvpage .pairrow .bar i{display:block;height:100%;background:linear-gradient(90deg,var(--shared),var(--gold))}
.dvpage .pairback{background:var(--panel2);border:1px solid var(--edge2);color:var(--ink);
  border-radius:8px;padding:5px 11px;font:inherit;font-size:.78rem;cursor:pointer;
  align-self:flex-start;margin-bottom:4px}
.dvpage .pairback:hover{border-color:var(--gold);color:var(--gold)}

/* similarities and differences, banded by the system each correspondence belongs to */
.dvpage .sumrow{display:grid;grid-template-columns:1fr 1fr;gap:14px}
@media(max-width:640px){.dvpage .sumrow{grid-template-columns:1fr}}
/* ---- the column comparer (redesign 2026-07-25) ----
   One column per selected entry; tabs are comparison targets; the share bar
   belongs to the active tab. Replaces the ring/matchcard + Similarities/
   Differences pair of cards (whose rules below stay for the standalone
   Compare page until it adopts this component). */
/* 13rem is 195.0px at the default. rem floor: see .dvgrid. This is the in-page column
   comparer, so a floor that ignores the text size costs the reader the comparison
   itself, not just a card title. */
.dvpage .cmpcols{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(13rem,100%),1fr));gap:14px;align-items:start}
/* Phones: one column per row with a clear band between the stacked cards --
   auto-fit at 195px could squeeze two crushed columns onto a 390px screen. */
@media(max-width:560px){.dvpage .cmpcols{grid-template-columns:1fr;gap:16px}}
/* A two-entry comparison is ONE card (the pair is the whole comparison). */
.dvpage .cmpcols.one{grid-template-columns:1fr}
.dvpage .cmpcol{background:var(--panel);border:1px solid var(--edge);border-radius:12px;padding:13px 15px;min-width:0}
.dvpage .cmpcol>h3{font-family:var(--serif);font-size:1.05rem;color:var(--ink);margin:0 0 9px;font-weight:600}
.dvpage .ctabs{display:flex;gap:6px;border-bottom:1px solid var(--edge);margin:0 0 10px;flex-wrap:wrap}
.dvpage .ctab{background:none;border:none;border-bottom:2px solid transparent;color:var(--muted);
  font-size:.78rem;padding:6px 8px;cursor:pointer;font-family:var(--sans)}
.dvpage .ctab.on{color:var(--gold);border-bottom-color:var(--gold);font-weight:600}
.dvpage .ctab:hover{color:var(--ink)}
.dvpage .cbar{margin:0 0 10px}
.dvpage .cbar .bl{display:flex;justify-content:space-between;font-size:.74rem;color:var(--muted);margin-bottom:3px}
.dvpage .cbar .bl b{color:var(--ink);font-weight:600;font-variant-numeric:tabular-nums}
.dvpage .cbar .bt{height:6px;border-radius:99px;background:var(--panel2);border:1px solid var(--edge);overflow:hidden}
.dvpage .cbar .bf{height:100%;border-radius:99px;background:linear-gradient(90deg,var(--gold),var(--gold-soft))}
/* Flex rows so the read-aloud button js/panel-speak.js now appends to each sits hard
   right, exactly as .tf-grp-h and .factgrp-h do -- .dspeak carries margin-left:auto and
   without a flex row that does nothing and the button lands inline after the text.
   min-width:0 on the shrinkable member per the house rule, and gap in em so the space
   beside the button tracks the A-/A+ control. w18-a11y-audit, 2026-07-31. */
.dvpage .csub{font-size:.68rem;text-transform:uppercase;letter-spacing:.08em;color:var(--gold);margin:10px 0 6px;
  display:flex;align-items:center;gap:.6em;min-width:0}
/* The system heading inside a pane carries its COUNT (owner call 2026-07-25),
   which is what made the standalone page's Correspondences-by-system section
   redundant. */
.dvpage .cmpcols .ahd .gc{margin-left:7px;color:var(--faint);font-size:.7rem;border:1px solid var(--edge);
  border-radius:99px;padding:1px 8px;font-variant-numeric:tabular-nums;letter-spacing:0}
.dvpage .sumcard{background:var(--panel);border:1px solid var(--edge);border-radius:12px;padding:13px 15px;min-width:0}
.dvpage .sumcard h3{font-family:var(--serif);font-size:1rem;color:var(--gold);margin:0 0 9px;font-weight:600}
/* Similarities heading matches Differences (owner QA 2026-07-25: no green set). */
/* em rather than the 10px this was, so the space between one pill set and the next
   grows with the A-/A+ control like the rule below it. .7em is 10.5px at the base
   root size, so nothing moved at 100 per cent; at 200 per cent it is now 21px where
   it used to stay at 10. */
.dvpage .agroup{margin:0 0 .7em}

/* ---- A RULE UNDER EACH PILL SET --------------------------------------------
   A divider under each pill set so a reader comparing can see clearly
   where a new section begins. Several areas run down one column with nothing but a
   small uppercase heading between them, and at 150 per cent text a reader scanning
   for where Countries ends and Doctrines begins has only that heading to go on.

   IT IS A BORDER ON THE GROUP BOX AND NOT AN ELEMENT, and both halves of that are
   deliberate:

     - Nothing is added to the accessibility tree, so the read-aloud is unchanged.
       A decorative element here would have needed the same nospeak treatment the
       None Recorded rows carry, and the safest decoration is the one that was never
       in the document.
     - The rule sits at the BOTTOM EDGE OF THE VERY BOX js/dvshowall.js sizes. That
       makes it an instrument as well as a divider: two columns whose rules do not
       line up are two columns whose rows do not line up, readable across a whole
       comparison at a glance instead of one measured row at a time. Anything that
       stretched or absorbed the difference would hide the thing worth seeing.
       This is deliberate even though it makes misalignment more visible: that
       visibility is what helps find the rows that do not line up.

   SIZED IN em, so it holds at 150 per cent and at the 200 per cent
   ceiling. A 1px hairline at 200 per cent reads as a scratch on the screen rather
   than as a line somebody drew.

   IT IS NOT CARRYING MEANING ON ITS OWN. Every group already names its area in words
   in .ahd and counts it in .gc, so a reader who cannot resolve the rule, or who is
   listening, loses nothing (WCAG 1.4.1). It is a second, redundant signal.

   THE LAST GROUP IN A PANE KEEPS THE SPACE AND LOSES THE INK, rather than losing the
   border outright. A rule with nothing under it reads as a mistake, but removing the
   border would make that one box shorter than its opposite number by the width of a
   border, and whether a group is last differs BETWEEN COLUMNS: one column can end
   with a truncation notice where another has none. Making it transparent keeps every
   group box exactly the same shape, so the alignment pass and the eye both still
   agree. */
.dvpage .cpane .agroup{padding-bottom:.5em;border-bottom:.14em solid var(--edge2)}
.dvpage .cpane .agroup:last-child{border-bottom-color:transparent}
/* flex, not block, so the per-group read-aloud button floats right. See the note on
   .csub above. */
/* flex-wrap added, and it is BELT AND BRACES rather than a
   fix, which is worth saying plainly because a comment claiming a fix that never fired
   is worse than no comment. The .gc badge now reads "12 of 35" rather than "12" under
   the per-type-group cap, roughly three times the width, so this row was checked with
   headless Chromium at 1280px and 390px against root font sizes of 15px, 22.5px (the
   A+ control's ceiling) and 30px (200%): zero clipped .ahd at every combination. The
   counterfactual was run too, forcing flex-wrap:nowrap back on at 390px and 200%, and
   it ALSO clipped nothing, because the heading text is a bare text node which the flex
   algorithm wraps in an anonymous item that shrinks and wraps internally. So this line
   changed no measured outcome today. It is kept because the element fill is about to
   put much longer type labels through here, and wrapping is the behaviour we want when
   one of them finally does not fit. */
.dvpage .ahd{display:flex;flex-wrap:wrap;align-items:center;gap:.5em;min-width:0;
  font-size:.63rem;text-transform:uppercase;letter-spacing:.13em;
  color:var(--faint);margin:0 0 5px}
.dvpage .tok{display:inline-block;font-size:.76rem;padding:2px 8px;border-radius:6px;
  margin:2px 3px 2px 0;background:var(--panel2);border:1px solid var(--edge);color:var(--muted);
  text-decoration:none;transition:.12s}
.dvpage a.tok:hover{border-color:var(--gold);color:var(--gold)}
/* Shared tokens read exactly like difference tokens (the
   green set clashed); the .sh class stays for the "N of M" count badge only. */
.dvpage .tok .cnt{margin-left:6px;font-size:.66rem;opacity:.75;font-variant-numeric:tabular-nums}
/* Shown only where one area holds two targets of the same name -- the three Gabriels
   are distinct rows (planetary, elemental, sephirotic), not duplicates. */
.dvpage .tok .qual{margin-left:5px;font-size:.66rem;opacity:.72;font-style:italic}
.dvpage .dgroup{margin:0 0 11px}
.dvpage .dgroup>b{font-size:.85rem;color:var(--ink)}
.dvpage .cmpresult .mut{color:var(--faint);font-size:.78rem;margin:4px 0 0}
/* .more says CONTENT IS MISSING FROM THIS PAGE, which is not metadata about the
   layout, so it is out of the .78rem faint micro-text it used to share with .mut.
   .mut is an empty-state sentence and stays where it
   was. Measured: 6,028 of 13,695 denomination pairs already truncate at
   least one section, and after the approved element fill about 131 of 166
   denominations carry more than the cap on their own, so this line is about to become
   one of the most-read sentences on the page. rem, so the A-/A+ control still scales
   it; no fixed box around it. */
.dvpage .cmpresult .more{color:var(--muted);font-size:.9rem;margin:8px 0 0;max-width:62ch}

/* ---- Show All, and the aligned group rows -----------------------------------
   Shared by BOTH comparison surfaces, which is why it is here and not in
   compare.css: the standalone page loads compare.css and the listing island does
   not, and the two must not lay one comparison out two different ways. The
   behaviour is js/dvshowall.js and the markup contract is written out in its
   header.

   THE HIDDEN REMAINDER IS display:contents WHEN IT IS OPEN. It sits inside its own
   group's .chips row so that a group cut in half opens back into one group rather
   than into a second copy of itself lower down, and .chips is a flex row: a plain
   wrapper would make several hundred chips ONE flex item and they would stop
   wrapping. display:contents takes the wrapper out of the layout and leaves the
   chips as the flex children they were.

   The [hidden] rule beneath it is not belt and braces. A declared display beats the
   UA stylesheet's own [hidden] rule, so without restating it here the remainder
   would be open on every page with no way to close it. */
.dvpage .cmore{display:contents}
.dvpage .cmore[hidden]{display:none}

/* The control. Sized in em against the notice beside it so the A-/A+ control and
   the browser's own text size both scale it, and given a real minimum height
   rather than a 12 pixel strip of text: the reader this was built for has central
   field loss and a small target is his weakest interaction. */
.dvpage .moreblk{margin:8px 0 0}
.dvpage .moreblk .more{margin:0 0 6px}
.dvpage button.showall{display:inline-flex;align-items:center;justify-content:center;
  min-height:2.2em;padding:.35em .9em;background:var(--panel2);color:var(--ink);
  border:1px solid var(--edge2);border-radius:99px;font:inherit;font-size:.82rem;
  font-weight:600;cursor:pointer;transition:.13s}
.dvpage button.showall:hover{border-color:var(--gold);color:var(--gold)}
.dvpage button.showall:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* A group this column records nothing in. It holds the row open opposite the
   column that does record something, which is the whole of the owner's second
   request, and it says None Recorded rather than None: the atlas has not gone and
   looked and found nothing, it simply holds nothing here, and the difference is
   the one this project is most careful about everywhere else. Muted and italic,
   the same treatment the facts grid already gives its own "not recorded" cell, so
   the two absences read alike. */
.dvpage .agroup.none .ahd{opacity:.75}
/* THE SECOND HALF OF THE SAME SHORTFALL, and it is in the HEADING rather than in the
   body of the row. A group that records something carries a .gc count badge beside its
   area name, and that badge is a bordered pill taller than the bare uppercase text: at
   the owner's 150 per cent the heading with a badge stood 15.53px against 10.98px for
   the None Recorded heading, which is 4.55px of the original 14.6px gap and every bit
   of what was left once the sentence below had been given a pill's box.

   A badge is not drawn here on purpose. "0" is a claim, and the whole point of this row
   is that the column holds nothing here rather than that the atlas counted nothing. So
   the SPACE the badge would take is reserved and the badge itself is not.

   A pseudo-element rather than markup, for the same reason the divider above is a border
   rather than a box: nothing is added to the document, so neither emitter changes, the
   read-aloud cannot reach it, and the two surfaces cannot drift apart. The content is a
   zero width space, which is what gives the box a line box to be as tall as; it is
   inherited line-height (unitless 1.55) times .gc's own font size, plus .gc's own
   vertical padding and border, so it is the badge's height BY CONSTRUCTION at every text
   size rather than a number measured once at one size. Zero horizontal padding and no
   margin, so it reserves height and no width. It must move with .gc. */
.dvpage .agroup.none .ahd::after{content:"\200B";font-size:.7rem;
  padding:1px 0;border:1px solid transparent}
/* AND IT IS SHAPED LIKE ONE TOKEN, WHICH IS THE WHOLE POINT OF IT (owner, 2026-08-05:
   "None Recorded is throwing off the alignment because the height is smaller than the
   pill height").

   Measured on /religions the same day, with js/dvshowall.js's own min-heights cleared
   so the natural boxes could be seen: a None Recorded group stood 52.3px against 66.9px
   for the group of ONE pill opposite it. The row that exists to hold two columns level
   was 14.6px short of the thing it was holding level, so every such row depended on the
   JavaScript pass to rescue it, and anywhere that pass stops early, or has not run yet,
   the columns come apart at exactly the rows added to keep them together.

   THE FLOOR IS BUILT BY MIRRORING .tok's BOX RATHER THAN BY A NUMBER. Same font-size,
   same padding, same margins, same one pixel border made transparent, so this sentence
   occupies exactly the height one pill occupies, at every text size, without anybody
   having to re-derive 14.6px when a pill changes. IT MUST MOVE WITH .tok: if that rule
   above changes its padding, its margins or its font-size, change this one in the same
   edit or the rows part again.

   The pass is not replaced by this and still matters: a group holding several pills, or
   one pill whose name wraps, is taller than one line and only measurement can know it.
   What changes is the direction of the work. The pass now only ever has to make a row
   TALLER, never rescue one that started short, and with script off or before the first
   pass runs the columns already line up. Nothing here is spoken: the row carries
   nospeak, and this rule adds no text and no element.

   AND IT IS TWO RULES BECAUSE THE TWO SURFACES STILL DRAW TWO DIFFERENT PILLS, EVEN
   AFTER RECONCILING THEM. Found while proving this on the standalone page: the listing
   island's pill is .tok and the standalone Compare page's is .chip, sitting in a
   .chips flex row, and measured side by side they were 18.58px against 30.18px.
   They were reconciled on the smaller pill, to match the listing, and
   compare.css's .cmppage .cpane .chip rule now carries the token's own font-size and a
   padding built to sit within a fraction of a pixel of it. THEY ARE STILL NOT ONE
   SHAPE. A chip carrying a stance or status badge is taller than a bare token by the
   badge's own box stacking inside the chip's padding, and even a bare chip sits a
   quarter pixel over .tok because .tok itself falls under the WCAG 24px target floor
   and this chip was built not to. So each surface's None row still mirrors ITS OWN
   pill, matched close rather than made identical, which is what keeps the two
   comparisons behaving alike even where they are drawn a hair apart. If a future
   change makes the two pills byte-for-byte the same box, these two rules collapse
   into one with them; the reconciliation above brought them close and stopped there. */
.dvpage .agroup.none .na{color:var(--faint);font-style:italic}
/* the island's pill: .tok, inline-block, its own vertical margins */
.dvpage .cmpresult .agroup.none .na{margin:2px 3px 2px 0;
  display:inline-block;font-size:.76rem;padding:2px 8px;border:1px solid transparent}
/* the standalone page's pill: .chip, inline-flex, no margins, spaced by .chips' gap.
   NOTE THE COMPOUND SELECTOR. cmppage and dvpage sit on the SAME article element, so
   ".dvpage .cmppage" is a descendant selector that can never match and the rule is
   silently dead; the None rows stay short and nothing says why. Written wrong here
   first, caught by measuring the standalone page rather than by reading. compare.css
   writes ".cmppage" unprefixed for the same reason.
   MOVED WITH THE CHIP UNDER THE SAME RECONCILIATION. font-size, padding and border-radius mirror
   compare.css's .cmppage .cpane .chip exactly, in the same em-against-its-own-font-size
   terms, so this placeholder tracks the chip's actual (badge-free) box rather than a
   number re-derived by hand. IT MUST MOVE WITH THAT RULE: if the chip's font-size,
   padding or radius change again, change this one in the same edit or the rows part
   again, precisely the discipline the comment above already asks for with .tok. */
.dvpage.cmppage .agroup.none .na{margin:0;
  display:inline-flex;align-items:center;font-size:.76rem;padding:.2em .7em;
  border:1px solid transparent;border-radius:6px}

/* ---- an entity page runs two compare scopes at once ----
   Its peers (this entity against others of its kind) and its children (a religion's
   denominations against each other), so this page can show TWO trays at once. That
   used to need an override here, because the peer tray was sticky and a second sticky
   bar at the same offset would have sat on top of the first. Neither is sticky as of
   2026-07-30, so the override is gone rather than left behind saying nothing: a rule
   that no longer does anything is a rule the next reader has to disprove. */
.dvpage .peersec>.mut{color:var(--muted);font-size:.86rem;margin:0 0 12px;max-width:62ch}
/* Same sentence, same reason, same treatment as .cmpresult .more above. */
.dvpage .peersec .more{color:var(--muted);font-size:.9rem;margin:10px 0 0;max-width:62ch}
.dvpage .peersec .cta{display:inline-flex;align-items:center;gap:7px;margin-top:12px;
  background:var(--panel2);border:1px solid var(--edge2);border-radius:10px;padding:7px 14px;
  color:var(--ink);font-size:.85rem;text-decoration:none;transition:.14s}
.dvpage .peersec .cta:hover{border-color:var(--gold);color:var(--gold)}

/* Anything genuinely wide scrolls inside its own box. html is overflow-x:hidden,
   so a wide child would otherwise be clipped away rather than reachable. */
.dvpage .scrollx{max-width:100%;overflow-x:auto}

/* ============================================================
   Detail-page sections restored from the retired Explorer SPA
   ------------------------------------------------------------
   Image gallery, compare affordance, sources, music, challenges
   and comments. These were lost when the SPA was replaced by
   server-rendered pages; the rules below are its own CSS,
   re-tokenised. Two deliberate departures from the original:

   - the SPA used var(--gold-soft) as link/emphasis text on
     panels. In the LIGHT palette --gold-soft is LIGHTER than
     --gold, so it washed out; those are var(--gold) here.
   - the "accepted" challenge badge was a hardcoded green. It
     now borrows --shared (the palette's own positive accent),
     which leaves all four statuses distinguishable using only
     theme tokens.
   ============================================================ */

/* ---- image gallery ----
   The hero art plus its .entgal strip, merged into one row now that the
   identity header is a card of its own and has no art column.

   TWO SHAPES SHARE .entgal, and they are told apart by data, never by type
   or count here (content in code is a defect). AN ORDINARY
   gallery is .entart (the hero) plus zero or more .entgthumb (smaller
   supporting pictures): the hero sits on its own row, the thumbnails wrap
   beneath it at up to two per row, since the
   smaller ones go under the large image, only two per row. A
   .entgal.isGlyphSet is the OTHER shape, dbo.spGetEntityImages'
   IsGlyphVariant=1: several images that are VARIANTS of the same subject,
   drawn EQUAL size with no hero at all, such as four tarot
   deck drawings of one card, none of them the "real" one -- see
   Entity.aspx.vb BuildGallery/BuildVariantGallery. All sizing here is rem
   and em, never a fixed pixel box, so the A-/A+ control still owns it
   (checked at 150% and 200% text). */
.dvpage .entgal{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:.65rem;margin:0 0 1.2rem}
.dvpage .entgal .entart{grid-column:1/-1;justify-self:start}
.dvpage .entgal .entgthumb{justify-self:start}
/* Opening block: the art floats right, the origin line and description wrap around it.
   flow-root rather than a clearfix so the float is contained even when the picture is
   taller than the prose beside it. The width cap keeps a multi-image gallery from
   taking so much of the measure that the wrapped text is left in a narrow gutter. */
.dvpage .entlede{display:flow-root;max-width:var(--measure)}
/* Tighter around the description: the header card sits
   closer above the lede, and the first section title after it drops from the
   standard 26px section rhythm to a near gap. Scoped to entity pages only. */
.entity .dvrail{margin-bottom:12px}
.entity .entlede+details.tsec>summary.sechd,
.entity .entlede+details.sec>summary.sechd,
.entity .entlede+section.learnmore>h2.sechd,
.entity .entlede+h2.sechd{margin-top:12px}
.dvpage .entlede .entgal{float:right;max-width:14rem;margin:.15rem 0 .95rem 1.3rem}
/* A glyph SET is not a small picture beside the prose, it is several equal
   drawings that need their own captioned width, so it drops the float and
   becomes its own full-width row above the lede paragraph instead. */
.dvpage .entlede .entgal.isGlyphSet{float:none;max-width:none;width:100%;margin:0 0 1.1rem}
.dvpage .entlede .lead,.dvpage .entlede .factline{max-width:none}
.dvpage .entart{flex:0 0 auto;width:8.8rem;height:8.8rem;border:1px solid var(--edge);border-radius:.95rem;
  background:var(--panel);padding:.65rem;display:grid;place-items:center;overflow:hidden}
.dvpage .entgthumb{width:4rem;height:4rem;border:1px solid var(--edge);border-radius:.6rem;
  background:var(--panel);padding:.4rem;display:grid;place-items:center;overflow:hidden}
.dvpage .entgthumb.pri{border-color:var(--gold)}
.dvpage .entart svg,.dvpage .entart img,
.dvpage .entgthumb svg,.dvpage .entgthumb img{max-width:100%;max-height:100%;height:auto;display:block}
/* A per-type emblem is a stand-in, not this entity's own art, so it reads lighter
   and dashed rather than passing itself off as a real image. */
.dvpage .entart.isEmblem{color:var(--faint);opacity:.55;border-style:dashed}

/* ---- the glyph set (four equal, captioned, no primary) ----
   display:flex rather than the base .entgal grid: a set has no hero to
   anchor a two-column grid on, only equal tiles that should wrap freely
   at their own width. Each .entgset is a figure: the drawing (decorative,
   aria-hidden) plus a REAL, VISIBLE .entgcap naming its deck. The artwork
   carries no aria-label of its own here and the caption is the entire
   accessible text for the tile, deliberately: giving the SVG its usual
   AltText aria-label as well would have the reader announce "Rider-Waite-
   Smith Ace of Wands, simplified symbolic line-art" and then immediately
   the caption "Rider-Waite-Smith" again, two overlapping sentences back to
   back for one picture. The AltText sentence adds nothing here beyond the
   deck name (already the caption) and the entity's own name (already the
   page heading), so it is suppressed for this shape only; the ordinary
   hero-plus-thumbnail gallery below is unchanged and keeps its aria-label. */
.dvpage .entgal.isGlyphSet{display:flex;flex-wrap:wrap;gap:1rem;align-items:flex-start}
.dvpage .entgset{display:flex;flex-direction:column;align-items:center;flex:0 0 auto;width:8.8rem;gap:.4rem;margin:0}
.dvpage .entgsetart{width:8.8rem;height:8.8rem;border:1px solid var(--edge);border-radius:.95rem;
  background:var(--panel);padding:.65rem;display:grid;place-items:center;overflow:hidden}
.dvpage .entgsetart svg,.dvpage .entgsetart img{max-width:100%;max-height:100%;height:auto;display:block}
.dvpage .entgcap{font-size:.85rem;line-height:1.3;text-align:center;color:var(--ink);max-width:100%;overflow-wrap:anywhere}

/* ---- sources (entity-level citations) ---- */
.dvpage .srclist{display:flex;flex-direction:column;gap:8px}
.dvpage .src{background:var(--panel2);border:1px solid var(--edge);border-radius:10px;padding:9px 12px;
  font-size:.86rem;display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px}
.dvpage .src-t{color:var(--ink);font-weight:600;text-decoration:none;overflow-wrap:anywhere}
.dvpage a.src-t:hover{color:var(--gold);text-decoration:underline}
/* The citation number declared at the front of a cited entry:
   real text, matching the exact digit the fact-cite superscript
   already carries, quiet rather than a second badge competing with the tier pill. */
.dvpage .src-num{color:var(--faint);font-weight:600}
.dvpage .src-m{color:var(--muted);font-size:.82rem}
.dvpage .src-loc{color:var(--faint);font-size:.78rem}
/* The tier pill is pushed to the end of the row, so a long title wraps against the
   citation rather than against the badge. */
/* The badge carries its meaning in title/aria-label, so it must LOOK explicable: the
   help cursor is the only visual hint that hovering will say something. */
.dvpage .src-tier{cursor:help;margin-left:auto;font-size:.66rem;padding:2px 8px;border-radius:99px;
  border:1px solid var(--edge2);color:var(--faint);white-space:nowrap;text-transform:uppercase;letter-spacing:.06em}
.dvpage .src-tier.tier1{color:var(--shared);border-color:var(--shared);background:var(--shared-bg)}
.dvpage .src-tier.tier2{color:var(--gold);border-color:color-mix(in srgb,var(--gold) 55%,var(--edge))}
.dvpage .src-tier.tier3{color:var(--muted)}
/* A source WITH citation detail (author/year, locator, custodian) is its own
   collapsible <details class="src">: the summary keeps the row a reader scans by
   (linked title + colour-coded tier pill) and the detail folds under it, on the
   same +/– marker scheme as the .lf folds. A title-only source stays the static
   div above. Additive: the base .src card look is unchanged. */
.dvpage details.src{display:block;padding:0}
.dvpage details.src[open]{border-color:color-mix(in srgb,var(--gold) 40%,var(--edge))}
/* The summary row itself NEVER wraps (marker, inner wrapper, speak button); the
   .src-hr wrapper is the flex-wrap container. This keeps the +/- on the title's
   own baseline at every size (a pixel-pinned marker floated off the line), while
   a long title still wraps INSIDE the wrapper -- the wrap-precedes-shrink stacking
   that broke the phone layout cannot move the marker, because the marker and the
   wrapper are siblings in a nowrap row. */
.dvpage details.src>summary.src-h{cursor:pointer;list-style:none;display:flex;flex-wrap:nowrap;
  align-items:baseline;gap:8px;padding:9px 12px}
.dvpage details.src>summary.src-h::-webkit-details-marker{display:none}
.dvpage details.src>summary.src-h::before{content:"+";color:var(--gold);font-family:var(--serif);
  font-size:1rem;line-height:1;flex:none;width:1ch;transition:.15s}
.dvpage details.src[open]>summary.src-h::before{content:"\2013"}
.dvpage .src-hr{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px;flex:1 1 auto;min-width:0}
.dvpage details.src>summary.src-h:hover .src-t{color:var(--gold)}
.dvpage .src-b{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px;
  padding:0 12px 9px 28px;font-size:.86rem}
/* The external link rides in the detail as its own capsule (summary titles toggle,
   capsules navigate -- same rule as the deck rows). */
.dvpage .src-b .src-visit{color:var(--gold);font-size:.72rem;text-decoration:none;white-space:nowrap;
  border:1px solid color-mix(in srgb,var(--gold) 45%,var(--edge));border-radius:99px;padding:2px 9px;margin-left:auto}
.dvpage .src-b .src-visit:hover{color:var(--gold-soft);border-color:var(--gold-soft)}
@media print{.dvpage details.src>summary.src-h::before{display:none}}
/* The custodian pill. NOT a tier: it answers "who is speaking", where the tier answers
   "how was it vetted", and the two are independent -- a custodian source exists at
   every tier. It carries no margin-left:auto, so it trails the tier pill rather than
   fighting it for the end of the row, and it is deliberately worded rather than
   numbered because a number is exactly what failed to communicate this. */
.dvpage .src-cust{cursor:help;font-size:.66rem;padding:2px 8px;border-radius:99px;
  border:1px solid color-mix(in srgb,var(--gold) 45%,var(--edge));color:var(--gold);
  white-space:nowrap;text-transform:uppercase;letter-spacing:.06em}

/* ===========================================================================
   STRUCTURED FACTS -- shared by the entity Facts DRAWER (Entity.aspx) and the
   browse Facts PANEL (Category.aspx). All colours are theme tokens, so light and
   dark work unchanged. The fact grid / citation superscript / tradition tag are
   the same component in both places; only the container chrome differs (a sticky
   right-margin drawer on the entity page, a plain .sec card on the category page).
   =========================================================================== */

/* the grouped fact grid (used inside both the drawer and the panel) */
.factgrp{margin:0 0 15px}
.factgrp:last-child{margin-bottom:0}
.factgrp-h{font-size:.64rem;text-transform:uppercase;letter-spacing:.13em;color:var(--gold);font-weight:600;margin:0 0 8px}
.factgrid{margin:0;display:flex;flex-direction:column;gap:10px}
.fact{margin:0}
.fact dt{color:var(--faint);font-size:.68rem;text-transform:uppercase;letter-spacing:.05em;margin:0 0 1px}
.fact dd{margin:0;color:var(--ink);font-size:.92rem;line-height:1.5;overflow-wrap:anywhere}
.fact-scope{display:inline-block;font-size:.63rem;padding:1px 8px;border-radius:99px;background:var(--panel);
  border:1px solid var(--edge2);color:var(--muted);margin-left:3px;white-space:nowrap;vertical-align:middle}
a.fact-cite{font-size:.62rem;line-height:1;vertical-align:super;color:var(--gold);text-decoration:none;
  font-variant-numeric:tabular-nums;margin-left:2px}
a.fact-cite:hover{text-decoration:underline}
a.fact-cite:focus-visible{outline:2px solid var(--gold);outline-offset:2px;border-radius:3px}

/* the numbered Sources footnotes that the fact-cite superscripts resolve to */
.factsrc{margin:15px 0 0;border-top:1px solid var(--edge);padding-top:11px}
.factsrc-h{font-size:.6rem;text-transform:uppercase;letter-spacing:.13em;color:var(--faint);font-weight:600;margin:0 0 7px}
.factsrc-l{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:6px}
.factsrc-l li{font-size:.76rem;color:var(--muted);line-height:1.45;overflow-wrap:anywhere}
.factsrc-l a{color:var(--ink);text-decoration:none}
.factsrc-l a:hover{color:var(--gold);text-decoration:underline}
.factsrc-m{color:var(--faint)}

/* ---- browse Facts PANEL (Category.aspx): a .sec card whose groups flow into
   columns on a wide viewport and stack on a narrow one. ---- */
/* 14.6667rem is 220.0px at the default. rem for the same reason the entity page's
   .tf-grp basis is (entity.css): the floor decides the column count, so a px floor
   holds the number of columns fixed while the text inside them grows, and the fact
   values are what lose the room. */
.dvpage .sec.facts .facts{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(14.6667rem,100%),1fr));gap:6px 30px}

/* ---- entity Facts DRAWER (Entity.aspx) ----
   ONE component in TWO states. It looks the same, sits in the same place and behaves
   the same way in both. The only differences are that a PINNED drawer is permanent
   and RESERVES a column of the page, and an UNPINNED one is temporary -- it slides
   in over the prose, is dismissed by the close button, Escape or a click outside,
   and reserves nothing, so the prose runs the full width of the page.

   The state is carried on the ARTICLE and is SERVER-RENDERED from the reader's
   stored preference (a saved member preference for a signed-in member, a cookie for an
   anonymous visitor -- see App_Code/Business/UserPreferences.vb), so the page paints
   in the right state instead of having script correct it after the fact:

     article.hasfacts             PINNED   (also the no-JS default, and the default
                                            on a wide viewport with nothing stored)
     article.hasfacts.factsoff    UNPINNED (the default on a narrow viewport with
                                            nothing stored; the handle is the way in)
     article.hasfacts.factsenh    the page script is running -- see below
     article.hasfacts.factsopen   UNPINNED and currently slid in

   factsenh IS THE PROGRESSIVE-ENHANCEMENT GATE. The server can emit .factsoff
   because it knows the stored preference, but it cannot know whether script will
   run -- and an off-canvas drawer with no script is a panel the reader can never
   open. So every off-canvas rule below is gated on .factsenh, which ONLY the page
   script adds. With scripting off the article is a plain single column and the
   drawer is an ordinary card at the end of it: fully readable in either state, and
   none of the controls (handle / pin / close, all shipped [hidden]) are ever shown.

   POSITION AND SIZE, IN BOTH STATES: the top edge sits UNDER the sticky site header
   and never over it (--headh is measured from the real header by the page script,
   with the historic 68px as the no-script fallback); the right edge sits on the
   page's right margin; the width is the same; and the panel is sized to its CONTENT
   with no max-height and no overflow of its own, so there is never a scrollbar
   inside the page. Travelling with the reader is opt-in and MEASURED: the script
   adds .canstick only when the drawer is shorter than the viewport. A drawer taller
   than the viewport keeps its place and is read by scrolling the PAGE -- which is
   the entire reason it is given no inner scroller.

   THE SCRIM IS GONE. It was a fixed full-viewport click-catcher, i.e. precisely the
   thing that lay over the site header, and it only made sense while the panel was a
   modal full-height overlay. It is not one any more: it hangs below the header in
   the right margin and the rest of the page stays live and clickable. Click-outside-
   to-close is kept, done by a document click listener that deliberately ignores
   clicks on the site header -- something a scrim could not do. */
/* Learn-more / long-form panels on CATEGORY pages. The component's full styling lives
   in Entity.aspx's page <style> scoped to .entity; a .catpage renders the SAME markup
   but never loaded those page-local rules, so /calendars and other category pages showed
   the panels unstyled. These mirror the .entity rules for .catpage, in the shared sheet. */
.catpage .sec.learnmore{background:none;border:0;padding:0;margin-bottom:16px}
.dvpage .lf{background:var(--panel);border:1px solid var(--edge);border-radius:13px;margin-bottom:10px;overflow:hidden}
.dvpage .lf[open]{border-color:color-mix(in srgb,var(--gold) 40%,var(--edge));background:var(--panel2)}
.dvpage .lf>summary{cursor:pointer;list-style:none;padding:13px 17px;display:flex;align-items:center;gap:10px;color:var(--ink);font-size:.98rem;font-weight:600;transition:.13s}
.dvpage .lf>summary::-webkit-details-marker{display:none}
.dvpage .lf>summary:hover{color:var(--gold)}
.dvpage .lf>summary::before{content:"+";color:var(--gold);font-family:var(--serif);font-size:1.1rem;line-height:1;width:1ch;flex:none;transition:.15s}
.dvpage .lf[open]>summary::before{content:"\2013"}
/* text-wrap:balance REMOVED here, w-launchfix1-2148, 2026-08-23 (launch
   readiness audit item 14; full reasoning in App_Themes\Divinity\home.css
   at its own .homepage .fcard h4 rule, ruling 309 prevalence sweep).
   flex:1 1 auto and min-width:0 alone do not protect against the failure,
   which is in the balance layout pass itself, not in ordinary wrapping;
   the identical pairing was already proven insufficient on the featured-
   card heading this same sweep fixed in home.css. */
.dvpage .lf>summary .lfh{font-family:var(--serif);flex:1 1 auto;min-width:0}
/* SCALED WITH THE READER'S TEXT UNDER THE OWNER'S 2026-08-03 RULING, and it is
   here as well as on .dspeak deliberately. chrome.css's read-aloud block was
   written to be "exactly the Learn-more .lfspeak style", so the two are one
   control wearing two selectors: moving only the injected one would recreate the
   very split entity.css records a few hundred lines below, where one family drew
   this control at a flat 40px while every other surface drew it at 2.5em and
   nobody noticed until a reader touched A+. Same tokens, same result.

   THE OPACITY IS LEFT AT .8 ON PURPOSE. The note below it is a measured contrast
   decision (3.42 dark / 3.15 light against the corrected --faint, WCAG 1.4.11),
   not the unliftable 55% dulling the owner ruled on for .dspeak, and .8 is
   plainly visible without hovering. Overturning a measured decision needs its
   own question, so this change is the box only. */
.dvpage .lf>summary .lfspeak{flex:0 0 auto;min-width:var(--actbtn);min-height:var(--actbtn);display:inline-flex;align-items:center;justify-content:center;padding:0;border:1px solid transparent;border-radius:99px;background:none;font:inherit;color:var(--faint);cursor:pointer;opacity:.8;transition:.13s}
/* opacity WAS .55, which put the Learn More read-aloud button at 1.94:1 in dark and
   1.67:1 in light against the surface behind it -- WCAG 1.4.11 asks 3:1 of a control's
   icon, and this is the control the owner uses more than any other on the site. It was
   effectively invisible until hovered, which also meant a touch user had no way to
   discover it at all. .8 against the corrected --faint gives 3.42 dark / 3.15 light.
   w18-a11y-audit, 2026-07-31. */
.dvpage .lf>summary .lfspeak svg{width:var(--actico);height:var(--actico)}
.dvpage .lf>summary:hover .lfspeak{opacity:1}
.dvpage .lf>summary .lfspeak:hover{color:var(--gold);border-color:var(--edge2);background:var(--panel2)}
.dvpage .lf>summary .lfspeak:focus-visible{opacity:1;outline:2px solid var(--gold);outline-offset:2px}
.dvpage .lf>summary .lfspeak.on{opacity:1;color:var(--gold);border-color:var(--gold)}
.dvpage .lfb{padding:2px 17px 17px;border-top:1px solid var(--edge);margin-top:-1px}
.dvpage .lfb>*:first-child{margin-top:14px}
/* THE ARTICLE CITATION LINE. New: no
   article's citation rendered anywhere on this site before this, so
   there was no existing .lfb paragraph rule to extend. Reuses .src-t/.src-m
   UNCHANGED, the exact title-then-author pair dbo.vSource already prints in
   the page-level Sources fold a few sections down this same sheet, so the
   only new selectors are the row itself and its small gold label, which
   copies .xtnote-l's declaration (color, size, case, spacing, weight)
   because that is the site's one established "small gold sibling label"
   look and a reader who has met it once should read this one the same way.
   rem/em throughout, the same rule every fact-adjacent label on this sheet
   follows, so the A-/A+ control governs it and nothing here is a fixed box
   around scaling text; overflow-wrap:anywhere because a long source title
   must wrap rather than push the fold wider than its card at 200% text. */
.dvpage .lfb .lfsrc{margin:14px 0 0;padding-top:10px;border-top:1px solid var(--edge);
  color:var(--muted);font-size:.82rem;line-height:1.55;overflow-wrap:anywhere}
.dvpage .lfb .lfsrc .lfsrc-l{color:var(--gold);font-size:.86em;text-transform:uppercase;
  letter-spacing:.06em;font-weight:600}
.catpage .lfb p{font-size:1rem;line-height:1.75;color:var(--ink);max-width:70ch;margin:0 0 13px}
.catpage .lfb h3{font-family:var(--serif);color:var(--gold);font-size:1.03rem;font-weight:600;margin:20px 0 9px}
.catpage .lfb h4{font-family:var(--serif);color:var(--muted);font-size:.94rem;font-weight:600;margin:16px 0 7px}
.catpage .lfb ul{margin:0 0 13px;padding-left:20px;max-width:70ch}
.catpage .lfb li{font-size:1rem;line-height:1.7;color:var(--ink);margin-bottom:5px}
.catpage .lfb blockquote{margin:0 0 13px;padding:2px 0 2px 15px;border-left:2px solid var(--gold);color:var(--muted);font-style:italic;max-width:66ch}
.catpage .lfb table,.catpage .lfb pre{display:block;max-width:100%;overflow-x:auto}
.catpage .lfb b{color:var(--gold-soft)}
:root[data-theme="light"] .catpage .lfb b{color:var(--gold)}
@media (prefers-color-scheme: light){:root:not([data-theme="dark"]) .catpage .lfb b{color:var(--gold)}}
@media print{.catpage .lf>summary::before{display:none}.catpage .lfb{border-top:0}}
/* ---- category facts: INLINE, not a drawer (owner, 2026-07-28) -------------
   Category pages used to borrow the entity page's off-canvas drawer: a fixed gold
   FACTS tab on the right edge and a slide-over panel in a second grid column. The
   panel now sits in the page under the description, as a plain <details>.

   Three things fall out of that and all three are wins: no fixed handle competing
   for the right edge with the audio drawer that lives there now; no pin, so no
   stored preference to resolve before the first paint; and no two-column grid, so
   the reading column keeps its full measure.

   Open on desktop, collapsed on a phone -- set by one line of script on the page,
   because only the client knows the viewport. */
/* .catfacts / .catfacts-head / .catfacts-in USED TO BE DECLARED HERE, a component
   built for the category page's facts panel earlier on 2026-07-28. Removed the same
   day: the owner asked for the category Facts heading and container to be exactly
   the entity page's, so Category.aspx.vb now emits the entity page's own
   details.tsec > summary.sechd > section.sec and inherits that styling instead.
   Keeping a near-identical second component beside the first is precisely how the
   two drifted apart in the first place, so it is deleted rather than left dormant. */

.factdrawer{background:var(--panel);border:1px solid var(--edge);border-radius:14px;margin:0 0 18px}
.factdrawer-in{padding:15px 17px}
.factdrawer-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin:0 0 13px}
.factdrawer-title{font-family:var(--serif);color:var(--gold);font-size:1.08rem;font-weight:600}
.factdrawer-btns{display:flex;align-items:center;gap:4px}
.factdrawer-pin,.factdrawer-close{background:none;border:1px solid transparent;color:var(--muted);border-radius:8px;
  width:30px;height:30px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;padding:0}
.factdrawer-pin svg,.factdrawer-close svg{width:16px;height:16px}
.factdrawer-pin:hover,.factdrawer-close:hover{color:var(--gold);border-color:var(--edge2);background:var(--panel2)}
.factdrawer-pin[aria-pressed="true"]{color:var(--gold);border-color:var(--edge2);background:var(--panel2)}
.factdrawer-pin[hidden],.factdrawer-close[hidden]{display:none}
/* Closing an already-pinned drawer would just be a second unpin control, so the
   close button only exists in the slid-out state. */
.entity.dvpage.hasfacts:not(.factsoff) .factdrawer-close,
.catpage.dvpage.hasfacts:not(.factsoff) .factdrawer-close{display:none}

/* The handle. Fixed to the right edge at EVERY width and deliberately large enough
   to be found: a vertical "Facts" label with a chevron, not a sliver. It is the ONLY
   way back into an unpinned drawer -- and on a phone, where unpinned is the default
   for a reader who has never chosen, it is the only thing advertising that the panel
   exists at all -- so it is bigger and louder at narrow widths, not smaller. It is
   present only while the drawer is unpinned AND closed; pinned, the drawer itself is
   the affordance, and while it is slid in the panel would sit on top of it. */
.factdrawer-handle{position:fixed;right:0;top:50%;transform:translateY(-50%);z-index:56;
  display:inline-flex;flex-direction:column;align-items:center;justify-content:center;gap:7px;
  background:var(--gold);color:#1a1508;border:0;border-radius:11px 0 0 11px;cursor:pointer;
  font-weight:700;letter-spacing:.09em;font-size:.75rem;text-transform:uppercase;padding:19px 10px;
  box-shadow:var(--shadow)}
.factdrawer-handle .fh-t{writing-mode:vertical-rl;text-orientation:mixed}
.factdrawer-handle svg{width:15px;height:15px;flex:0 0 auto}
.factdrawer-handle:hover{background:var(--gold-soft)}
@media(max-width:999px){
  .factdrawer-handle{padding:23px 13px;font-size:.82rem;gap:9px;border-radius:14px 0 0 14px}
  .factdrawer-handle svg{width:17px;height:17px}
  /* The handle is position:fixed at the viewport's right edge, 50% height, so as the
     reader scrolls EVERY line of prose passes behind it (not just the header buttons the
     original gutter reasoning covered). On a phone there is no side-rail gutter to absorb
     it, so reserve a right gutter on the content ONLY while the handle is actually shown.
     Per factdrawer.js the handle shows exactly in the UNPINNED + CLOSED state -- article
     carries .factsoff and not .factsopen (pinned removes .factsoff; open adds .factsopen,
     both of which hide the handle). Scoping to that state keeps the PINNED inline drawer
     and the OPEN slide-over at full width. */
  .entity.dvpage.hasfacts.factsoff:not(.factsopen),
  .catpage.dvpage.hasfacts.factsoff:not(.factsopen){padding-right:52px}
}
/* The light theme's --gold is dark enough that the near-black label on it lands at
   about 4:1 -- under AA for a 12px bold label. White on that same gold clears it, and
   the hover darkens rather than lightens so the hover state clears it too. The dark
   theme's gold is bright, so there the near-black label is the readable pairing. */
@media (prefers-color-scheme: light){
  .factdrawer-handle{color:#fff}
  .factdrawer-handle:hover{background:#7f5f19}
}
:root[data-theme="dark"] .factdrawer-handle{color:#1a1508}
:root[data-theme="dark"] .factdrawer-handle:hover{background:var(--gold-soft)}
:root[data-theme="light"] .factdrawer-handle{color:#fff}
:root[data-theme="light"] .factdrawer-handle:hover{background:#7f5f19}
.factdrawer-handle[hidden]{display:none}
.entity.dvpage.hasfacts:not(.factsoff) .factdrawer-handle,
.catpage.dvpage.hasfacts:not(.factsoff) .factdrawer-handle{display:none}
.entity.dvpage.hasfacts.factsopen .factdrawer-handle,
.catpage.dvpage.hasfacts.factsopen .factdrawer-handle{display:none}

/* PINNED on desktop: two-column shell -- prose left (capped at the measure), facts
   docked in the right margin.
   The 1000px floor is where the two columns genuinely fit: the 34px .wrap padding
   either side, the 40.5rem measure, the 38px gutter and a 220px drawer come to
   ~980px. Below it the left column would have to eat into the drawer, so the page
   drops to one column and pinned means the inline infobox instead. (The measure
   column is minmax(0,...) rather than a fixed width, so even at the boundary the
   prose flexes down instead of overflowing the page.)

   NOTE ON THE SCROLLBAR: the docked drawer is deliberately given NO max-height and
   NO overflow. It sizes to its content and scrolls with the page, so there is never
   a scrollbar inside the page. Stickiness is opt-in: the script measures the drawer
   and only adds .canstick when it is shorter than the viewport. A drawer taller than
   the viewport therefore sits still in its column and is read by scrolling the page
   normally -- which degrades gracefully, unlike a sticky box with an inner scroller
   (the old max-height:calc(100vh - 92px);overflow:auto, which is what produced the
   nested scrollbar the owner saw). */
@media(min-width:1000px){
  /* The drawer's position:sticky resolves against the viewport because the
     no-horizontal-scroll guard sits on HTML alone -- see the base rules at the top
     of this file. Nothing page-specific is needed here. */
  .entity.dvpage.hasfacts:not(.factsoff),
  .catpage.dvpage.hasfacts:not(.factsoff){display:grid;
    grid-template-columns:minmax(0,var(--measure)) minmax(220px,1fr);
    column-gap:38px;align-items:start}
  .entity.dvpage.hasfacts:not(.factsoff)>.dvrail,
  .catpage.dvpage.hasfacts:not(.factsoff)>.dvrail{grid-column:1 / -1;grid-row:1}
  .entity.dvpage.hasfacts:not(.factsoff)>.dvbody,
  .catpage.dvpage.hasfacts:not(.factsoff)>.dvbody{grid-column:1;grid-row:2;min-width:0}
  .entity.dvpage.hasfacts:not(.factsoff)>.factdrawer,
  .catpage.dvpage.hasfacts:not(.factsoff)>.factdrawer{grid-column:2;grid-row:2;align-self:start;margin:0}
  .entity.dvpage.hasfacts:not(.factsoff)>.factdrawer.canstick,
  .catpage.dvpage.hasfacts:not(.factsoff)>.factdrawer.canstick{position:sticky;top:calc(var(--headh, 68px) + 8px)}
}

/* PINNED on mobile: the script has moved the drawer to sit directly under the
   identity header, so it is a normal in-flow card. Nothing to do but keep it clear
   of the prose that follows. */
@media(max-width:999px){
  .entity.dvpage.hasfacts:not(.factsoff)>.factdrawer,
  .catpage.dvpage.hasfacts:not(.factsoff)>.factdrawer{margin:0 0 18px}
}

/* UNPINNED (script running). The SAME panel in the SAME place -- it just does not
   reserve the column.
   The article becomes a single-column grid and the drawer is placed in the SAME
   grid cell as the prose, aligned to the end of it. Overlapping grid items is what
   buys both halves of the requirement at once: the prose gets the whole width
   (nothing is reserved for the drawer) while the drawer still hangs from the page's
   right margin, at the same width it has when docked -- calc(100% - measure - gutter)
   is literally the docked column's width, measured against the same box, so the two
   states line up to the pixel with no viewport arithmetic and no scrollbar-width
   guesswork.
   Being a normal in-flow grid item is also what lets it use the same position:sticky
   (via the same measured .canstick) as the docked state, and what lets an overlong
   panel scroll away with the page instead of trapping its overflow in an inner
   scroller. position:fixed could do neither. */
.entity.dvpage.hasfacts.factsoff.factsenh,
.catpage.dvpage.hasfacts.factsoff.factsenh{display:grid;grid-template-columns:minmax(0,1fr);align-items:start;
  /* The parked panel is translated past the right edge, and a transform DOES enlarge
     the document's scrollable overflow -- the html overflow-x guard hides the
     scrollbar but the page stays programmatically pannable, which position:fixed
     never did. overflow-x:clip cuts it off here instead. CLIP, not hidden: clip is
     not a scroll container, so the drawer's position:sticky still resolves against
     the viewport (hidden would break it, exactly as the note at the top of this file
     warns). A browser too old for `clip` drops the declaration and simply behaves as
     the page did before -- clipped by html, never scrolled by the reader. */
  overflow-x:clip;overflow-y:visible}
.entity.dvpage.hasfacts.factsoff.factsenh>.dvrail,
.catpage.dvpage.hasfacts.factsoff.factsenh>.dvrail{grid-column:1;grid-row:1}
.entity.dvpage.hasfacts.factsoff.factsenh>.dvbody,
.catpage.dvpage.hasfacts.factsoff.factsenh>.dvbody{grid-column:1;grid-row:2;min-width:0}
.entity.dvpage.hasfacts.factsoff.factsenh>.factdrawer,
.catpage.dvpage.hasfacts.factsoff.factsenh>.factdrawer{grid-column:1;grid-row:2;
  justify-self:end;align-self:start;margin:0;z-index:60;box-shadow:var(--shadow);
  width:min(360px,100%);
  /* Parked off the right edge of the page (html carries the overflow-x guard, so it
     never widens the document) and slid back in by .open. visibility is what stops a
     closed panel from swallowing clicks over the prose it covers; it is switched at
     the end of the slide out and at the start of the slide in. */
  transform:translateX(calc(100% + 44px));visibility:hidden;
  transition:transform .22s ease, visibility 0s linear .22s}
.entity.dvpage.hasfacts.factsoff.factsenh>.factdrawer.open,
.catpage.dvpage.hasfacts.factsoff.factsenh>.factdrawer.open{transform:none;visibility:visible;
  transition:transform .22s ease, visibility 0s}
.entity.dvpage.hasfacts.factsoff.factsenh>.factdrawer.canstick,
.catpage.dvpage.hasfacts.factsoff.factsenh>.factdrawer.canstick{position:sticky;top:calc(var(--headh, 68px) + 8px)}
@media (prefers-reduced-motion: reduce){
  .entity.dvpage.hasfacts.factsoff.factsenh>.factdrawer,
  .entity.dvpage.hasfacts.factsoff.factsenh>.factdrawer.open,
  .catpage.dvpage.hasfacts.factsoff.factsenh>.factdrawer,
  .catpage.dvpage.hasfacts.factsoff.factsenh>.factdrawer.open{transition:none}
}
@media(min-width:1000px){
  /* Exactly the docked column's width: the article's content box less the measure
     and the 38px gutter, floored at the same 220px the docked track is floored at. */
  .entity.dvpage.hasfacts.factsoff.factsenh>.factdrawer,
  .catpage.dvpage.hasfacts.factsoff.factsenh>.factdrawer{
    width:max(220px, calc(100% - var(--measure) - 38px))}
}

/* ---- cross-tradition connections (the relation graph) ----
   Reuses .relgroup / .rl / .chip / .src-tier; adds the row shell, the three status
   badges and the debunked-myth correction line. The attested/debated/debunked reading
   is carried on the shared/gold/dark vars so it holds in both light and dark themes. */
.dvpage .xtlist{display:flex;flex-direction:column;gap:10px}
.dvpage .xtrow{background:var(--panel2);border:1px solid var(--edge);border-left-width:3px;
  border-radius:10px;padding:10px 12px;display:flex;flex-direction:column;gap:6px}
.dvpage .xt-att{border-left-color:var(--shared)}
.dvpage .xt-deb{border-left-color:var(--gold)}
.dvpage .xt-myth{border-left-color:var(--dark-c)}
/* "unknown", the fourth status: nobody has an answer,
   as against debated where scholars have competing ones. Deliberately the quietest
   of the four, on --muted rather than a signal colour, because an open question is
   an invitation rather than a warning. It must not read as louder than debunked. */
.dvpage .xt-unk{border-left-color:var(--muted);border-left-style:dashed}
/* partially-attested / partially-debunked:
   the fifth and sixth status, for a claim genuinely too tangled to split into its
   own attested fact plus a debated-or-debunked remainder. READS AS MIXED, never
   as a new colour: each borrows the LEANING parent's own hue (--shared for the
   attested lean, --dark-c for the debunked lean) exactly as xt-att/xt-myth do,
   but DASHED, this block's own established style for "not fully settled"
   (xt-unk). A partial row is never mistaken for either the fully-settled edge it
   leans toward or for xt-unk's "nobody has an answer at all", which this is not:
   part of it IS answered. */
.dvpage .xt-partatt{border-left-color:var(--shared);border-left-style:dashed}
.dvpage .xt-partdeb{border-left-color:var(--dark-c);border-left-style:dashed}
.dvpage .xtmain{display:flex;flex-wrap:wrap;align-items:center;gap:8px}
.dvpage .xtmyth{color:var(--ink);font-size:.9rem;overflow-wrap:anywhere}
.dvpage .xtnote{color:var(--muted);font-size:.84rem;margin:0;line-height:1.5}
.dvpage .xtb{font-size:.66rem;padding:2px 9px;border-radius:99px;border:1px solid var(--edge2);
  white-space:nowrap;text-transform:uppercase;letter-spacing:.06em;cursor:help}
.dvpage .xtb-att{color:var(--shared);border-color:var(--shared);background:var(--shared-bg)}
.dvpage .xtb-deb{color:var(--gold);border-color:color-mix(in srgb,var(--gold) 55%,var(--edge))}
.dvpage .xtb-myth{color:var(--dark-c);border-color:var(--dark-c);background:var(--dark-bg)}
.dvpage .xtb-unk{color:var(--muted);border-color:var(--edge2);border-style:dashed}
/* The "associated" stance, reading "Connected".
   This is the lightest badge in the family on purpose: "holds" draws no badge
   at all, so the row beneath it that DOES draw one has to read as a step down
   from that silence, not a step up from it. --muted text, a solid (not dashed
   or dotted) border in --edge2, no background tint: solid because the
   connection is real and settled, unlike .xtb-unk's dashed "not fully settled"
   border on the SAME neutral colour; no tint because a tint here would read as
   agreement with .xtb-att's shared-bg two lines up, which is exactly the
   well-attested look this badge exists to be told apart from. Never the sole
   signal regardless: the badge carries its own word, "Connected", same as
   every other stance and status badge in this file. */
.dvpage .xtb-assoc{color:var(--muted);border-color:var(--edge2)}
/* partially-attested / partially-debunked.
   TEXT AND BORDER ON --ink, the SAME neutral pairing .xtb-var below already uses
   for "variant", the one other badge here whose whole point is that neither side
   is the baseline: proven safe on both themes rather than a new guess, measured
   there at 11.92 to 14.39:1 against --panel/--panel2 in both themes (comfortably
   past AA and AAA alike), and re-verified here against --shared-bg/--dark-bg
   composited over --panel2 in both themes before this rule was written (worst
   case 10.24:1 dark, 11.61:1 light). DASHED, this block's own established style
   for "not fully settled" (xtb-unk). What makes each read as MIXED rather than
   as a second "unknown" is the BACKGROUND TINT, borrowed from whichever parent
   status the row leans toward: --shared-bg (attested's own tint) behind
   "Partially Attested", --dark-bg (debunked's own tint) behind "Partially
   Debunked". Colour is still never the sole signal, exactly as the note above
   .xtb-var already says: the badge carries its own words either way. */
.dvpage .xtb-partatt{color:var(--ink);border-color:var(--edge2);border-style:dashed;background:var(--shared-bg)}
.dvpage .xtb-partdeb{color:var(--ink);border-color:var(--edge2);border-style:dashed;background:var(--dark-bg)}
/* ---- a non-attested status badge that is a real BUTTON --------------------
   EntityMarkup.Badge emits a <button class="xtb xtb-XXX xtb-why"> instead of a
   span for a debated, debunked or unknown fact that carries a dbo.EntityProperty
   id, opening js/factwhy.js's "why" panel. This block strips the handful of
   browser button defaults .xtb's own declarations do not already cover (font,
   line-height, margin, the UA appearance), pads the TAP TARGET outward without
   touching the pill's own visible size, which stays exactly the "xtb" look
   above, and (background:none, below) closes a contrast hole every
   background-less xtb/src-tier modifier fell into.

   THE CONTRAST DEFECT, owner-reported 2026-08-11 on Christianity's
   "Practitioner System" register pill: appearance:none removes the NATIVE
   button chrome but does NOT clear the UA stylesheet's own
   background-color:buttonface, which stays painted (measured rgb(240,240,240),
   an opaque near-white) underneath whatever colour a modifier class sets for
   its text. Nine modifier classes never declare their own background, because
   a plain span never needed one: .xtb-reg, .xtb-deb, .xtb-unk, .xtb-assoc,
   .xtb-crit, .xtb-var, .xtb-banddev, .xtb-bandprelim, and .src-tier's own base
   plus .tier2/.tier3. Every one of those is wired to render as a button in
   production today (StatusBadge and StanceBadge pass a whyId on every live
   call site; TierPill gets a real sourceId from Entity.aspx.vb, SpreadMap.aspx.vb
   and Cases.aspx.vb), so this was never a span-only, theoretically-safe gap.
   A theme-coloured pill text (var(--muted), var(--gold), var(--ink)...) over an
   OS-level near-white box is a light-on-light or low-contrast pairing in BOTH
   themes at once, because the background is not a theme token at all, it is
   the browser's own default, which is why the earlier fix guess of a
   light-theme pair leaking into dark theme was close but not quite it.

   ONE background:none here removes the hole at its root for every current and
   future xtb/src-tier modifier: a class that sets its own background (xtb-att,
   xtb-myth, xtb-rej, xtb-partatt, xtb-partdeb, xtb-bandtv, xtb-bandv,
   src-tier.tier1) still wins, because ".dvpage .xtb-att" and its siblings carry
   two classes of specificity against this rule's one-element-one-class, so
   nothing here can override an intentional background. What changes is only
   the classes that were relying on a background that was never actually
   transparent. */
button.xtb-why{font:inherit;line-height:inherit;margin:0;-webkit-appearance:none;
  appearance:none;background:none;cursor:pointer;position:relative}
button.xtb-why::-moz-focus-inner{border:0;padding:0}
/* A SPECIFICITY FIX, not a new rule of its own.
   .dvpage .xtb and .dvpage .src-tier (two classes, higher specificity than the
   one element plus one class above) both set cursor:help for the ordinary,
   non-clickable case, and that rule was quietly winning over button.xtb-why's
   cursor:pointer on every badge that had already been turned into a button:
   the pointer never showed a hand, only the "hover for a tooltip" cursor, on a
   control that in fact opens a panel on click. Caught while wiring the tier
   pill into the same button shape; it was already live on the status and
   stance why-buttons before this change touched anything. Three classes beats
   two, so this wins without touching the two-class rules that colour the
   ordinary span case correctly. */
.dvpage .xtb.xtb-why,.dvpage .src-tier.xtb-why{cursor:pointer}
/* The expanded hit area: an absolutely positioned box with no visible paint,
   reaching .5em beyond the pill on every side. .tf-b and .xtmain both give this
   badge room of its own (see Entity.aspx.vb's own notes on .tf-b taking a full
   row), so this does not run into a neighbouring control. */
button.xtb-why::after{content:"";position:absolute;inset:-.5em}
button.xtb-why:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
/* ---- stance badges (wave 18) ----
   A SECOND axis beside the four status badges above, and it answers a different
   question: those say how settled a claim is, these say what the body thinks of it.
   Only the two negative stances draw one, so a page of ordinary affirmative
   relations gains nothing. They inherit .xtb wholesale, which means .66rem and no
   fixed height, so the A-/A+ control still owns them at 200% exactly as it owns
   Well-attested. The colour is never the signal: every one of these badges carries
   its own words ("Rejected Here", "Critiqued Here") and a full sentence on
   title + aria-label. */
.dvpage .xtb-rej{color:var(--dark-c);border-color:var(--dark-c);background:var(--dark-bg);font-weight:600}
.dvpage .xtb-crit{color:var(--gold);border-color:color-mix(in srgb,var(--gold) 55%,var(--edge));
  border-style:dashed}
/* The fifth stance, "variant", reading "Held Differently".
   It is the one stance carved out of the AFFIRMATIVE side: the body holds the thing,
   by a different account. So it must not borrow either negative treatment. --dark-c
   would read as a rejection, which is the precise misreading the value exists to
   prevent, and --gold is the critique colour besides.

   COLOUR CHOSEN ON MEASURED CONTRAST, not on taste, because the badge text is .66rem
   uppercase and therefore SMALL text under WCAG 2.2, which needs 4.5:1. Measured
   against both themes' --panel and --panel2:
       --ink     14.39 / 13.15 dark, 11.92 / 13.38 light   -> used
       --shared   8.05 dark, 3.54 light                    -> FAILED AA in light
       --gold     3.48 light                               -> FAILS, known, unfixed
   --ink is the only token that clears AA on all four surfaces, and at 11.92:1 worst
   case it clears AAA (7:1) too. Reported separately: .xtb-att above is on --shared
   and was therefore at 3.54:1 in the light theme, 3.10:1 once its --shared-bg was
   composited in.

   THAT HALF IS NOW FIXED, so do not quote the --shared row above as live. Acting on
   the report, w19-a11y-shared re-measured it on 2026-07-31 and moved the TOKEN rather
   than this badge: light --shared went #3c8578 -> #2f685e, worst case 3.10 -> 4.58,
   which carried .xtb-att and the eight other places --shared is small text in one
   change. The same sweep found --dark-c failing in BOTH themes, 4.33 light and 4.15
   dark, which is .xtb-myth and .xtb-rej two lines below, and moved that token too.
   See the notes on the token declarations at the top of this file. --gold is still
   unfixed and is still deliberately so, because --gold-soft beside it is a background
   colour and the pair has to move together.

   None of that changes the choice made here: --ink remains the right value for this
   badge, on the affirmative-but-different reading, not on contrast alone.

   It inherits .xtb wholesale, so it keeps .66rem and NO fixed height and the A-/A+
   control still owns it at 200%. Nothing here sets a px font or a box around text.
   DOTTED, where rejects is solid and critiques and unknown are dashed, so the border
   distinguishes it without relying on hue; and it is the brightest of the five in
   dark and the darkest in light, which is the distinction that survives a central
   field loss. Colour is still never the signal: the badge carries the words
   "Held Differently" and a full sentence on title and aria-label. */
.dvpage .xtb-var{color:var(--ink);border-color:var(--edge2);border-style:dotted;font-weight:600}
/* The two-part confidence badge. .xtb-reg is
   the register pill, informational rather than a confidence judgment, so it
   reuses .xtb-assoc's neutral, untinted, solid-border treatment exactly
   (the same colour already proven safe on both themes). The four standing
   pills reuse the SAME tokens .xtb-att / .xtb-deb / .xtb-unk already carry,
   at the same proven contrast: Thoroughly Verified is the boldest of the
   two positive bands (font-weight 600, the badge family's own marker for
   "strongest of a pair", also used by .xtb-var above); Verified reuses
   .xtb-att's shared-colour tint unweighted; Developing reuses .xtb-deb's
   gold; Preliminary reuses .xtb-unk's dashed, muted, unsettled look. No new
   colour was introduced for this feature. */
.dvpage .xtb-reg{color:var(--muted);border-color:var(--edge2)}
.dvpage .xtb-bandtv{color:var(--shared);border-color:var(--shared);background:var(--shared-bg);font-weight:600}
.dvpage .xtb-bandv{color:var(--shared);border-color:var(--shared);background:var(--shared-bg)}
.dvpage .xtb-banddev{color:var(--gold);border-color:color-mix(in srgb,var(--gold) 55%,var(--edge))}
.dvpage .xtb-bandprelim{color:var(--muted);border-color:var(--edge2);border-style:dashed}
/* The badge pair sits in its own row, above the standing-notice aside and
   the gallery, first in reading order inside .entlede. Flex-wraps rather
   than clipping at narrow widths or 200% text; no fixed height anywhere,
   matching every other .xtb consumer in this file. */
.dvpage .badgepair{display:flex;flex-wrap:wrap;gap:.5em;align-items:center;margin:0 0 .6em 0}
/* The chip's count slot carries the stance word on the comparison page, so it is the
   one place .cnt appears outside a .tok. Same treatment as .tok .cnt: secondary, and
   in em so it scales with the chip rather than against it. */
.dvpage .chip .cnt{margin-left:6px;font-size:.82em;opacity:.75}
/* ---- open questions (rollout 2026-07-25) ----
   The panel that completes the `unknown` fact status: what nobody has answered
   about this entity, and what would settle it. Deliberately quiet, on --muted and
   a dashed left edge to match the .xt-unk atom badge, because an open question is
   an invitation and must not read louder than a debunking. */
.dvpage .oq{background:var(--panel2);border:1px solid var(--edge);border-left-width:3px;
  border-left-style:dashed;border-left-color:var(--muted);border-radius:10px;
  padding:12px 14px;margin:0 0 10px}
.dvpage .oq-q{color:var(--ink);font-size:.98rem;font-weight:600;line-height:1.45}
.dvpage .oq-why{color:var(--muted);font-size:.86rem;line-height:1.55;margin:6px 0 0}
.dvpage .oq-res{margin:8px 0 0;font-size:.88rem;color:var(--ink);line-height:1.5}
.dvpage .oq-res-l{display:block;color:var(--gold);font-size:.68rem;text-transform:uppercase;
  letter-spacing:.07em;font-weight:600;margin:0 0 2px}
.dvpage .oq-meta{display:flex;flex-wrap:wrap;gap:6px 14px;margin:10px 0 0;
  font-size:.74rem;color:var(--faint)}
.dvpage .oq-disc{text-transform:uppercase;letter-spacing:.05em}
/* oq-status: the question's own page only (w1971-openqpage, 2026-08-16), the
   Open/Resolved/Withdrawn chip that gives a reader landing directly on a
   question's own address the orientation the subject panel's heading and
   list position already give for free. Same faint .oq-meta row, no italic
   and no uppercase, so it reads as a fact rather than a category label. */
.dvpage .oq-status{font-weight:600}
.dvpage .oq-src{font-style:italic}
.dvpage .oq-foot{margin:4px 0 0;font-size:.82rem}
/* ---- dissenting readings (w-dissentrender-1649, 2026-08-27) ----
   Same card shape as .oq immediately above (background, border, radius,
   padding), because both are the atlas naming a limit of its own record;
   the left edge is SOLID gold rather than .oq's dashed muted edge, the
   same tone .xtb-deb already draws a debated claim in, because a dissent
   is a live competing reading recorded against a claim, not merely an
   unanswered gap, and the two must not look identical. */
.dvpage .dis{background:var(--panel2);border:1px solid var(--edge);border-left-width:3px;
  border-left-style:solid;border-left-color:var(--gold);border-radius:10px;
  padding:12px 14px;margin:0 0 10px}
.dvpage .dis-about{color:var(--faint);font-size:.7rem;text-transform:uppercase;
  letter-spacing:.06em;font-weight:600;margin:0 0 4px}
.dvpage .dis-text{color:var(--ink);font-size:.92rem;line-height:1.55;margin:0}
.dvpage .dis-meta{display:flex;flex-wrap:wrap;gap:6px 14px;margin:8px 0 0;
  font-size:.78rem;color:var(--muted)}
.dvpage .dis-who{color:var(--ink)}
.dvpage .dis-who-l{color:var(--faint);font-size:.7rem;text-transform:uppercase;
  letter-spacing:.06em;margin-right:2px}
.dvpage .dis-src{font-style:italic}
.dvpage .xtcite{display:flex;flex-wrap:wrap;align-items:baseline;gap:4px 8px;font-size:.82rem}
.dvpage .xtcite-l{color:var(--faint);font-size:.7rem;text-transform:uppercase;letter-spacing:.06em}

/* ---- music (Divinity Sessions) ---- */
.dvpage .musiclist{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:16px}
.dvpage .musicitem{background:var(--panel2);border:1px solid var(--edge);border-radius:12px;padding:12px;transition:.14s}
.dvpage .musicitem:hover{border-color:var(--gold)}
.dvpage .music-h{display:flex;align-items:baseline;gap:9px;margin-bottom:9px;flex-wrap:wrap}
.dvpage .music-t{font-family:var(--serif);font-size:1rem;color:var(--ink);font-weight:600}
.dvpage .music-k{font-size:.64rem;text-transform:uppercase;letter-spacing:.08em;padding:2px 8px;
  border-radius:99px;background:var(--panel);border:1px solid var(--edge);color:var(--faint)}
/* Responsive 16:9 box: the iframe fills an aspect-ratio padding box, so an embed
   never overflows the column on a phone. */
.dvpage .ytframe{position:relative;width:100%;padding-top:56.25%;border-radius:9px;overflow:hidden;background:var(--ground)}
.dvpage .ytframe iframe{position:absolute;inset:0;width:100%;height:100%;border:0}
/* Click-to-load facade (TrackLinks.YouTubeFacadeHtml). The button IS the player
   until it is pressed: it fills the same 16:9 well the iframe will occupy, so the
   swap on click costs no reflow, and nothing is fetched from youtube.com before
   then. Deliberately NOT scoped to .dvpage -- the Listen hub and the album page
   carry their own .ytframe rules and this button belongs to all three surfaces.
   Sized in em/rem so the A-/A+ control scales it with everything else. */
.ytframe .ytplay{position:absolute;inset:0;width:100%;height:100%;display:flex;
  flex-direction:column;align-items:center;justify-content:center;gap:.5em;
  padding:1em;background:transparent;border:0;cursor:pointer;font-family:inherit;
  font-size:.95rem;line-height:1.35;text-align:center;color:var(--gold,#c9a227)}
.ytframe .ytplay:hover{color:var(--gold-soft,#e3c675)}
.ytframe .ytplay:focus{color:var(--gold-soft,#e3c675);outline:2px solid currentColor;outline-offset:-4px}
.ytframe .ytplay-i{width:3em;height:3em;flex:0 0 auto}
.ytframe .ytplay-t{min-width:0;max-width:100%;overflow-wrap:break-word}
.dvpage .music-more{display:inline-block;margin-top:14px;color:var(--gold);font-size:.86rem;text-decoration:none}
.dvpage .music-more:hover{text-decoration:underline}
/* With several tracks each is its own collapsible <details class="musicitem">
   (a lone track stays the static card above): the title row is the summary, the
   player folds under it -- and a closed fold's lazy iframe is never fetched, so
   a many-track page loads no players up front. Same +/– scheme as the .lf folds;
   base .musicitem card look unchanged. */
.dvpage details.musicitem{display:block;padding:0}
.dvpage details.musicitem[open]{border-color:var(--gold)}
/* No-wrap summary row + inner flex-wrap wrapper, same construction as details.src
   above: the marker keeps the title's baseline, the kind pill wraps inside. */
.dvpage details.musicitem>summary.music-h{cursor:pointer;list-style:none;padding:12px;
  margin-bottom:0;display:flex;flex-wrap:nowrap;align-items:baseline;gap:9px}
.dvpage details.musicitem>summary.music-h::-webkit-details-marker{display:none}
.dvpage details.musicitem>summary.music-h::before{content:"+";color:var(--gold);font-family:var(--serif);
  font-size:1.1rem;line-height:1;flex:none;width:1ch;transition:.15s}
.dvpage details.musicitem[open]>summary.music-h::before{content:"\2013"}
.dvpage .music-hr{display:flex;flex-wrap:wrap;align-items:baseline;gap:9px;flex:1 1 auto;min-width:0}
.dvpage details.musicitem>summary.music-h:hover .music-t{color:var(--gold)}
.dvpage details.musicitem>.ytframe{width:auto;margin:0 12px 12px}
@media print{.dvpage details.musicitem>summary.music-h::before{display:none}}

/* ---- member threads (challenges, comments) ----
   Both are <details> so the browser's own disclosure collapses them while the text
   stays in the served markup. They are .sec cards, so only the summary needs styling
   to look like the section heading it replaces. */
.dvpage .thread>summary{cursor:pointer;list-style:none;display:flex;align-items:center;gap:9px;
  font-family:var(--serif);color:var(--gold);font-size:1.12rem;font-weight:600}
.dvpage .thread>summary::-webkit-details-marker{display:none}
.dvpage .thread>summary::after{content:"+";margin-left:auto;font-size:1.05rem;line-height:1;color:var(--gold)}
.dvpage .thread[open]>summary::after{content:"\2013"}
.dvpage .thread>summary:hover{color:var(--gold-soft)}
:root[data-theme="light"] .dvpage .thread>summary:hover{color:var(--ink)}
@media (prefers-color-scheme: light){:root:not([data-theme="dark"]) .dvpage .thread>summary:hover{color:var(--ink)}}
.dvpage .thread .th-c{color:var(--faint);font-size:.85rem;font-family:var(--sans);font-weight:400;font-variant-numeric:tabular-nums}
/* The pilot's title-outside thread shells (tsec.challenge / tsec.comments) keep the
   same live count beside the gold title, quieter than the title itself. */
.dvpage details.tsec>summary.sechd .th-c{color:var(--faint);font-size:.88rem;font-family:var(--sans);
  font-weight:400;font-variant-numeric:tabular-nums}
/* Inside those shells, what follows the list (the post form / sign-in line) stands
   clearly apart from it: at the list's own 12px card gap the form read as just
   another card in the thread, so this is deliberately roomier. */
.dvpage .sec>.ch-list~*,.dvpage .sec>.cmt-list~*{margin-top:26px}
.dvpage .thread>*:not(summary){margin-top:14px}
/* Same clear list-to-form separation in the in-card (non-pilot) thread shell. */
.dvpage .thread>.ch-list~*,.dvpage .thread>.cmt-list~*{margin-top:26px}
/* The member threads open a different register (contributed content, not curated
   content), so the first of them stands a little further off the section above it
   than the standard 16px section rhythm. */
.dvpage .sec.thread.challenge{margin-top:14px}
.dvpage .ch-empty,.dvpage .cmt-empty,.dvpage .ch-signin,.dvpage .cmt-signin{color:var(--faint);font-size:.86rem;padding:6px 0}
.dvpage .ch-signin a,.dvpage .cmt-signin a{color:var(--gold)}

/* ---- challenges ---- */
.dvpage .ch-list{display:flex;flex-direction:column;gap:12px}
.dvpage .cha{background:var(--panel2);border:1px solid var(--edge);border-radius:11px;padding:12px 14px}
/* An unresolved challenge is drawn provisional -- dashed and faintly gold -- so a
   reader can tell at a glance what has been accepted and what is still contested. */
.dvpage .cha.pending{border-style:dashed;border-color:var(--edge2);
  background:linear-gradient(180deg,color-mix(in srgb,var(--gold) 6%,var(--panel2)),var(--panel2))}
.dvpage .cha-h{display:flex;align-items:center;flex-wrap:wrap;gap:8px;font-size:.82rem;margin-bottom:6px}
.dvpage .cha-h b{color:var(--ink)}
.dvpage .cha-field{font-size:.7rem;padding:2px 8px;border-radius:99px;background:var(--panel);
  border:1px solid var(--edge);color:var(--muted)}
.dvpage .cha-t,.dvpage .cmt-t{color:var(--faint);margin-left:auto;font-size:.78rem}
.dvpage .cha-badge{font-size:.66rem;padding:2px 9px;border-radius:99px;text-transform:uppercase;
  letter-spacing:.06em;border:1px solid var(--edge2);color:var(--muted)}
.dvpage .cha-badge.ch-open{color:var(--gold);border-color:color-mix(in srgb,var(--gold) 55%,var(--edge));
  background:color-mix(in srgb,var(--gold) 10%,transparent)}
.dvpage .cha-badge.ch-review{color:var(--muted);border-color:var(--edge2);background:var(--panel)}
.dvpage .cha-badge.ch-accepted{color:var(--shared);border-color:var(--shared);background:var(--shared-bg)}
.dvpage .cha-badge.ch-rejected{color:var(--dark-c);border-color:var(--dark-c);background:var(--dark-bg)}
.dvpage .cha-pend-note{color:var(--faint);font-size:.72rem;font-style:italic;margin-top:6px}
.dvpage .cha-title{font-family:var(--serif);font-size:1rem;color:var(--ink);margin:2px 0 4px;font-weight:600}
.dvpage .cha-b{color:var(--muted);font-size:.9rem;white-space:pre-wrap;overflow-wrap:anywhere}
.dvpage .cha-corr,.dvpage .cha-src,.dvpage .cha-note{margin-top:8px;font-size:.85rem;color:var(--muted);
  white-space:pre-wrap;overflow-wrap:anywhere}
.dvpage .cha-note{border-left:2px solid var(--gold);padding-left:10px}
.dvpage .cha-lab{display:block;color:var(--faint);font-size:.66rem;text-transform:uppercase;
  letter-spacing:.1em;margin-bottom:2px}
.dvpage .cha-src a{color:var(--gold);overflow-wrap:anywhere}
.dvpage .ch-form-wrap[hidden]{display:none}
.dvpage .ch-open-btn{background:var(--panel2);border:1px solid var(--edge2);color:var(--ink);border-radius:9px;
  padding:8px 15px;font:inherit;font-size:.85rem;cursor:pointer}
.dvpage .ch-open-btn:hover{border-color:var(--gold)}
.dvpage .ch-form{background:var(--panel2);border:1px solid var(--edge);border-radius:11px;padding:14px;margin-top:12px}
.dvpage .ch-form[hidden]{display:none}
.dvpage .ch-fl{display:block;color:var(--muted);font-size:.8rem;margin:10px 0 0}
.dvpage .ch-fl.first{margin-top:0}
.dvpage .ch-opt{color:var(--faint)}
.dvpage .ch-req{color:var(--gold)}
.dvpage .ch-in,.dvpage .ch-ta,.dvpage .cmt-ta{display:block;width:100%;margin-top:4px;background:var(--panel);
  border:1px solid var(--edge);color:var(--ink);border-radius:8px;padding:9px 10px;font:inherit;font-size:.88rem;outline:none}
.dvpage .ch-ta,.dvpage .cmt-ta{min-height:64px;resize:vertical}
.dvpage .ch-in:focus,.dvpage .ch-ta:focus,.dvpage .cmt-ta:focus{border-color:var(--gold)}
.dvpage .ch-actions,.dvpage .cmt-actions{display:flex;align-items:center;gap:12px;margin-top:12px;flex-wrap:wrap}
.dvpage .ch-post,.dvpage .cmt-post{background:var(--gold-bg);color:var(--gold-bg-ink, #1a1508);border:1px solid var(--gold);
  border-radius:8px;padding:8px 16px;font:inherit;font-size:.85rem;font-weight:650;cursor:pointer}
.dvpage .ch-post:disabled,.dvpage .cmt-post:disabled{opacity:.6;cursor:default}
.dvpage .ch-msg,.dvpage .cmt-msg{color:var(--dark-c);font-size:.8rem}

/* ---- comments ---- */
.dvpage .cmt-list{display:flex;flex-direction:column;gap:12px}
.dvpage .cmt{background:var(--panel2);border:1px solid var(--edge);border-radius:11px;padding:12px 14px;position:relative}
/* The gutter reserved for the absolutely-positioned Report button is EM-based: it was
   a fixed 56px while the button's own width scaled with text, so past ~130% the button
   sat on top of the author's name. 6em of the row's .82rem grows in step with the
   button. (Mobile audit, 2026-07-26, finding E2.) */
.dvpage .cmt-h{font-size:.82rem;margin-bottom:5px;display:flex;align-items:baseline;gap:8px;
  flex-wrap:wrap;padding-right:6em}
.dvpage .cmt-h b{color:var(--ink)}
.dvpage .cmt-b{color:var(--muted);font-size:.9rem;white-space:pre-wrap;overflow-wrap:anywhere}
.dvpage .cmt-report{position:absolute;top:10px;right:12px;background:none;border:none;color:var(--faint);
  font-size:.72rem;cursor:pointer;text-decoration:underline}
.dvpage .cmt-report[hidden]{display:none}
.dvpage .cmt-report:hover{color:var(--dark-c)}
.dvpage .cmt-form{background:var(--panel2);border:1px solid var(--edge);border-radius:11px;padding:12px 14px}
.dvpage .cmt-form[hidden]{display:none}
.dvpage .cmt-l{display:block;color:var(--muted);font-size:.8rem;margin-bottom:2px}
/* Honeypot: off-screen rather than display:none, which some bots skip. */
.dvpage .cmt-hp{position:absolute;left:-9999px;width:1px;height:1px;opacity:0}

@media(max-width:560px){
  .dvpage .musiclist{grid-template-columns:minmax(0,1fr)}
  .dvpage .entart{width:4.8rem;height:4.8rem}
  .dvpage .entgthumb{width:3.2rem;height:3.2rem}
  /* "Two per row is a maximum, not a target" (owner, 2026-08-05): on a phone
     the gallery drops to one column rather than squeezing two narrow tiles. */
  .dvpage .entgal{grid-template-columns:minmax(0,1fr)}
  /* Owner wants the prose to wrap the art on phones too: a small float on the right leaves
     a wide reading column on the left rather than the art sitting alone above the prose. */
  .dvpage .entlede .entgal{float:right;max-width:5.3rem;margin:.15rem 0 .55rem .8rem}
  .dvpage .entlede .entgal.isGlyphSet{float:none;max-width:none;width:100%;margin:0 0 1rem}
  .dvpage .entgset{width:5.4rem}
  .dvpage .entgsetart{width:5.4rem;height:5.4rem}
}

/* ---- responsive ----
   The rail unpins and becomes a full-width header card once two columns stop
   fitting; below that the label rail inside .relgroup folds away too. */
@media(max-width:900px){
  .dvpage{padding-top:8px}
  .dvrail{margin:0 0 20px}
  .dvrail .page{font-size:2.05rem}
}
@media(max-width:760px){
  .dvpage{padding-bottom:18px}
  .dvrail{padding:15px 15px 17px;border-radius:14px}
  .dvrail .page{font-size:1.8rem}
  .dvpage .sec{padding:14px 15px 16px}
  /* THE 44px PHONE TOUCH TARGET, ON EVERY SURFACE AND NOT JUST .dvpage.
     Both controls, or they sit in the same row at two different sizes on a
     phone; the follow bell moved into .entacts beside the star on 2026-07-29 and
     inherited this row's phone sizing with it.

     WIDENED 2026-08-02. This bump was scoped to .dvpage, which is the entity and
     category pages and nothing else, so the shared bar's other twenty surfaces
     (every prose page, Compare, the trails, Today, the music pages, the
     communities, the forum) kept a 40px target on a phone while those two got
     44px. For a reader with central-field loss, a small
     touch target is the weakest interaction on the site, so the smaller box was
     exactly the wrong half to leave behind. Same token, same value, all of
     them. */
  .dvpage .readbtn,[data-pageacts] .readbtn,.followbtn,
  .dvpage .actsmore,[data-pagehead]>.actsmore{width:var(--actbtn-touch);height:var(--actbtn-touch)}
  [data-pageacts] .dspeak{min-width:var(--actbtn-touch);min-height:var(--actbtn-touch)}
}
@media(max-width:560px){
  .dvpage .relgroup{margin-top:16px}
  .dvrail .page{font-size:1.6rem}
  /* rem floors here too, 10rem and 11.2rem being 150.0px and 168.0px at the default:
     a phone at 200 per cent text is the case the whole ceiling raise is for, and a px
     floor on the narrowest layout the site has is where a fixed box hurts most. min()
     keeps the floor from exceeding the column it sits in. */
  .dvpage .dvgrid,.dvpage .catgrid{grid-template-columns:repeat(auto-fill,minmax(min(10rem,100%),1fr));gap:9px}
  /* An entry card holds a thumbnail beside its name, so it cannot shrink as far
     as a directory tile: below this the name is squeezed into a two-character column. */
  .dvpage .entgrid{grid-template-columns:repeat(auto-fill,minmax(min(11.2rem,100%),1fr))}
  .dvpage .lead{font-size:1rem}
}
@media (prefers-reduced-motion: reduce){.dvpage,.dvpage *{transition:none!important}}

/* ---- persistent audio dock ----
   A RIGHT-HAND DRAWER on a wide screen; the original bottom bar on a narrow one.
   Owner, 2026-07-27, with screenshots: "put the player on the right side where there
   is plenty of space. Would be great if it could be like a drawer. Not sure if that
   is possible with an iframe."

   It is, and the iframe is not the obstacle: the dock has always lived in the SHELL's
   top window, as a sibling of the stage iframe rather than inside it. That is exactly
   why playback survives navigation, and it is what makes a drawer straightforward --
   the drawer and the framed page are two boxes side by side, with no z-index contest
   and nothing reaching across the frame boundary.

   Why a drawer beats the bottom bar here: the bar spans the full width and stands
   about 140px tall, which on a 16:9 screen is the scarcest axis, and it forced the
   two content-clearance reservations further down this file that were producing the
   dead band the owner also reported. Sideways, the space is free.

   Below 900px it stays a bottom bar. A 19rem drawer on a phone is most of the screen,
   and there the vertical axis is the plentiful one. */
:root{--dockw:19rem}

.divdock{position:fixed;z-index:200;background:var(--panel);
  box-shadow:0 -6px 24px rgba(0,0,0,.28)}
.divdock[hidden]{display:none}

/* Shared dock chrome, both layouts. Deliberately placed BEFORE the two
   breakpoint blocks below (moved here 2026-08-15, w1541-playerdock; values
   unchanged, position only): at equal selector specificity the LATER rule in
   the file wins whenever its own condition matches, so with this block sitting
   AFTER the media queries as it used to, its plain .dock-media{width:120px...}
   and .dock-volrange{width:7.33em...} were silently outranking the wide
   drawer's own .dock-media{width:100%;aspect-ratio:16/9} and
   .dock-volrange{width:100%} rules further down at every width, narrow bar
   included, not only the narrow bar the pixel sizes are actually for. That is
   what pinned the video to a small fixed thumbnail and the volume slider to a
   fixed narrow width even inside the >=900px drawer, where both were meant to
   fill it. Same cascade lesson the .rangectl comment above already documents
   for this file ("at equal specificity the later rule wins"). Base sizes live
   here now; the narrow block below still owns them for <900px (they are what
   that breakpoint wants) and the wide block after it now correctly wins for
   >=900px because it comes later in the file, not before. */
.dock-media{flex:0 0 auto;width:120px;height:68px;border-radius:8px;overflow:hidden;background:#000}
.dock-media iframe{width:120px;height:68px;border:0;display:block}
.dock-main{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:6px}
.dock-title{color:var(--muted);font-size:.86rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.dock-ctrls{display:flex;align-items:center;gap:8px}
.dock-btn{background:var(--panel2);color:var(--gold-soft);border:1px solid var(--edge2);border-radius:99px;
  width:30px;height:30px;font-size:.82rem;cursor:pointer;line-height:1;display:inline-flex;align-items:center;justify-content:center}
.dock-btn:hover{border-color:var(--gold);color:var(--gold)}
.dock-pp{width:34px;height:34px;font-size:.92rem}
.dock-x{flex:0 0 auto;width:26px;height:26px;font-size:.72rem}

/* Volume: its own row under the transport buttons, sized to span exactly the three
   transport buttons above it - 30 + 34 + 30 with the two 8px .dock-ctrls gaps. Keep
   this in step with .dock-btn / .dock-pp if those sizes ever change. */
.dock-vol{display:flex;align-items:center}
/* Converted from a fixed px box to em, w666-spreadqa 2026-08-11, owed since
   w641-spreadmapfix left it deliberately untouched (that lane's own comment
   in .rangectl above: "the media dock volume, which does not need it"). It
   needed it: a px thumb never grows with the A-/A+ control or OS text
   scaling, so at 150 to 200 percent every other control on the dock grows
   while this one stands still, which is the same inverted-typography defect
   the site's body-copy rule exists to prevent, just on a control instead of
   text. The WCAG 2.5.8 argument for the original 24px (w18-a11y-audit) is a
   FLOOR, not a reason to fix the unit: em only ever grows from that floor as
   text size increases, it never drops below it, so the target-size minimum
   is satisfied at every zoom level rather than only at the default. Every
   value below is that same 24px/4px/6px/-10px geometry re-expressed in em
   against this element's inherited 1em (the site's 15px default, matching
   .dock-btn/.dock-pp beside it), so nothing moves at 100% and everything
   grows together above it. */
.dock-volrange{-webkit-appearance:none;appearance:none;background:transparent;cursor:pointer;
  flex:0 0 auto;width:7.33em;height:1.6em;margin:0}
.dock-volrange::-webkit-slider-runnable-track{height:.27em;border-radius:99px;background:var(--edge2)}
.dock-volrange::-moz-range-track{height:.27em;border-radius:99px;background:var(--edge2)}
/* 1.6em (24px at the default) not the 12px this was until 2026-07-31. WCAG 2.2's
   2.5.8 Target Size (Minimum) asks 24x24 CSS px or larger, and its user-agent
   exception does not apply here because the author IS overriding the native
   thumb. The visible dot is kept small with an inset ring so the control still
   looks like a volume slider: the grab area is the full 1.6em. The track stays
   proportionate and the slider remains keyboard-operable with the arrow keys,
   which is what 2.5.7 wants. w18-a11y-audit, unit converted w666-spreadqa. */
.dock-volrange::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:1.6em;height:1.6em;
  border-radius:99px;background:var(--gold-soft);border:.4em solid var(--panel2);
  background-clip:padding-box;box-sizing:border-box;margin-top:calc((.27em - 1.6em) / 2)}
.dock-volrange::-moz-range-thumb{width:1.6em;height:1.6em;border-radius:99px;
  background:var(--gold-soft);border:.4em solid var(--panel2);background-clip:padding-box;
  box-sizing:border-box}
.dock-volrange:hover::-webkit-slider-thumb{background:var(--gold)}
.dock-volrange:hover::-moz-range-thumb{background:var(--gold)}
.dock-volrange:focus-visible{outline:2px solid var(--gold);outline-offset:3px;border-radius:99px}

/* ---------- narrow: a bottom drawer ----------
   Owner, 2026-07-28: "let's make a bottom a drawer as well so that there's more
   screen space." The bar stood about 140px tall on a phone, which is a quarter of a
   small viewport spent on transport controls the reader is not looking at.

   Collapsed it keeps ONLY what a listener needs at a glance -- the title and the
   transport row -- and drops the artwork and the volume slider, which is what the
   height was going to. It never slides fully away: a player you cannot see is a
   player you cannot pause, and the bottom edge is where a thumb already is.

   The two heights are declared on BODY rather than on the dock, because the pull tab
   is a SIBLING of the dock (it has to be -- see the drawer note above) and a custom
   property cannot travel sideways between siblings. Setting it on the shared ancestor
   is what lets the tab sit exactly on the dock's top edge in both states. Keep the
   two numbers in step with the padding and the media size below. */
@media (max-width:899px){
  body:has(.divdock:not([hidden])){--dockbh:6.6rem}
  body:has(.divdock.dockshut){--dockbh:3.1rem}

  .divdock{left:0;right:0;bottom:0;display:flex;align-items:center;gap:14px;
    padding:8px 14px;border-top:1px solid var(--edge);
    min-height:var(--dockbh);box-sizing:border-box;
    transition:min-height .18s ease,padding .18s ease}
  .divdock.dockshut{padding:4px 12px;gap:10px}
  /* The two things the height was going to. */
  .divdock.dockshut .dock-media,
  .divdock.dockshut .dock-vol{display:none}
  .divdock.dockshut .dock-main{flex-direction:row;align-items:center;gap:10px}
  .divdock.dockshut .dock-title{flex:1 1 auto;min-width:0;font-size:.8rem}
  .divdock.dockshut .dock-ctrls{flex:0 0 auto}

  /* The tab rides the dock's top edge, right-aligned, clear of the close button. */
  .dockhandle{position:fixed;z-index:201;right:12px;bottom:var(--dockbh);
    width:3.4rem;height:1.25rem;display:flex;align-items:center;justify-content:center;
    padding:0;cursor:pointer;color:var(--gold-soft);
    background:var(--panel);border:1px solid var(--edge);border-bottom:0;
    border-radius:8px 8px 0 0;box-shadow:0 -3px 10px rgba(0,0,0,.22);
    transition:bottom .18s ease,color .14s}
  .dockhandle[hidden]{display:none}
  .dockhandle:hover{color:var(--gold)}
  .dockhandle:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  /* The chevron points the way the panel will move: down to collapse, up to open.
     Rotated from the shared right-pointing glyph so one icon serves both layouts. */
  .dockhandle svg{width:13px;height:13px;display:block;transform:rotate(90deg);
    transition:transform .18s ease}
  body:has(.divdock.dockshut) .dockhandle svg{transform:rotate(-90deg)}
}

/* ---------- wide: the drawer ---------- */
@media (min-width:900px){
  .divdock{top:0;right:0;bottom:0;width:var(--dockw);
    display:flex;flex-direction:column;align-items:stretch;gap:14px;
    padding:18px 16px 20px;border-left:1px solid var(--edge);
    box-shadow:-6px 0 24px rgba(0,0,0,.28);
    transform:translateX(0);transition:transform .2s ease}
  .divdock.dockshut{transform:translateX(100%)}

  /* The artwork becomes the head of the drawer at full width. aspect-ratio rather
     than a fixed height so it stays 16:9 at every drawer width and text size. */
  .dock-media{width:100%;height:auto;aspect-ratio:16/9;flex:0 0 auto}
  .dock-media iframe{width:100%;height:100%}

  .dock-main{flex:0 0 auto;gap:14px}
  /* Sideways there is room for the whole title over two lines, where the bar had to
     ellipsise it to one. Clamped so a long album name cannot push the controls off. */
  .dock-title{white-space:normal;overflow:hidden;text-overflow:clip;text-align:center;
    display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;line-height:1.35}
  .dock-ctrls{justify-content:center}
  .dock-vol{width:100%}
  .dock-volrange{width:100%}
  /* Close sits in the corner rather than in the flow, so the transport row stays
     centred on the drawer's axis. */
  .dock-x{position:absolute;top:10px;right:10px}

  /* THE PAGE MAKES ROOM. padding on BODY, so in the app shell the header, the stage
     and the footer all shrink together and the drawer never overlaps the reading
     column; on an ordinary scrolling page it does the same to the whole document.
     Only when the drawer is actually open -- shut, the page takes the width back. */
  body:has(.divdock:not([hidden]):not(.dockshut)){padding-right:var(--dockw)}

  /* The pull tab. A sibling of the drawer, NOT a child: a child would be carried off
     screen by the same transform that hides the drawer, and there would be no way
     back. It tracks the drawer's edge instead. */
  .dockhandle{position:fixed;z-index:201;top:50%;right:var(--dockw);transform:translateY(-50%);
    width:1.6rem;height:4.5rem;display:flex;align-items:center;justify-content:center;
    padding:0;cursor:pointer;color:var(--gold-soft);
    background:var(--panel);border:1px solid var(--edge);border-right:0;
    border-radius:10px 0 0 10px;box-shadow:-4px 0 14px rgba(0,0,0,.22);
    transition:right .2s ease,color .14s}
  .dockhandle[hidden]{display:none}
  .dockhandle:hover{color:var(--gold)}
  .dockhandle:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
  .dockhandle svg{width:14px;height:14px;display:block;transition:transform .2s ease}
  body:has(.divdock.dockshut) .dockhandle{right:0}
  body:has(.divdock.dockshut) .dockhandle svg{transform:rotate(180deg)}
}

@media (prefers-reduced-motion: reduce){
  .divdock,.dockhandle,.dockhandle svg{transition:none}
}
/* app-shell: a page rendered inside the persistent shell frame hides its own
   chrome (the shell provides the header + dock) */
html.framed header.top{display:none}
html.framed #divdock{display:none}
html.framed #appmain{padding-bottom:0}

/* "Play album" trigger button (Listen cards + album header) */
.ac-play{display:inline-flex;align-items:center;gap:8px;background:var(--gold-bg);color:var(--gold-bg-ink, #1a1508);border:none;border-radius:10px;
  padding:11px 18px;font-weight:650;font-size:.92rem;cursor:pointer;font-family:var(--sans)}
.ac-play:hover{background:var(--gold-soft)}
/* Keep page content clear of the dock when it is showing. REM, not px: the dock's
   height is driven by text that scales, and a fixed 108px reservation left the last
   lines of every page hidden under the dock at large text sizes.

   NARROW ONLY, since 2026-07-27. Above 900px the dock is a right-hand drawer and the
   room it needs is made with padding-RIGHT on body (see the drawer block above).
   Leaving these vertical reservations in place at every width is what produced the
   dead band that was reported: in the app shell the layout is a fixed-height flex
   column that does not scroll, so 8rem on #appmain AND another 9rem on the footer are
   not scroll-clearance at all -- they are just empty space, reserved twice, for a bar
   that is no longer at the bottom. */
@media (max-width:899px){
  /* ONE reservation, on the LAST element only. It used to be here AND on .sitefoot
     below, 8rem plus 9rem, and since the footer follows #appmain the first one was
     never clearance at all -- it was a 128px black band between the content and the
     footer, which is the bar the owner photographed on 2026-07-28. The footer is the
     bottom of the document, so reserving there is the only reservation that does
     anything. */
  body:has(.divdock:not([hidden])) #appmain{padding-bottom:0}
}
@media (max-width:560px){ .dock-media{width:84px;height:48px} .dock-media iframe{width:84px;height:48px} }

/* ---- the anonymous reading list (js/reading-list.js) ----
   Part of a visitor engagement and shareability pass.

   The banner is inserted as the FIRST child of body by the script, so it sits
   above the header rather than over it: an offer that covers the site's own
   navigation is an offer that reads as an advert. It is in normal flow and pushes
   the page down, which is the honest shape for something that appears once and is
   dismissed.

   Everything is rem/em with no fixed height, and the row wraps, so at 200% text
   the three parts stack instead of the buttons falling off the right. */
.rlbanner{display:flex;flex-wrap:wrap;align-items:center;gap:10px 14px;
  padding:12px 20px;background:var(--panel2);border-bottom:1px solid var(--gold);
  color:var(--ink);font-size:.92rem;line-height:1.5}
.rlbanner span{flex:1 1 16rem;min-width:0}
@media (max-width:560px){ .rlbanner{padding:12px 16px} }

/* The one-time explanation under the save control. Said once per browser, the
   first time anything is saved: a local list the visitor does not know is local
   is a promise the atlas has not actually made. */
.rlnote{margin:8px 0 0;color:var(--faint);font-size:.82rem;line-height:1.5;max-width:var(--measure)}

/* The saved state on the star control, so a locally saved entry looks saved. Uses
   the same gold the starred state uses, because to the visitor it IS the saved
   state; what differs is where it is kept, and the note says that. */
.starbtn[data-readsave][aria-pressed="true"]{color:var(--gold)}

/* ---- site footer ----
   The quiet typographic close to every page: one rule, one row of links, one line of
   fine print. Deliberately plain -- it carries the legal and contact routes that a
   payment processor requires to be reachable from checkout, so it must be legible
   rather than decorative. Capped at the reading measure only for the fine print; the
   link row runs the page width like the header's nav does. */
.sitefoot{border-top:1px solid var(--edge);margin-top:28px;padding:26px 20px 34px}
.sitefootin{max-width:1100px;margin:0 auto;display:flex;flex-wrap:wrap;align-items:baseline;
  gap:10px 22px}
.sitefoot nav{display:flex;flex-wrap:wrap;gap:8px 18px}
.sitefoot a{color:var(--muted);text-decoration:none;font-size:.86rem}
.sitefoot a:hover{color:var(--gold);text-decoration:underline}
.sitefoot .footspacer{flex:1 1 40px}
.sitefoot .footnote{color:var(--faint);font-size:.78rem;line-height:1.6;max-width:var(--measure);
  flex:1 1 100%;margin:14px 0 0}
@media (max-width:560px){ .sitefoot{padding:22px 16px 30px} .sitefoot .footspacer{display:none} }
/* Inside the app shell exactly ONE footer may render, and since 2026-07-31 it is
   THIS one, in the framed page, not the shell's copy.

   It was the other way round until the owner pointed out twice that the footer was
   still stuck to the bottom of the screen. It had to be: the shell is a
   viewport-height flex column with overflow:hidden, so its last child sits on the
   bottom edge by construction, with no scrolling document to move it out of the
   way. The framed page is the thing that scrolls, so the footer belongs to it, and
   App.aspx now hides the shell's own with body>.sitefoot{display:none}.

   :not(.sitefoot) rather than dropping the rule, because the ELEMENT match is still
   doing work (added 2026-07-27 from the owner's screenshot of two stacked footers
   on the framed Forum): Default.aspx and Forum.aspx predate the shared .sitefoot
   and carry their own bare <footer><div class="wrap"><div class="footin">. Those
   stay hidden, along with any future page that hand-rolls one. Only the shared
   footer that Site.master gives every page is allowed through. */
html.framed footer:not(.sitefoot){display:none}
/* THE LAST SECTION OF A FRAMED PAGE HAS TO BE REACHABLE.

   Owner, 2026-07-29, twice: first "leave some space at the bottom", then "the
   challenges section is completely cut off still", and on being asked, that the
   page simply stops scrolling before it arrives, reached from the home page and
   therefore inside the shell.

   Challenges is the LAST element on an entity page, immediately before the
   closing article tag, which is why it is the one that goes missing: anything
   eating the bottom band of a framed page eats that first. Entity pages ARE
   framed, whatever the comment in App.aspx says about them being standalone SEO
   surfaces: SiteNav.IsShellPage lists entity.aspx, the server's answer is
   authoritative in Site.master's bootstrap, and a directly loaded entity URL
   redirects itself into App.aspx.

   Two fixed bars sit over the bottom of that frame on a phone, the music dock at
   about 6.6rem and the menu handle at 2rem, and the shell reserves both around
   the stage. This is deliberately GENEROUS rather than exact anyway, because the
   framed document cannot see --dockbh (it is set in the shell's document, not
   this one) and because the previous two attempts at an exact figure were both
   wrong in the same direction.

   The trade is one-sided: a few centimetres of blank space at the end of a page
   nobody has scrolled to the end of, against a section of the site that cannot
   be reached at all. If this is still short, raise this number rather than
   anything else.

   Narrow only, framed only. A standalone page ends in an ordinary footer and
   needs none of it. */
@media (max-width:899px){
  html.framed body{padding-bottom:6rem}
}
/* and, like #appmain, the footer clears the dock -- narrow only, same reasoning. */
@media (max-width:899px){
  /* Sized from the SAME variable the dock's own height uses, so collapsing the drawer
     gives the page that space back instead of leaving a reserved band under a bar that
     is no longer there. The fallback covers a browser without :has(), where the var
     never gets set and the open height is the safe assumption. */
  body:has(.divdock:not([hidden])) .sitefoot{padding-bottom:calc(var(--dockbh,6.6rem) + .8rem)}
}

/* ============================================================
   A-Z index pages (/systems and /a-z) -- SystemsPage / AtoZPage
   A letter jump-nav over alphabetical sections of links. Uses the
   .dvpage detail shell; only the index-specific pieces live here. */
/* The bar pins BELOW the site header, not at the same offset as it. With top:0 it
   stuck to the identical line the header sticks to, and since the header outranks it
   (z-index 20 against 5) the header simply painted over it: on a narrow viewport,
   where the letters wrap to three rows, the top two rows vanished under the header
   and only "W X Y Z" cleared its bottom edge. --headh is the real measured header
   height, published site-wide by Site.master; 68px is the desktop measurement, kept
   as the no-script fallback. */
.azpage .azjump{position:sticky;top:var(--headh, 68px);z-index:5;display:flex;flex-wrap:wrap;gap:4px;
  padding:10px 0;margin:0 0 14px;background:var(--ground);border-bottom:1px solid var(--edge2)}
.azpage .azjump a{display:inline-block;min-width:1.7em;text-align:center;padding:3px 7px;
  color:var(--muted);text-decoration:none;font-size:.82rem;font-variant-numeric:tabular-nums;
  border:1px solid transparent;border-radius:6px}
.azpage .azjump a:hover{color:var(--gold);border-color:var(--edge2)}
/* The currently loaded letter, w941-azpage: aria-current="page" is set at
   first paint and kept in step across an in-place letter switch by
   js/atoz-island.js (see that file's own header). Same token language as
   the rest of the site's aria-current styling (Types.aspx pager, Quizzes
   filter chips): a filled pill rather than a colour change alone, so the
   state survives without colour. */
.azpage .azjump a[aria-current]{color:var(--gold-bg-ink, #1a1508);background:var(--gold-bg);border-color:var(--gold)}
/* A letter click has to land the heading below BOTH layers of sticky chrome -- the
   header and the jump bar pinned under it -- so the scroll offset is the sum of the
   two measured heights plus a little air, not the flat 60px guess it used to be (which
   was already short of the header alone on desktop, and short by two whole rows of
   letters on a phone). --azjumph is published alongside --headh by Site.master; its
   47px fallback is the desktop one-row measurement, matching --headh's 68px. */
.azpage .azsec{margin:0 0 22px;scroll-margin-top:calc(var(--headh, 68px) + var(--azjumph, 47px) + 12px)}
.azpage .azhd{font-family:var(--serif);font-size:1.3rem;font-weight:600;color:var(--gold);
  margin:0 0 10px;padding:0 0 5px;border-bottom:1px solid var(--edge2)}
.azpage .azhd .azcount{color:var(--faint);font-size:.72rem;font-weight:400;
  font-variant-numeric:tabular-nums;margin-left:6px}
.azpage .azlist{list-style:none;margin:0;padding:0}
.azpage .azlist li{margin:0 0 4px;line-height:1.5;break-inside:avoid}
.azpage .azlist a{color:var(--ink);text-decoration:none}
.azpage .azlist a:hover{color:var(--gold);text-decoration:underline}
.azpage .azlist .azmeta{color:var(--faint);font-size:.78rem}
.azpage .azlist .azmeta::before{content:"\00B7";margin:0 .35em 0 .15em;color:var(--edge2)}
/* The category label links to its browse page but must stay the muted azmeta colour,
   not the default/entry link colour: inherit the span's --faint (theme-aware), no
   underline at rest, a subtle underline only on hover as the link affordance. */
.azpage .azlist .azmeta a,.azpage .azlist .azmeta a:link,.azpage .azlist .azmeta a:visited{color:inherit;text-decoration:none}
.azpage .azlist .azmeta a:hover{color:inherit;text-decoration:underline}
/* The entry index runs to thousands of items; flow them into responsive columns so
   the page reads across rather than as one very long strip. */
.azpage .azcols{column-width:230px;column-gap:34px}
@media (max-width:560px){ .azpage .azcols{column-width:auto;column-count:1} }

/* ---- /compare depth: structured attribute diff + AI synthesis ----
   All colour is a theme token (--gold/--edge/--faint/--ink/--shared/...), so light and
   dark both read correctly with no second copy. No JS: the AI card is a <details>. */
/* the structured attribute table */
/* The tables scroll INSIDE their own wrapper on narrow screens (owner QA
   2026-07-25: on a phone the value columns bled past the card edge). The
   server stamps each table's min-width from its entry count, so columns keep
   a readable width and the wrapper takes the horizontal scroll. */
.dvpage .cmpattrs .cmpgridwrap{overflow-x:auto;-webkit-overflow-scrolling:touch;margin:0 0 4px}
.dvpage .cmpattrs .cmpgrid{width:100%;border-collapse:collapse;margin:0 0 4px;font-size:.9rem}
.dvpage .cmpattrs .cmpgrid th,.dvpage .cmpattrs .cmpgrid td{text-align:left;padding:9px 12px;
  border-bottom:1px solid var(--edge);vertical-align:top}
.dvpage .cmpattrs .cmpgrid tr:last-child th,.dvpage .cmpattrs .cmpgrid tr:last-child td{border-bottom:0}
.dvpage .cmpattrs .cmpgrid thead th{color:var(--gold);font-family:var(--serif);font-weight:600;
  font-size:.95rem;border-bottom:1px solid var(--edge2)}
.dvpage .cmpattrs .cmpgrid thead th.rlab{width:1%}
.dvpage .cmpattrs .cmpgrid th.rlab{color:var(--faint);text-transform:uppercase;letter-spacing:.08em;
  font-size:.66rem;font-weight:600;white-space:nowrap;font-family:var(--sans)}
.dvpage .cmpattrs .cmpgrid td.av{color:var(--ink)}
/* WCAG 1.4.1 Use of Color. Until 2026-07-31 "the two entries agree here" was carried by
   TEAL TEXT ON A TEAL WASH AND NOTHING ELSE, while "they differ" carried an inset gold
   bar. So the single most important reading of the whole comparison page -- which facts
   are shared and which are not -- was available to a colour-blind reader in one of its
   two states only, and in a deuteranope's rendering teal against an off-white ground is
   close to the plain cell it is meant to contrast with. Printed in greyscale it vanished
   entirely.

   The fix keeps the colour and adds a SHAPE that differs between the two states, which
   is what 1.4.1 actually asks: shared cells take a SOLID 3px inset bar, differing cells
   a DASHED one, drawn with a repeating gradient because box-shadow cannot be dashed.
   The two are now told apart by edge treatment at any colour perception, and the gold
   bar the differing cells already had is preserved rather than replaced.

   Deliberately NOT done with a pseudo-element: these are table cells whose text is read
   by the Compare read-aloud path, and generated content is not in the accessibility tree
   -- an ::after tick would have been a purely visual cue that told a screen reader
   nothing. The information a screen reader needs is already in the section headings
   ("In Both...", "Only in..."), which is why this fix is visual only. w18-a11y-audit. */
.dvpage .cmpattrs .cmpgrid td.shared{color:var(--shared);background:var(--shared-bg);
  box-shadow:inset 3px 0 0 var(--shared)}
.dvpage .cmpattrs .cmpgrid td.contrast{color:var(--ink);
  background-image:repeating-linear-gradient(to bottom,
    var(--gold-soft) 0 5px, transparent 5px 10px);
  background-repeat:no-repeat;background-size:3px 100%;background-position:left top}
.dvpage .cmpattrs .cmpgrid .na{color:var(--faint);font-style:italic}
.dvpage .cmpattrs .cmpnote{margin-top:12px;max-width:64ch}
/* the by-system correspondence profile */
.dvpage .corrprofile{margin:14px 0 2px;border-top:1px solid var(--edge);padding-top:12px}
.dvpage .corrprofile .cpcap{color:var(--faint);text-transform:uppercase;letter-spacing:.1em;
  font-size:.64rem;font-weight:600;margin:0 0 8px}
.dvpage .corrprofile .cprow{display:flex;gap:10px;align-items:baseline;padding:5px 0;flex-wrap:wrap;
  border-bottom:1px dotted var(--edge)}
.dvpage .corrprofile .cprow:last-child{border-bottom:0}
.dvpage .corrprofile .cpsys{min-width:150px;flex:0 0 auto;color:var(--ink);font-weight:600;font-size:.85rem}
.dvpage .corrprofile .cpcts{display:flex;gap:6px;flex-wrap:wrap}
.dvpage .corrprofile .cpshared{color:var(--shared);background:var(--shared-bg);border-radius:6px;
  padding:1px 8px;font-size:.78rem;font-variant-numeric:tabular-nums}
.dvpage .corrprofile .cponly{color:var(--muted);background:var(--panel2);border:1px solid var(--edge);
  border-radius:6px;padding:1px 8px;font-size:.78rem;font-variant-numeric:tabular-nums}
/* the AI synthesis card (reuses .sec + .thread summary; only the body needs styling) */
/* The AI card follows the house fold pattern (owner QA 2026-07-25): the +/-
   rides LEFT of the title as a ::before flex item (the thread shell's
   right-side ::after is suppressed), leaving the read-aloud button alone at
   the row's right edge. */
.dvpage .thread.aicompare>summary::after{content:none}
.dvpage .thread.aicompare>summary::before{content:"+";color:var(--gold);font-family:var(--serif);
  font-size:1.05rem;line-height:1;width:1ch;flex:none;transition:.15s}
.dvpage .thread.aicompare[open]>summary::before{content:"\2013"}
.dvpage .aicompare .aibody{color:var(--ink)}
.dvpage .aicompare .aibody h3{font-family:var(--serif);color:var(--gold);font-size:1rem;font-weight:600;margin:16px 0 6px}
.dvpage .aicompare .aibody h3:first-child{margin-top:0}
.dvpage .aicompare .aibody h4{font-family:var(--serif);color:var(--gold-soft);font-size:.92rem;margin:12px 0 5px}
.dvpage .aicompare .aibody ul{margin:6px 0 10px;padding-left:20px}
.dvpage .aicompare .aibody li{margin:3px 0;line-height:1.55}
.dvpage .aicompare .aibody p{margin:0 0 9px;line-height:1.65}
.dvpage .aicompare .ainote{color:var(--muted)}
/* the member reveal affordance */
.dvpage .aicompare .airevealwrap{margin:0}
.dvpage .aicompare a.aireveal{display:inline-flex;align-items:center;gap:8px;background:var(--gold-bg);
  color:var(--gold-bg-ink, #1a1508);border:1px solid var(--gold);border-radius:11px;padding:9px 16px;font-size:.86rem;
  font-weight:650;text-decoration:none;transition:.15s}
.dvpage .aicompare a.aireveal:hover{background:var(--gold-soft);transform:translateY(-1px)}
/* the anonymous locked teaser + registration CTA */
.dvpage .aicompare .ailk{color:var(--ink);line-height:1.65;margin:0 0 13px;max-width:60ch}
.dvpage .aicompare .aicta{display:flex;align-items:center;gap:15px;flex-wrap:wrap;margin:0}
.dvpage .aicompare .aicta a.cta{display:inline-flex;align-items:center;background:var(--gold-bg);
  color:var(--gold-bg-ink, #1a1508);border:1px solid var(--gold);border-radius:11px;padding:9px 16px;font-size:.86rem;
  font-weight:650;text-decoration:none;transition:.15s}
.dvpage .aicompare .aicta a.cta:hover{background:var(--gold-soft);transform:translateY(-1px)}
.dvpage .aicompare .aicta a.aisignin{color:var(--gold);text-decoration:none;font-size:.85rem}
.dvpage .aicompare .aicta a.aisignin:hover{text-decoration:underline}
@media (max-width:560px){
  .dvpage .cmpattrs .cmpgrid th,.dvpage .cmpattrs .cmpgrid td{padding:7px 8px;font-size:.82rem}
  .dvpage .corrprofile .cpsys{min-width:0;flex:1 0 100%}
}

/* ---- A-Z: unpin the letter bar on phones, and give the reader a way back ----
   Correctly pinned is not the same as usable. At 390px the header plus a
   three-row letter bar is ~149px of permanently occupied screen, and ~190px at
   320px -- roughly a quarter of a phone viewport before a single entry shows.
   Below this breakpoint the bar scrolls away with the page and .aztop carries
   the reader back to it. The bar keeps its --headh offset above the breakpoint,
   where it costs one row. */
@media (max-width:560px){
  .azpage .azjump{position:static;top:auto}
}
.aztop{position:fixed;right:16px;bottom:16px;z-index:30;
  padding:10px 14px;border-radius:999px;cursor:pointer;
  font:inherit;font-size:.82rem;line-height:1;
  color:var(--ink);background:color-mix(in srgb,var(--ground) 92%,transparent);
  border:1px solid var(--edge);backdrop-filter:blur(10px);
  box-shadow:0 2px 10px rgba(0,0,0,.28)}
.aztop:hover{color:var(--gold);border-color:var(--edge2)}
/* ---- and it has to be REACHABLE, which at bottom:16px it was not ----
   Owner, 2026-07-29: "the back to top on the a-z page is not reachable."

   Two things sit at the bottom edge of a phone screen and this button had
   clearance for neither:

     the app shell's menu handle, added today, which paints above the whole
     iframe from the shell's own document, so a z-index inside the framed page
     cannot get out from under it;
     the music dock, which is a fixed bottom bar about 6.6rem tall on a narrow
     screen and PREDATES all of this. With music playing, a 16px offset put the
     button entirely behind it. That one has been reachable-by-accident since the
     dock shipped.

   So the offset is composed rather than fixed. Framed pages clear the handle;
   any page clears the dock when it is showing; a page that is both clears both.
   The dock's height comes from the same variable the dock itself uses, so
   collapsing it gives the space back instead of stranding the button in mid-air.

   Narrow only. Above 900px the dock is a right-hand drawer and there is no
   handle, so the original corner offset is right and is left alone. */
@media (max-width:899px){
  .aztop{bottom:20px}
  html.framed .aztop{bottom:calc(2.5rem + 16px)}
  body:has(.divdock:not([hidden])) .aztop{bottom:calc(var(--dockbh,6.6rem) + 16px)}
  html.framed body:has(.divdock:not([hidden])) .aztop{bottom:calc(var(--dockbh,6.6rem) + 2.5rem + 16px)}
}
/* [hidden] needs the !important because .aztop sets display via position/padding
   and a bare attribute selector loses to the class on specificity. */
.aztop[hidden]{display:none !important}
@media (prefers-reduced-motion: reduce){
  .aztop{transition:none}
}


/* ---- Save To Collection (spec section 9.2) --------------------------------
   MOVED HERE FROM Entity.aspx ON 2026-07-29. The control is on entity pages AND
   category pages, and this CSS was inline in one of them, which is exactly how
   .followbtn ended up unstyled on three surfaces. A control that more than one
   page renders has its styles in the theme. */
/* Save To Collection. A native <details> beside the star, so it opens with no
   script and a keyboard reaches it for free. The panel is absolutely positioned
   so it does not push the header around when it opens, and it is capped in
   height with its own scroller: a member with thirty collections must not get a
   panel taller than the viewport with no way to reach the bottom. */
/* The picker is one icon button in the .entacts row, so it lays out as a flex
   item like its neighbours rather than as an inline-block that carries its own
   text baseline gap. flex:none stops it being squeezed narrower than its box. */
.savecol{position:relative;display:inline-flex;flex:none}
.savecol > summary{cursor:pointer;list-style:none}
/* The same icon token the rest of the row uses. This rule ties with the
   [data-pageacts] .readbtn svg rule on specificity and wins on order, so leaving
   it at a fixed 18px would have held ONE control's icon still while its five
   neighbours grew with the reader's text size. */
.savecol > summary svg{width:var(--actico);height:var(--actico);display:block}
.savecol > summary::-webkit-details-marker{display:none}
.savecolpanel{position:absolute;right:0;top:calc(100% + 6px);z-index:40;min-width:14rem;max-height:60vh;overflow-y:auto;
              background:var(--panel);border:1px solid var(--edge2);border-radius:12px;box-shadow:var(--shadow);
              padding:8px;display:flex;flex-direction:column;gap:4px}
/* ON A PHONE IT IS PINNED TO THE VIEWPORT, NOT TO THE BUTTON. right:0 anchors the
   panel's right edge to the control, which was fine while this sat at the end of
   the row and is not now that it sits second: the panel hung off the LEFT of the
   screen, where html{overflow-x:hidden} clips it away with no scrollbar, so most
   of it simply did not exist.
   position:fixed with both edges set makes the panel exactly as wide as the
   screen allows wherever its button happens to be, which is the only version that
   cannot be pushed off an edge by a later change to the row. */
/* A BOTTOM SHEET ON A PHONE, anchored to the bottom edge rather than to the
   button. Anchoring to the top left the panel's position depending on where the
   control happened to sit, and a panel taller than the gap under it had its first
   line cut off by its own top edge. bottom:12px cannot do that: the sheet is
   always fully on screen whatever is above it and whatever the row holds. */
@media(max-width:560px){
  .savecolpanel{position:fixed;left:12px;right:12px;bottom:12px;top:auto;min-width:0;max-height:min(60vh,26rem)}
}
/* THE WAY OUT. A <details> closes by pressing its summary again, which is a 40px
   icon in a row the sheet may be covering, so in practice there was no way to
   close or cancel this. The header is sticky inside the
   scrolling panel so the close stays reachable however far down the member has
   scrolled a long list of collections. */
.savecolhd{display:flex;align-items:center;justify-content:space-between;gap:10px;
  position:sticky;top:0;z-index:1;background:var(--panel);padding:2px 4px 8px;margin:0 0 4px;
  border-bottom:1px solid var(--edge)}
.savecolhd .t{font-size:.78rem;text-transform:uppercase;letter-spacing:.12em;color:var(--faint)}
.savecolclose{background:none;border:1px solid var(--edge2);color:var(--muted);border-radius:8px;
  padding:5px 10px;font:inherit;font-size:.82rem;cursor:pointer;flex:none}
.savecolclose:hover{color:var(--ink);border-color:var(--gold)}
.savecolclose:focus-visible{outline:2px solid var(--gold);outline-offset:2px}
.savecolrow{margin:0}
.savecolbtn{display:block;width:100%;text-align:left;background:none;border:1px solid transparent;color:var(--muted);
            border-radius:8px;padding:7px 10px;font-size:.9rem;font-family:var(--sans);cursor:pointer;overflow-wrap:anywhere}
.savecolbtn:hover{color:var(--ink);border-color:var(--edge)}
.savecolbtn.on{color:var(--gold)}
.savecolempty{margin:2px 10px 8px;color:var(--faint);font-size:.82rem;line-height:1.45}
.savecolnew{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin:4px 0 0;padding:8px 10px 0;border-top:1px solid var(--edge)}
.savecollabel{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
.savecolnew input[type=text]{flex:1 1 8rem;min-width:0;background:var(--ground);border:1px solid var(--edge2);
  color:var(--ink);border-radius:8px;padding:7px 9px;font-size:.9rem;font-family:var(--sans)}
.savecolnew input[type=text]:focus{outline:2px solid var(--gold);border-color:var(--gold)}
.savecoladd{width:auto;flex:none;border-color:var(--edge)}
.savecolmanage{display:block;padding:7px 10px;font-size:.82rem;color:var(--faint);text-decoration:none;border-top:1px solid var(--edge);margin-top:4px}
.savecolmanage:hover{color:var(--ink)}

/* CONTENT RIGHTS NOTICE. The atlas saying, in the place
   where a picture or a passage would be, that something is deliberately withheld and
   why. It sits at the head of the lede, before the description, so a reader meets the
   restriction before the prose that respects it.

   Body copy in rem and padding in em so the whole block grows with the A-/A+ control
   rather than trapping text in a fixed box; no nowrap and no fixed height anywhere,
   and it reads as one flowing paragraph stack at 200%. The rule is a left border and
   a muted tone rather than a warning colour: this is not an error, it is the site
   keeping a promise, and colour is not the only thing carrying that (role="note" and
   the aria-label do it for a screen reader). */
.rights-notice{border-left:3px solid var(--edge2);background:var(--panel);
  border-radius:0 8px 8px 0;padding:.7em 1em;margin:0 0 1em;max-width:var(--measure)}
.rights-notice .rights-gated{margin:0;color:var(--muted);font-size:.92rem;line-height:1.6}
.rights-notice .rights-gated+.rights-gated{margin-top:.7em}
.dvpage .entlede .rights-notice{max-width:none}

/* ENTITY STANDING NOTICE. The page-top disclaimer for an
   entity the atlas cannot verify: somebody named reports this and we have not been
   able to check them. Sits above the prose it qualifies, for the same reason the
   rights notice does, so a reader meets the caveat before the claim.

   Same construction rules as .rights-notice above, and for the same reasons: body
   copy in rem, padding and icon in em so the whole block grows with the A-/A+
   control, no fixed height, no nowrap, no fixed-px box around scaling text. It is a
   flowing paragraph stack and stays one at 200%.

   COLOUR CARRIES NOTHING. The left rule and the muted tone are decoration; the
   sentence states the whole caveat, and role="note" plus the aria-label carry it to a
   screen reader. Deliberately NOT a warning colour: an unverified entry is not an
   error, it is the atlas being honest about what it knows.

   The icon is sized in em so it tracks the text it sits beside; it is aria-hidden in
   the markup, so it says nothing a listener needs. align-items:baseline with the icon
   given flex:0 0 auto and the span left shrinkable keeps the sentence wrapping
   normally at any width instead of the icon squashing to a smear. */
/* THE LEFT RULE IS DASHED WHERE .rights-notice ABOVE IS SOLID (attest-render,
   2026-08-05, when the emitter for this block was finally written). The two can
   appear on one page and, drawn identically, a reader meets two panels in the
   same tint saying two completely different things: one is a PERMISSIONS
   statement, that something is deliberately withheld, and this is a CONFIDENCE
   statement, that what is here is not vouched for.

   Dashed rather than a second colour, deliberately, and it is not the only thing
   telling them apart: the sentences are different prose, the accessible region
   names are different ("Content Restriction" against "Sources Not Verified"),
   and this one carries an icon where the rights notice carries none. The dash
   echoes .xtb-unk and .xt-unk, which already draw the unsettled status as a
   dashed edge, so the page has one visual idiom for "not settled" rather than
   two. It survives a colour-blind reader and a monochrome print, and it still
   carries nothing on its own. */
.standing-notice{border-left:3px dashed var(--edge2);background:var(--panel);
  border-radius:0 8px 8px 0;padding:.7em 1em;margin:0 0 1em;max-width:var(--measure)}
.standing-notice .standing-line{display:flex;gap:.55em;align-items:baseline;
  margin:0;color:var(--muted);font-size:.92rem;line-height:1.6}
.standing-notice .standing-line svg{width:1.05em;height:1.05em;flex:0 0 auto;
  align-self:flex-start;margin-top:.25em}
.standing-notice .standing-line span{min-width:0}
.standing-notice .standing-src{margin:.7em 0 0;color:var(--muted);
  font-size:.92rem;line-height:1.6}
.dvpage .entlede .standing-notice{max-width:none}

/* ---- the same caveat on a LISTING, .standing-mark ----
   The compact counterpart of .standing-notice above, emitted by
   EntityStanding.MarkHtml onto an entry card (.entcard .tags), a search result row and
   the home page's featured cards. One rule for all three: a card, a row and a tile are
   three different boxes but the mark inside them is one thing, and a second rule is how
   the three quietly drift into three different words.

   It is REAL TEXT, so it is announced with the link it sits inside; nothing here is
   carried by colour, and there is no title attribute anywhere near it. Muted rather
   than a warning tone, for the reason the banner gives: an unverified entry is not an
   error.

   SIZED IN em, NOT rem, and this is the one place it matters. The mark sits inside
   cards whose own text is already .78rem to 1.06rem; in em it stays proportionate to
   whatever it is labelling and it still tracks the A-/A+ control, because that control
   moves the root and the card's rem sizes with it. Padding in em for the same reason,
   so the pill grows with its own words rather than clipping them at 200%.

   white-space:normal and overflow-wrap are the 200% insurance: "Sources Not Verified"
   is three long words in a 220px grid track, so it MUST be allowed to break onto a
   second line rather than paint over the card border, which is what the .dvcard .n
   comment records happening to "Denominations" on a phone. max-width:100% keeps it
   inside the track it wraps in. */
.standing-mark{display:inline-block;font-size:.72em;line-height:1.4;
  color:var(--muted);border:1px solid var(--edge2);border-radius:99px;
  padding:.15em .6em;max-width:100%;white-space:normal;overflow-wrap:break-word}
/* The tags row is a flex box, so the mark needs to be shrinkable in it or a long label
   forces the row wider than the card instead of wrapping inside it. */
.dvpage .entcard .tags .standing-mark{min-width:0}

/* The per-article banner (.lfnote) is styled in Entity.aspx's own page-scoped
   block, at `.entity .lfb .lfnote`, and it is done properly: flex row, icon at
   1.15em, min-width:0 on the text, em and rem throughout. A duplicate was briefly added
   here after grepping only App_Themes and css/ and
   concluding it had none. It had. The rules were removed again rather than left
   sitting dead behind a 0,3,0 selector they could never beat. Noted so the next
   person greps the .aspx files too. */
/* ---- a report row's state, its PDF palette, and the share panel -----------
   (account/reports). The report-library block above
   already carries .pklist, .pkitem, .frbtns and .sharebox; these are the pieces
   the first edition wrote as style ATTRIBUTES on the elements and as one
   page-private <style> block. Same declarations, one copy, rem/em throughout so
   they track the reader's text size. Land these next to the .frbtns rules. */

/* A report being written right now. */
.pkitem .pkgen{color:var(--gold);font-weight:600}
/* A report that was PAID FOR and could not be written. It must not read as an
   ordinary purchased row and must not keep saying "generating". The WORDS carry
   the meaning; the colour only reinforces it, because colour alone says nothing
   to a screen reader and nothing to a reader who cannot distinguish it. */
.pkitem .pkfail{color:var(--dark-c);font-weight:600}
/* A shared report whose owner revoked the link: present, named, and plainly not
   actionable. Distinct from .acctpager .pgoff, which is the pager's own. */
.frbtns .pkoff{opacity:.45;pointer-events:none}
/* The PDF palette picker sits inside the Generate PDF control group, so it has
   to match the button beside it rather than the page's full-size inputs. */
.frbtns .pdfstyle{background:var(--ground);border:1px solid var(--edge2);color:var(--ink);
  border-radius:9px;padding:4px 6px;font:inherit;font-size:.75rem;line-height:1.35;margin-right:6px}
/* The Share disclosure. Its summary is already a real disclosure button to a
   keyboard and a screen reader (see the .sharebox rules above); only the cursor
   is missing from them. */
.frbtns .sharebox>summary{cursor:pointer}
/* The panel inside it. min-width rather than a fixed width: it has to hold a
   select and a button, and it has to collapse to the row on a phone, which the
   max-width:640px query at the end of the .frbtns block already enforces on its
   inputs. */
.shrpanel{display:flex;flex-direction:column;gap:4px;margin:8px 0 0;min-width:16rem;max-width:100%}
.shrpanel .shrlabel{font-size:.7rem;text-transform:uppercase;letter-spacing:.12em;color:var(--faint)}
.shrpanel .shrlink{font-size:.75rem;width:100%;box-sizing:border-box}
.shrpanel .shror{margin:6px 0;opacity:.7;font-size:.85rem}
.shrpanel .shrnote{width:100%;max-width:21rem}
.shrpanel .btn{align-self:flex-start;margin-top:4px}

