/* cheesewiki.net — the "Press" theme. Paper, ink, one red, and type doing the shouting.
   Replaced the Dungeons of Blood and Dream tokens on 2026-10-27. DOBAD keeps the old look by
   holding every value below explicitly in its own settings; tools/blackletter-theme.json is that
   theme, and tools/freeze-skin.js is what writes it into a wiki. */

/* The first web fonts this project has ever loaded, and they are served from this host like any
   other asset — `default-src 'self'` allows that and would block a third party, which is the whole
   reason not to use a font CDN. Three files, ~45 KB in total, in public/fonts/.
   `font-display:swap` so a slow font never leaves the page blank: the system fallback paints first
   and is replaced, which matters most on the one page a stranger sees before they trust us. */
@font-face{font-family:Archivo;src:url(/fonts/archivo-400.woff2) format('woff2');font-weight:400;font-style:normal;font-display:swap}
@font-face{font-family:Archivo;src:url(/fonts/archivo-600.woff2) format('woff2');font-weight:600;font-style:normal;font-display:swap}
@font-face{font-family:"Archivo Black";src:url(/fonts/archivo-black.woff2) format('woff2');font-weight:400;font-style:normal;font-display:swap}

:root{
  /* Every stack ends in a real system face, so a wiki whose fonts failed to load is plain rather
     than broken. Arial Black and Impact are the closest ubiquitous weights to Archivo Black. */
  --font-display:"Archivo Black","Arial Black",Impact,system-ui,sans-serif;
  --font-body:Archivo,system-ui,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
  --font-read:Archivo,system-ui,-apple-system,"Segoe UI",Helvetica,Arial,sans-serif;
}
/* The three background images, as tokens rather than literals, so an owner can repoint them without
   anything they upload ever becoming CSS: lib/branding.js writes the file and re-declares the token,
   and the value is always url(/branding/<a name that module chose>) or one of the engine's own
   pictures by name. Kept in a plain :root rule of their own — not inside the theme blocks — so the
   override emitted into the page head after this stylesheet wins on source order without needing a
   heavier selector, and so the art is the same in dark and bone.
   Press has no texture at all: it is paper and ink, and a tile under it would be a different idea. */
:root{
  --tex-page:none;
  --tex-panel:none;
  --tex-auth:none;
  /* Press's footer bar: a solid slab of ink. The catalogue of alternatives is in lib/skin.js. */
  --page-rule:linear-gradient(var(--ink),var(--ink));
  --page-rule-h:6px;
  --title-filter:none;
}
:root,[data-theme=dark]{
  /* Warm near-black, not cool grey: this theme's character is paper, and the dark mode keeps that
     warmth in the ink rather than going clinical. */
  --bg:#131110; --bg2:#1b1816; --panel:#1b1816; --panel2:#241f1c; --ink:#f2efe9; --muted:#a49c92;
  --green:#ff7a5c; --green-d:#c8341f; --moss:#a49c92; --blood:#ff7a5c; --blood-d:#5c241a; --violet:#b79ae0; --stone:#8c867e; --gold:#e0b050;
  --sel:#f2efe9; --sel-ink:#131110;   /* the highlight chip: inverted, which is this theme's one move */
  --link:#ff9b82; --link-v:#c7a6e8; --new:#ff5a3d; --line:#332e2a; --floor:.55;
  /* Transparent rather than `none`: --glow sits in a text-shadow list. */
  --glow-c:#00000000; --glow:0 0 0 rgba(0,0,0,0); --title-drop:rgba(0,0,0,0);
}
[data-theme=light]{ /* paper: the mode this theme was drawn in */
  --bg:#fbfbf9; --bg2:#f1f0ec; --panel:#ffffff; --panel2:#f1f0ec; --ink:#121212; --muted:#5a5a56;
  /* every value here clears 4.5:1 against both --bg and --panel; see the light-mode contrast test */
  --green:#c8341f; --green-d:#9e2816; --moss:#5a5a56; --blood:#b22d18; --blood-d:#e8d3ce; --violet:#5e3a7a; --stone:#6b6b66; --gold:#8a6a14;
  --link:#a8301c; --link-v:#5e3a7a; --new:#c8341f; --line:#d6d6d0; --floor:.1;
  --glow-c:#00000000; --glow:0 0 0 rgba(0,0,0,0); --title-drop:rgba(0,0,0,0);
  --sel:#121212; --sel-ink:#fbfbf9;
}
*{box-sizing:border-box} html{scroll-behavior:smooth}
@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}}   /* a jump to an anchor should not animate for someone who asked it not to */
body{margin:0;color:var(--ink);font:15px/1.6 var(--font-body);background:var(--bg) var(--tex-page);background-blend-mode:normal;image-rendering:pixelated;min-height:100vh;position:relative}
body::before{content:"";position:fixed;inset:0;background:var(--bg);opacity:calc(1 - var(--floor));pointer-events:none;z-index:-1}
/* CRT scanlines. These used to blend with mix-blend-mode:multiply, which made the browser
   re-composite the whole viewport against the scrolling page every frame — 39 dropped frames in
   110 while scrolling. The lines are pure black, and for a black source multiply and ordinary
   compositing are the same equation ((1-a)·backdrop), so plain stacking looks identical: measured
   at a maximum of 1/255 per channel, and zero dropped frames. Do not put the blend mode back. */
body::after{content:"";position:fixed;inset:0;pointer-events:none;background:repeating-linear-gradient(0deg,rgba(0,0,0,.14) 0 1px,transparent 1px 3px);z-index:50}
a{color:var(--link);text-decoration:none;text-shadow:var(--glow)} a:hover{text-decoration:underline wavy var(--blood);text-underline-offset:3px}
a:visited{color:var(--link-v)} a.new{color:var(--new);text-shadow:none} a.ext::after{content:" ↗";font-size:.8em}
.blackletter,h1,h2{font-family:var(--font-display);font-weight:400;letter-spacing:.02em}
/* Every part of a title's treatment is a token. The glow used to be rgba(68,255,30,.35) — one
   wiki's accent, hardcoded — so no amount of recolouring could change it, and the melt filter was
   applied unconditionally. Both are settings now. --glow is a transparent shadow rather than `none`
   when unset, because `none` inside a comma-separated text-shadow list is invalid and kills the
   whole declaration, drop shadow included. */
h1.blackletter{color:var(--green);font-size:2.6rem;margin:.2em 0 .3em;text-shadow:var(--glow),0 3px 0 var(--title-drop);filter:var(--title-filter)}
[data-theme=light] h1.blackletter{text-shadow:var(--glow),0 2px 0 var(--title-drop)}
.wiki h2{color:var(--green);border-bottom:3px double var(--blood);padding-bottom:.1em;font-size:1.9rem} .wiki h3,.wiki h4{color:var(--violet)}
.top{position:sticky;top:0;z-index:20;display:flex;gap:1rem;align-items:center;padding:.5rem 1rem;background:linear-gradient(var(--bg),var(--bg)) ,var(--tex-panel);background-blend-mode:multiply;border-bottom:4px solid #000;box-shadow:0 4px 0 var(--blood-d),0 8px 24px #000a}
.brand{display:flex;align-items:center;gap:.5rem;color:var(--green);font-size:1.7rem;white-space:nowrap}.brand img{height:44px;image-rendering:pixelated;filter:drop-shadow(0 0 6px #7d5fa6)}
.search{flex:1;position:relative;max-width:560px}
input,select,textarea{font:inherit;color:var(--ink);background:var(--bg);border:2px solid var(--line);border-radius:0;padding:.45rem .6rem;width:100%;outline:none}
input:focus,select:focus,textarea:focus{border-color:var(--green);box-shadow:var(--glow)}
#sugg{position:absolute;inset:100% 0 auto 0;background:var(--panel);border:2px solid var(--green-d);z-index:30}#sugg a{display:block;padding:.35rem .6rem;color:var(--ink)}#sugg a:hover,#sugg a.sel{background:var(--sel);color:var(--sel-ink);text-decoration:none}
nav{display:flex;gap:.9rem;align-items:center;margin-left:auto;flex-wrap:wrap}nav form{display:inline}
button.link{background:none;border:0;color:var(--link);font:inherit;cursor:pointer;padding:0}button.link:hover{text-decoration:underline wavy var(--blood)}
.btn{display:inline-block;font:inherit;color:var(--bg);background:var(--green);border:0;padding:.5rem 1.1rem;cursor:pointer;text-transform:uppercase;letter-spacing:.08em;box-shadow:inset -3px -3px 0 var(--green-d),3px 3px 0 #000;clip-path:polygon(0 0,100% 0,100% calc(100% - 6px),calc(100% - 6px) 100%,0 100%)}
.btn:hover{background:var(--blood);color:#fff;text-decoration:none}.btn.ghost{background:var(--panel2);color:var(--green);box-shadow:inset 0 0 0 2px var(--green-d)}.btn.danger{background:var(--blood);color:#fff;margin-left:auto}
.shell{display:grid;grid-template-columns:270px minmax(0,1fr);gap:1rem;max-width:1400px;margin:1rem auto;padding:0 1rem}
.side{align-self:start;position:sticky;top:calc(var(--top-h, 64px) + 8px);max-height:calc(100vh - 90px);overflow:auto;background:var(--panel) var(--tex-panel);background-blend-mode:multiply;border:3px solid #000;outline:2px solid var(--moss);padding:.6rem .7rem}
.side-h{font-family:var(--font-display);font-size:1.4rem;color:var(--blood);border-bottom:2px dotted var(--blood-d);margin-bottom:.4rem}
.tree,.tree ul{list-style:none;margin:0;padding-left:.9rem}.tree{padding:0}.tree li{margin:.1rem 0;font-size:.88rem}.tree summary{cursor:pointer}.tree summary::marker{color:var(--blood)}
.tree a.cur{background:var(--sel);color:var(--sel-ink);padding:0 .3rem;text-shadow:none}.side-links{font-size:.8rem;color:var(--muted)}
main.page{background:var(--panel);border:3px solid #000;outline:2px solid var(--moss);outline-offset:-8px;padding:1.4rem 1.8rem 2rem;min-width:0;box-shadow:0 0 0 5px var(--bg),0 0 0 7px var(--blood-d),0 20px 40px #000a}
/* The rule across the bottom of every page. It was url(/assets/drip.svg) written straight in here:
   a bar with blood running off it, on every page of every wiki on the host, with no setting that
   could reach it. It is a choice now (Appearance → Footer bar); the drip is one of the options and
   belongs to the wiki it came from. */
main.page::after{content:"";display:block;height:var(--page-rule-h);margin-top:2rem;background:var(--page-rule)}
article.wiki{font-family:var(--font-read);font-size:1.02rem}.wiki img{max-width:100%;height:auto;image-rendering:pixelated}
/* wiki images appear outside article.wiki too (the upload page gallery), so the size cap rides on the class */
.wimg{max-width:100%;height:auto;image-rendering:pixelated}
.tabs{display:flex;gap:.2rem;border-bottom:2px solid var(--line);margin-bottom:.5rem}.tabs a{padding:.2rem .9rem;color:var(--muted)}.tabs a.on{background:var(--sel);color:var(--sel-ink);text-shadow:none}
.crumbs{font-size:.8rem;color:var(--muted)}.muted{color:var(--muted)}.lock{font-size:1.2rem}
.notice{background:var(--blood-d);border-left:6px solid var(--blood);padding:.6rem .9rem;margin:.6rem 0;color:#fff}[data-theme=light] .notice{color:var(--ink)}.notice.err{border-color:#ff5a3c}
.toc{float:right;margin:0 0 1rem 1rem;background:var(--panel2);border:2px dashed var(--line);padding:.5rem 1rem;font-size:.82rem;max-width:260px}.toc ol{margin:.3rem 0;padding-left:1.1rem}.toc .l3{margin-left:.8rem}
.infobox{float:right;clear:right;width:260px;margin:0 0 1rem 1.2rem;background:var(--panel2);border:3px solid #000;outline:2px solid var(--moss);font-family:var(--font-body);font-size:.85rem}
.ib-title{background:var(--blood-d);color:#fff;text-align:center;font-family:var(--font-display);font-size:1.4rem;padding:.2rem}[data-theme=light] .ib-title{color:var(--ink)}.ib-img{text-align:center;padding:.5rem}.ib-cap{text-align:center;font-style:italic;color:var(--muted);padding:0 .5rem .5rem}
.infobox dl{display:grid;grid-template-columns:auto 1fr;margin:0}.infobox dt{color:var(--violet);text-transform:capitalize;padding:.2rem .5rem;border-top:1px solid var(--line)}.infobox dd{margin:0;padding:.2rem .5rem;border-top:1px solid var(--line)}
/* Every table lives in one of these. A table is the one element that refuses to be narrower than its
   content, so without a scroll container a long cell — an IPv6 address in the audit log was the one
   that found this — pushes the table straight out through the page's own border. max-width keeps the
   box inside the column; overflow-x gives the table somewhere to go instead of somewhere to escape. */
.tablewrap{max-width:100%;overflow-x:auto;margin:1rem 0;overscroll-behavior-x:contain;
  /* Scroll shadows. Two pairs of gradients: the `local` ones are painted with the content and so
     slide away at each end, and the `scroll` ones are fixed to the box — so a shadow appears only on
     the side there is still something to scroll towards. Without this a clipped table reads as a
     rendering fault rather than as something you can drag. */
  background:
    linear-gradient(to right,var(--panel) 40%,transparent) left center/32px 100% no-repeat local,
    linear-gradient(to left,var(--panel) 40%,transparent) right center/32px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%,#000a,transparent) left center/12px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%,#000a,transparent) right center/12px 100% no-repeat scroll}
.wikitable{border-collapse:collapse;min-width:50%}
.wikitable th,.wikitable td{border:2px solid var(--line);padding:.35rem .7rem}
/* break-word, NOT anywhere. Both break a long unbreakable value like an IPv6 address, but `anywhere`
   also tells the layout the column may be as narrow as one character — so ordinary words get chopped
   ("Keep er", "user.cr eate") to buy width the table did not need. break-word leaves the column its
   natural minimum and only breaks what genuinely cannot fit. */
.wikitable td{overflow-wrap:break-word}
.wikitable th{background:var(--panel2);color:var(--green);font-family:var(--font-body);white-space:nowrap}
.wikitable caption{color:var(--violet)}

/* A long table is a box you scroll, not a page you scroll. ~15 rows, but never more than most of
   the viewport: 15 rows is roughly 36rem, which on a laptop is the whole screen, and a table that
   fills the screen has not been capped at all. */
.tablewrap{max-height:min(36rem,70vh);overflow-y:auto}
/* The header stays. border-collapse:collapse and position:sticky do not cooperate — a collapsed
   border belongs to the TABLE's border grid rather than to the cell, so it scrolls away with the
   rows and leaves the header sitting unlined on top of them. The box-shadow redraws that one edge
   on the cell itself, where sticky can carry it. The background is what stops rows showing through,
   and it was already there. */
.wikitable th{position:sticky;top:0;z-index:2;box-shadow:0 2px 0 var(--line)}

/* The filter above a table. Added by app.js only when there are enough rows to be worth filtering,
   so this styles something that is frequently absent. */
.tfilter{display:flex;align-items:baseline;gap:.6rem;flex-wrap:wrap;margin:1rem 0 -.6rem}
.tfilter input{min-width:16rem;max-width:100%}
.tfilter .count{color:var(--muted);font-size:.85em}
.tfilter .count.none{color:var(--blood)}
/* `hidden` on a <tr> is honoured by browsers, but both `tr{display:table-row}` and
   `[hidden]{display:none}` come from the UA sheet, which is a thinner guarantee than it looks.
   Said once here so a filtered row is gone rather than nearly gone. */
.wikitable tr[hidden]{display:none}
/* An unframed [[File:…]] is inline-block, not block. A block box with an explicit width ignores the
   text-align it inherits — only its contents are centred — so a sized icon in a centred table cell sat
   flush left while the label under it centred properly. Inline-block shrink-wraps the picture and then
   obeys the alignment around it, which is also what MediaWiki does with an unframed image. .file.center
   stays a block because margin:auto needs one. */
.file{display:inline-block;vertical-align:top;margin:.4rem 0;max-width:100%}.file.right{float:right;margin:0 0 .8rem 1rem}.file.left{float:left;margin:0 1rem .8rem 0}.file.center{display:block;margin:.8rem auto;text-align:center}.cap{display:block;font-size:.8rem;color:var(--muted);font-style:italic}
/* A picture that was taken off the wiki. Deliberately looks like a gap rather than like a picture:
   the dashed outline reads as "something is meant to be here" to an editor, and the words say why
   to a reader. The old behaviour was a broken-image icon, which told both of them the wrong thing. */
.file-gone .gone-box{display:flex;align-items:center;justify-content:center;min-width:8rem;min-height:5rem;padding:1rem;
  border:1px dashed var(--line);border-radius:3px;background:var(--panel);color:var(--muted);font-size:.8rem;font-style:italic;text-align:center}
/* The file description page. */
.filepage{display:flex;flex-wrap:wrap;gap:1.2rem;align-items:flex-start;margin:.8rem 0}
.filebig{max-width:min(420px,100%);height:auto;border:1px solid var(--line);border-radius:3px}
.file-held{display:flex;flex-direction:column;gap:.35rem;min-width:min(420px,100%);padding:1.6rem;
  border:1px dashed var(--line);border-radius:3px;background:var(--panel);text-align:center}
.filemeta{margin:0;min-width:14rem;flex:1}
.filemeta dt{font-size:.78rem;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);margin-top:.6rem}
.filemeta dd{margin:.1rem 0 0}
.file-acts{margin-top:1.2rem;gap:.5rem;display:flex;flex-wrap:wrap}
/* The cropper. Sits over everything like the zoom viewer, and is dismissed the same ways. */
.crop{position:fixed;inset:0;z-index:60;background:rgba(0,0,0,.82);display:flex;align-items:center;justify-content:center;padding:1rem}
.crop-box{display:flex;flex-direction:column;gap:.6rem;max-width:min(96vw,1100px);max-height:92vh;
  background:var(--panel);border:1px solid var(--line);border-radius:4px;padding:.8rem}
.crop-head{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}
.crop-head .crop-dim{margin-right:auto;font-variant-numeric:tabular-nums}
.crop-stage{position:relative;flex:1;min-height:0;display:flex;align-items:center;justify-content:center;
  overflow:hidden;background:var(--bg2);border:1px solid var(--line);border-radius:3px}
.crop-cv{display:block;max-width:100%;max-height:100%;touch-action:none}
/* The selection is positioned against .crop-area, which wraps the canvas exactly. */
.crop-area{position:relative;line-height:0;max-width:100%;max-height:100%}
.crop-sel{position:absolute;box-sizing:border-box;border:1px solid #fff;cursor:move;touch-action:none;
  box-shadow:0 0 0 100vmax rgba(0,0,0,.55);outline:1px solid rgba(0,0,0,.6)}
.crop-sel i{position:absolute;width:14px;height:14px;background:#fff;border:1px solid #000;border-radius:2px}
.crop-sel i[data-h=nw]{left:-7px;top:-7px;cursor:nwse-resize}
.crop-sel i[data-h=ne]{right:-7px;top:-7px;cursor:nesw-resize}
.crop-sel i[data-h=sw]{left:-7px;bottom:-7px;cursor:nesw-resize}
.crop-sel i[data-h=se]{right:-7px;bottom:-7px;cursor:nwse-resize}
/* The image picker in the editor. */
.pick{position:fixed;inset:0;z-index:60;background:rgba(0,0,0,.82);display:flex;align-items:center;justify-content:center;padding:1rem}
.pick-box{display:flex;flex-direction:column;gap:.6rem;width:min(96vw,1000px);max-height:90vh;
  background:var(--panel);border:1px solid var(--line);border-radius:4px;padding:.8rem}
.pick-head{display:flex;align-items:center;gap:.6rem}
.pick-head .pick-q{flex:1;min-width:0}
.pick-x{flex:none}
.pick-grid{list-style:none;margin:0;padding:.2rem;display:grid;gap:.6rem;overflow:auto;
  grid-template-columns:repeat(auto-fill,minmax(130px,1fr))}
.pick-item button{display:flex;flex-direction:column;gap:.25rem;width:100%;padding:.35rem;text-align:left;
  background:var(--bg2);border:1px solid var(--line);border-radius:3px;color:inherit;cursor:pointer;font:inherit}
.pick-item button:hover,.pick-item button:focus-visible{border-color:var(--link);outline:none}
.pick-item img{width:100%;height:92px;object-fit:contain;background:var(--bg);border-radius:2px}
.pick-name{font-size:.78rem;word-break:break-word;line-height:1.2}
.pick-item small{font-size:.7rem;line-height:1.2}
/* The delete-a-wiki confirmation: the exact name to type, set apart so it is read rather than skimmed. */
.confirm-target{font-family:var(--font-display);font-size:1.5rem;margin:.4rem 0 .8rem;padding:.5rem .8rem;
  border:2px dashed var(--line);border-radius:3px;background:var(--bg2);user-select:all}
.nopaste-note{display:block;margin-top:.35rem;color:var(--blood)}
.danger-card{border-color:var(--blood)}
/* Numbered page navigation (the upload gallery). */
.pages{display:flex;flex-wrap:wrap;gap:.45rem;align-items:center;margin:.6rem 0;font-variant-numeric:tabular-nums}
.pages a,.pages b{min-width:1.9rem;padding:.15rem .4rem;text-align:center;border:1px solid var(--line);border-radius:3px}
.pages b{background:var(--panel2);border-color:var(--link)}
.pages a:hover{border-color:var(--link)}
.gallery{list-style:none;display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:.8rem;padding:0}
/* Attribution for an imported article. Quiet, but present on the page itself rather than tucked away on
   a credits page — it is the condition the text is licensed under, not a footnote. `clear:both` keeps it
   below any floated image or infobox instead of tucking itself alongside one. */
.srcline{clear:both;margin:1.4rem 0 0;padding-top:.6rem;border-top:1px dashed var(--line);font-size:.78rem;color:var(--muted);line-height:1.5}
/* Moderation bot: the unread tally, and one card per report. The evidence block keeps the offending
   text in a monospace well rather than letting it render as part of the page, so an admin reading a
   report is reading a quotation and not being shouted at by the layout. */
.tally{display:inline-block;min-width:1.15rem;padding:0 .3rem;border-radius:.7rem;background:var(--blood);color:#fff;font-size:.72rem;line-height:1.15rem;text-align:center;vertical-align:text-top}
.rep{border:2px solid var(--line);border-left:6px solid var(--moss);background:var(--panel2);padding:.6rem .9rem;margin:.7rem 0}
.rep-ban{border-left-color:var(--blood)}
.rep-new{box-shadow:0 0 0 1px var(--green) inset}
.rep-head{display:flex;flex-wrap:wrap;gap:.5rem;align-items:baseline;margin-bottom:.3rem}
.rep-ev{white-space:pre-wrap;overflow-wrap:anywhere;background:var(--panel);border:1px dashed var(--line);padding:.4rem .6rem;margin:.4rem 0;font-size:.82rem;max-height:11rem;overflow:auto}
.rep .row{gap:.5rem;align-items:center}
/* The bin sits hard right of the report's title line and stays quiet until you reach for it. */
.rep-head .inline{margin-left:auto}
.trash{background:none;border:1px solid transparent;color:var(--muted);cursor:pointer;padding:.15rem .3rem;line-height:0;border-radius:3px}
.trash:hover,.trash:focus-visible{color:#fff;background:var(--blood);border-color:var(--blood)}
[data-theme=light] .trash:hover,[data-theme=light] .trash:focus-visible{color:#fff}
blockquote{border-left:5px solid var(--blood);margin:1rem 0;padding:.3rem 1rem;background:linear-gradient(90deg,var(--blood-d),transparent);font-style:italic}
.sub{clear:both;margin-top:1.5rem}.sub h3{font-family:var(--font-display);font-size:1.5rem;color:var(--blood);margin:.3rem 0}.cols{columns:3 170px;list-style:square;margin:0;padding-left:1.2rem}.cols li::marker{color:var(--blood)}
.card.auth{max-width:420px;margin:2rem auto;background:linear-gradient(rgba(4,6,4,.78),rgba(4,6,4,.92)),var(--tex-auth) center/cover;border:3px solid #000;outline:2px solid var(--blood);padding:1.2rem 1.6rem}
/* The auth card keeps its dark background in BOTH themes — that is the look, and it is the one
   card that never goes pale. So the text inside has to keep the dark theme's colours too: left to
   inherit, light mode paints --ink #14180f and --muted #4e5544 onto a near-black panel and the
   labels, the hints and the "already have one?" line all but disappear. Re-declaring the tokens on
   the card itself means everything inside it, now and later, is covered without hunting selectors. */
[data-theme=light] .card.auth{--ink:#c9d6b8;--muted:#7f8c70;--violet:#8f72bd;--stone:#8c8c96;color:var(--ink)}
/* Confirm-your-email, in both its forms: the page sign-up lands on, and the wall an unconfirmed
   account meets when it tries to take part. Wider than a sign-in form because both are prose rather
   than fields, and left-aligned for the same reason. .wall-go is the way out of either — set off by
   a rule so the reading and the leaving do not run together. */
.card.auth.wall{max-width:560px;text-align:left}
.card.auth .wall-go{margin-top:1.1rem;padding-top:.9rem;border-top:1px solid var(--blood-d)}
/* The reminder that rides along on every wiki page until the address is confirmed. Quieter than a
   site notice: it is a standing condition, not news, and it is on screen for days. */
.notice.verify{border-left:3px solid var(--violet)}
label{display:block;margin:.7rem 0 .2rem;color:var(--violet);font-size:.85rem}label.chk{display:flex;gap:.5rem;align-items:center}label.chk input{width:auto}small{color:var(--muted)}
.row{display:flex;gap:.7rem;margin-top:1rem}.edit textarea{font-size:.9rem;line-height:1.45;min-height:380px;resize:vertical}form.inline{display:inline}
.results{padding-left:1.2rem}.results mark{background:var(--blood);color:#fff;padding:0 .15rem}.results p{margin:.1rem 0 .8rem;color:var(--muted);font-size:.85rem}
.diff{font-size:.8rem;border:2px solid var(--line);overflow:auto}.dl{white-space:pre-wrap;padding:0 .5rem}.dl.add{background:#10400a}.dl.del{background:#4a0c0c}
.hero{display:block;width:100%;image-rendering:pixelated;border:3px solid #000;outline:2px solid var(--moss)}
footer{text-align:center;color:var(--muted);font-size:.8rem;padding:1rem}.foot-eye{height:22px;vertical-align:middle;filter:grayscale(.3)}
::selection{background:var(--blood);color:#fff}
@media(max-width:860px){.shell{grid-template-columns:1fr}.infobox,.toc{float:none;width:auto;margin:1rem 0}.top{flex-wrap:wrap}.brand span{display:none}}
@media(prefers-reduced-motion:no-preference){.brand img{animation:pulse 4s steps(8) infinite}@keyframes pulse{50%{filter:drop-shadow(0 0 12px #b01818)}}}

/* ---- admin panel ---- */
.tabs.adm{flex-wrap:wrap;margin-bottom:.8rem}
.stats{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:.6rem;margin:.8rem 0}
.stat{background:var(--panel);border:1px solid var(--line);padding:.6rem .8rem;display:flex;flex-direction:column}
.stat b{font-size:1.7rem;line-height:1.1;color:var(--green)}.stat span{color:var(--muted);font-size:.85rem}
.pill{display:inline-block;padding:0 .5rem;font-size:.75rem;letter-spacing:.06em;text-transform:uppercase;border:1px solid currentColor}
.pill.ok{color:var(--green)}.pill.off{color:#fff;background:var(--blood);border-color:var(--blood)}
.pill.muted{color:var(--muted)}   /* a neutral state, not a bad one: "nothing has been changed here" */
.sw{display:flex;justify-content:space-between;align-items:center;gap:1rem;background:var(--panel);border:1px solid var(--line);padding:.7rem .9rem;margin:.5rem 0}
.sw .danger{margin-left:0}
.site-banner{background:var(--panel2);border-left:6px solid var(--gold);padding:.5rem .9rem;margin:0 0 .6rem}
.site-banner.off{border-left-color:var(--blood)}
details.reset{display:inline-block}details.reset summary{cursor:pointer;color:var(--link)}details.reset[open]{display:block;margin-top:.3rem}

.gallery.uploads li a{display:block;background:var(--panel2);border:2px solid var(--green-d);padding:.3rem}
.gallery.uploads li img{display:block;width:100%;height:120px;object-fit:contain}
.uploadbar{margin:.4rem 0}.uploadbar label.inline{display:inline-flex;gap:.5rem;align-items:center}.upl{max-width:34rem}.gallery li code{font-size:.7rem;word-break:break-all}

/* spoilers: blurred until the reader asks for them */
.spoil{position:relative;display:inline-block;border-radius:3px;cursor:pointer;outline-offset:2px;background:var(--panel2);box-decoration-break:clone;-webkit-box-decoration-break:clone}
.spoil>.spoil-in{filter:blur(.37em);opacity:.75;transition:filter .12s ease,opacity .12s ease;user-select:none;pointer-events:none}
.spoil::after{content:"";position:absolute;inset:0;border-bottom:1px dotted var(--muted)}
.spoil:hover>.spoil-in{filter:blur(.3em)}
.spoil:focus-visible{outline:2px solid var(--link)}
.spoil.shown{background:none;cursor:auto}
.spoil.shown>.spoil-in{filter:none;opacity:1;user-select:auto;pointer-events:auto}
.spoil.shown::after{display:none}
@media print{.spoil>.spoil-in{filter:none;opacity:1}.spoil::after{display:none}}


/* Anchors: the header is sticky, so a plain jump parks the heading underneath it. Offset every
   anchor target by the header's real height (app.js measures it into --top-h and keeps it current;
   the fallback covers the moment before that runs, and the case where JS never does). scroll-padding
   on the scroller covers every anchor target, not just headings — do not also add scroll-margin to
   the headings, or the two offsets stack and the target lands twice as far down. */
html{scroll-padding-top:calc(var(--top-h, 64px) + 1rem)}

/* ── Retractable "The Dungeon" ─────────────────────────────────────────────
   Two different retractions, because 270px of fixed column and a phone want
   opposite things. Wide: the grid column collapses to 0 and the article takes
   the space. Narrow: the sidebar leaves the flow entirely and slides in over
   the page as a drawer with a scrim. data-nav lives on <html> and is set by an
   inline script in <head>, so neither state flashes before app.js runs. */
.sr{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
.navtog{background:none;border:2px solid var(--green-d);color:var(--green);font:inherit;font-size:1.1rem;line-height:1;cursor:pointer;padding:.35rem .5rem;flex:none}
.navtog:hover{background:var(--panel2)} .navtog:focus-visible{outline:2px solid var(--green);outline-offset:2px}
.side-x{display:none;float:right;background:none;border:0;color:var(--blood);font:inherit;font-size:1.5rem;line-height:1;cursor:pointer;padding:0 .2rem}
.nav-scrim{display:none}
.shell{transition:grid-template-columns .22s ease,gap .22s ease}
[data-nav=off] .shell{grid-template-columns:0 minmax(0,1fr);gap:0}
[data-nav=off] .side{opacity:0;padding:0;border-width:0;outline-width:0;overflow:hidden;pointer-events:none}
.side{transition:opacity .18s ease}

@media(max-width:860px){
  .side{position:fixed;z-index:30;left:0;top:var(--top-h,64px);width:min(300px,86vw);height:calc(100dvh - var(--top-h, 64px));max-height:none;transform:translateX(-102%);transition:transform .2s ease;box-shadow:6px 0 24px #000c;opacity:1;padding:.6rem .7rem;border-width:3px;outline-width:2px;pointer-events:auto}
  [data-nav=on] .side{transform:none}
  [data-nav=on] .nav-scrim{display:block;position:fixed;inset:0;z-index:29;background:#000a}
  .side-x{display:block}
  .shell{transition:none}
  [data-nav=off] .shell{grid-template-columns:1fr}
}
@media(prefers-reduced-motion:reduce){.shell,.side{transition:none}}

/* ── Click an image to open it big ─────────────────────────────────────────
   Pointer-driven: wheel or +/− zooms about the cursor, drag pans, a plain
   click toggles fit ↔ 2.5×. The scanline overlay sits above this on purpose,
   so a blown-up sprite still looks like it belongs to the rest of the site. */
.wimg{cursor:zoom-in}
.lb{position:fixed;inset:0;z-index:40;display:flex;flex-direction:column;background:#000d;backdrop-filter:blur(2px)}
.lb[hidden]{display:none}
.lb-bar{display:flex;align-items:center;gap:.5rem;padding:.4rem .6rem;background:var(--panel);border-bottom:3px solid #000;flex:none}
.lb-cap{flex:1;min-width:0;color:var(--muted);font-size:.85rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.lb-bar button{background:none;border:2px solid var(--green-d);color:var(--green);font:inherit;line-height:1;min-width:2.1rem;padding:.3rem .45rem;cursor:pointer}
.lb-bar button:hover{background:var(--panel2)} .lb-bar button:focus-visible{outline:2px solid var(--green);outline-offset:2px}
.lb-stage{flex:1;min-height:0;display:grid;place-items:center;overflow:hidden;touch-action:none}
/* The viewer's backdrop. Dark suits most of this wiki's art, but a black sprite or a transparent PNG
   vanishes into it, so the bar carries a switch.
   A translucent lift rather than an opaque pale field: a near-white backdrop separates the silhouette
   beautifully and destroys everything inside it, because the eye adapts to the bright surround and a
   dark sprite flattens to a cut-out. Letting the dark backdrop through at about a third strength lands
   on a mid grey — enough to show the edges of black artwork, not so much that its internal shading is
   drowned out. */
.lb[data-bg=light] .lb-stage{background:rgba(255,255,255,.32)}
.lb[data-bg=light] .lb-stage img{box-shadow:0 0 0 1px #0000002e}
.lb-bar button[data-z=bg][aria-pressed=true]{background:var(--panel2);color:var(--green)}
.lb-bar button[data-z=bg] svg{display:block}
.lb-stage img{max-width:100%;max-height:100%;image-rendering:pixelated;cursor:zoom-in;transform-origin:center;will-change:transform;-webkit-user-select:none;user-select:none;-webkit-user-drag:none}
.lb.zoomed .lb-stage img{cursor:grab} .lb.drag .lb-stage img{cursor:grabbing}
.lb-hint{flex:none;text-align:center;color:var(--muted);font-size:.75rem;padding:.3rem}
@media(max-width:860px){.lb-hint{display:none}}
.lb-bar a.lb-raw{color:var(--green);border:2px solid var(--green-d);padding:.3rem .45rem;font-size:.85rem;flex:none;text-shadow:none}
.lb-bar a.lb-raw:hover{background:var(--panel2);text-decoration:none}

/* ── The page at the edge of the map ───────────────────────────────────────
   Shown when a request never reached a route. Styled from the stylesheet only:
   no sidebar, no session, nothing that could be the thing that just broke. */
.fault{max-width:40rem;margin:clamp(1rem,7vh,5rem) auto;padding:1.6rem 1.4rem 2.2rem;text-align:center;background:var(--panel);border:3px solid #000;outline:2px solid var(--moss);outline-offset:-8px;box-shadow:0 0 0 5px var(--bg),0 0 0 7px var(--blood-d),0 20px 40px #000a}
.fault-art{display:block;margin:0 auto .4rem;width:min(260px,62vw);height:auto}
.fault h1{font-size:clamp(2rem,7vw,3rem);margin:.1em 0 .4em}
.fault-lead{font-family:var(--font-read);font-size:1.05rem;color:var(--ink);margin:0 auto 1rem;max-width:34rem}
.fault-warn{color:var(--blood);font-size:1.15rem;letter-spacing:.04em;margin:0 0 1.4rem}
[data-theme=light] .fault-warn{color:var(--blood)}
.fault-act{display:flex;flex-direction:column;align-items:center;gap:.7rem;margin:0 0 1.4rem}
.fault-code{color:var(--muted);font-size:.78rem;margin:0;border-top:1px dotted var(--line);padding-top:.8rem}
@media(prefers-reduced-motion:no-preference){
  .fault-art{animation:fault-drift 7s ease-in-out infinite}
  @keyframes fault-drift{50%{transform:translateY(5px) rotate(-1.2deg);filter:drop-shadow(0 0 14px #8f72bd66)}}
}

/* ── Bone mode ─────────────────────────────────────────────────────────────
   The scale texture is a dark tile. Multiplied onto a dark panel it reads as a
   faint sheen; multiplied onto a pale one it survives at nearly full strength,
   which is what put loud green scales behind the sidebar text. Screen keeps the
   texture but lets the pale panel win, so it stays a sheen either way. */
[data-theme=light] .top,[data-theme=light] .side{background-blend-mode:screen}
[data-theme=light] .side{background-color:var(--panel)}
[data-theme=light] .wiki h3,[data-theme=light] .wiki h4{color:#4a3375}   /* violet needs a little more weight on paper */

.credit-list{font-size:.85rem}
.lb-by{flex:none;color:var(--muted);font-size:.8rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:40%}
.loosehead{font-size:1.3rem;color:var(--muted);border-bottom:1px dotted var(--line);margin-top:2rem}

/* ── Profiles ──────────────────────────────────────────────────────────────
   An avatar is a stamped seal, not a social-media bubble: hard edges, a double
   border in the panel colours, pixelated so uploaded sprite art stays crisp.
   Anyone without a picture gets their initial in the same frame, so a page of
   members reads as one set rather than a row of gaps. */
.av{display:inline-block;width:96px;height:96px;object-fit:cover;image-rendering:pixelated;background:var(--panel2);border:3px solid #000;outline:2px solid var(--moss);outline-offset:-6px;vertical-align:middle;flex:none}
.av-none{display:inline-flex;align-items:center;justify-content:center;font-family:var(--font-display);font-size:2.6rem;color:var(--blood);line-height:1}
.av-sm{width:26px;height:26px;outline-width:1px;outline-offset:-3px;border-width:2px}.av-sm.av-none{font-size:.9rem}
.av-big{width:132px;height:132px}.av-big.av-none{font-size:3.6rem}
.me{display:inline-flex;align-items:center;gap:.45rem}

.prof-head{display:flex;gap:1.2rem;align-items:flex-start;flex-wrap:wrap;margin-bottom:.6rem}
.prof-id h1{margin:.1em 0 .1em}
.prof-handle{color:var(--muted);font-size:.9rem;margin:0 0 .4rem}
.prof-bio{font-family:var(--font-read);font-size:1.05rem;max-width:42rem;border-left:4px solid var(--blood-d);padding-left:.9rem;margin:.6rem 0 1.2rem}
.badges{display:flex;gap:.4rem;flex-wrap:wrap;margin:.2rem 0}
.badge{display:inline-block;font-size:.7rem;text-transform:uppercase;letter-spacing:.1em;padding:.15rem .5rem;border:2px solid var(--green-d);color:var(--green);background:var(--panel2)}
.badge.role-admin{border-color:var(--blood);color:var(--blood)}
.badge.role-mod{border-color:var(--violet);color:var(--violet)}
.badge.role-viewer{border-color:var(--muted);color:var(--muted)}
.badge-off{border-color:var(--new);color:var(--new)}

.prof-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(8rem,1fr));gap:.6rem;margin:0 0 1.4rem;padding:0}
.prof-stats div{background:var(--panel2);border:2px solid #000;outline:1px solid var(--line);outline-offset:-5px;padding:.6rem .7rem}
.prof-stats dt{font-size:.7rem;text-transform:uppercase;letter-spacing:.1em;color:var(--muted)}
.prof-stats dd{margin:.15rem 0 0;font-family:var(--font-display);font-size:1.8rem;color:var(--green);line-height:1}

.groups{list-style:none;padding:0;margin:0 0 1rem}
.groups li{display:flex;gap:.6rem;align-items:center;flex-wrap:wrap;background:var(--panel2);border-left:4px solid var(--moss);padding:.5rem .7rem;margin-bottom:.4rem}
.groups li .muted{font-size:.8rem}

/* Name and face on the first line, standing on their own; role and tally beneath. Laying all four
   across one row squeezes the name to nothing in a narrow card, which is the one thing here that
   must always be readable. */
.members{list-style:none;padding:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(16rem,1fr));gap:.7rem}
.members li{background:var(--panel2);border:2px solid #000;outline:1px solid var(--line);outline-offset:-5px;padding:.7rem .8rem}
.members li a{display:flex;gap:.7rem;align-items:center;min-width:0}
.members .av{width:52px;height:52px}.members .av-none{font-size:1.5rem}
/* No clipping around a name. overflow:hidden (for an ellipsis) cuts a glow off square, which is what
   drew a rectangle around the lit flares here and nowhere else. A long name wraps instead. */
.members .m-name{font-family:var(--font-read);font-size:1.05rem;min-width:0;overflow-wrap:anywhere}
.members .m-foot{display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.55rem}
.members .muted{font-size:.75rem}

/* Columns, not a grid. The cards are different heights, and a grid leaves whichever one is shorter
   sitting next to a hole — which is exactly the gap under Sign-in. Columns flow the next card up
   into that space instead, so the panel stays filled whatever each card ends up containing. */
.acct{columns:21rem 2;column-gap:1rem}
.acct .card{break-inside:avoid;margin:0 0 1rem}
.acct .card{background:var(--panel2);border:2px solid #000;outline:1px solid var(--line);outline-offset:-6px;padding:.9rem 1rem 1.1rem}
.acct .card h2{font-size:1.4rem;margin:.1em 0 .6em;border-bottom:1px dotted var(--line)}
.acct-av{display:flex;gap:1rem;align-items:flex-start;flex-wrap:wrap}
.acct textarea{width:100%;font:inherit;background:var(--bg2);color:var(--ink);border:2px solid var(--green-d);padding:.4rem}
.btn.small{padding:.3rem .7rem;font-size:.8rem}
@media(max-width:860px){.acct{grid-template-columns:1fr}.av-big{width:104px;height:104px}}
/* An upload that failed should look like it failed, not like a status line. */
#avMsg,#upMsg,#edUpMsg{display:block;margin-top:.4rem;font-size:.82rem;line-height:1.45;max-width:30rem}
#avMsg.bad,#upMsg.bad,#edUpMsg.bad{color:var(--new);border-left:3px solid var(--new);padding-left:.5rem}

/* ── Flare: how a name is written ──────────────────────────────────────────
   Cosmetic only. Each theme is one class on one span, so a name looks the same
   in the header, on a profile, in the member list and on a byline — there is a
   single place that decides, and nowhere for them to drift apart. */
.uname{font-weight:inherit}
.uname-big{font-family:var(--font-display);font-size:inherit}
.fl-blood{color:var(--blood);text-shadow:0 1px 0 #000,0 2px 0 var(--blood-d)}
.fl-gold{color:var(--gold);text-shadow:0 1px 0 #000,0 0 7px rgba(168,149,40,.45)}
[data-theme=light] .fl-gold{text-shadow:0 1px 0 rgba(0,0,0,.25)}
.fl-violet{color:var(--violet);text-shadow:0 0 9px rgba(143,114,189,.6)}
[data-theme=light] .fl-violet{text-shadow:none}
.fl-bone{color:#d9d3bd;text-shadow:0 1px 0 #000}
[data-theme=light] .fl-bone{color:#6b6450;text-shadow:none}
.fl-moss{color:var(--moss);text-shadow:0 1px 0 #000}
[data-theme=light] .fl-moss{text-shadow:none}
.fl-ember,.fl-spectral{background:linear-gradient(90deg,var(--a),var(--b));-webkit-background-clip:text;background-clip:text;color:transparent}
.fl-ember{--a:#f5c542;--b:#b01818}
.fl-spectral{--a:#44ff1e;--b:#8f72bd}
[data-theme=light] .fl-ember{--a:#8a6a05;--b:#8e1010}
[data-theme=light] .fl-spectral{--a:#1d6a08;--b:#5b3f8a}
.fl-iron{color:var(--stone);text-shadow:1px 1px 0 #000,-1px -1px 0 rgba(255,255,255,.18)}
[data-theme=light] .fl-iron{text-shadow:1px 1px 0 rgba(0,0,0,.3)}
.fl-cursed{color:var(--new);text-shadow:1px 0 0 rgba(0,220,255,.33),-1px 0 0 rgba(255,0,90,.33)}
[data-theme=light] .fl-cursed{text-shadow:1px 0 0 rgba(0,120,160,.3),-1px 0 0 rgba(180,0,60,.3)}

/* the picker: each option writes your own name in that theme, so you choose by looking */
/* Swatches, two across: you pick one by seeing your own name written in it, so the name is the
   biggest thing in each tile and the theme's label sits under it. Kept short so the card does not
   grow tall enough to put the hole back on the other side of the panel. */
.flares{list-style:none;padding:0;margin:0 0 .8rem;display:grid;grid-template-columns:repeat(auto-fill,minmax(9rem,1fr));gap:.4rem}
/* Form labels are violet, and an unflared name inherits whatever is around it — so without this the
   "Plain" swatch previews as violet and reads like a theme of its own. Body-text colour here means the
   swatch shows what the name will actually look like in a sentence. */
.flares label{position:relative;display:block;padding:.45rem .55rem;border:2px solid transparent;background:var(--bg2);cursor:pointer;text-align:center;color:var(--ink)}
.flares label:hover{border-color:var(--green-d)}
/* The radio is invisible but still the thing you click and tab to: it covers its whole swatch rather
   than being hidden out of the way, so pointer and keyboard both land on the real control. */
.flares input{position:absolute;inset:0;width:100%;height:100%;margin:0;opacity:0;cursor:pointer}
.flares label:has(input:focus-visible){outline:2px solid var(--green);outline-offset:2px}
.flares .uname{display:block;font-size:1rem;overflow-wrap:anywhere}   /* same reason: a swatch must not clip its own glow */
.flares b{display:block;font-size:.65rem;text-transform:uppercase;letter-spacing:.09em;color:var(--muted);font-weight:400;margin-top:.15rem}

/* ── Architect: the owner's own ────────────────────────────────────────────
   The only flare that moves of its own accord and the only one that is earned
   rather than chosen — blood into gold into violet, sweeping, lit from behind.
   Gated server-side at save AND at render, so it cannot outlive the role. */
.fl-architect{background:linear-gradient(100deg,var(--blood) 0%,var(--gold) 28%,#fff3c4 46%,var(--violet) 66%,var(--blood) 100%);
  background-size:260% 100%;-webkit-background-clip:text;background-clip:text;color:transparent}
.fl-architect::before{background:radial-gradient(ellipse at center,
  rgba(224,196,96,.22) 0%,rgba(168,149,40,.10) 42%,transparent 70%)}
[data-theme=light] .fl-architect{background-image:linear-gradient(100deg,#8e1010 0%,#7a6a14 28%,#4a3f0a 46%,#5b3f8a 66%,#8e1010 100%)}
[data-theme=light] .fl-architect::before{background:none}
.fl-architect::after{content:"✦";font-size:.72em;margin-left:.3em;vertical-align:.08em;-webkit-text-fill-color:var(--gold);color:var(--gold)}

/* ── Keystone: the trusted owner's ─────────────────────────────────────────
   Deliberately not a second Architect. That one sweeps blood→gold→violet, one
   way, forever — a maker's flare. This is dressed stone with a lamp on it, and
   it travels and then HOLDS at each end, because the thing it describes is
   bearing weight rather than building. Same gating: earned, never chosen. */
.fl-keystone{background:linear-gradient(100deg,#8d9bab 0%,#c9d4e0 32%,#ffe6a8 50%,#c9d4e0 68%,#8d9bab 100%);
  background-size:300% 100%;-webkit-background-clip:text;background-clip:text;color:transparent}
.fl-keystone::before{background:radial-gradient(ellipse at center,
  rgba(255,214,122,.18) 0%,rgba(160,176,196,.09) 45%,transparent 72%)}
/* Bone mode needs its own ramp: the dark-mode stones are far too pale on parchment. */
[data-theme=light] .fl-keystone{background-image:linear-gradient(100deg,#3c4652 0%,#262e38 32%,#6b4e10 50%,#262e38 68%,#3c4652 100%)}
[data-theme=light] .fl-keystone::before{background:none}
.fl-keystone::after{content:"❖";font-size:.7em;margin-left:.3em;vertical-align:.06em;-webkit-text-fill-color:#c9d4e0;color:#c9d4e0}
[data-theme=light] .fl-keystone::after{-webkit-text-fill-color:#3c4652;color:#3c4652}


/* ── The flare picker as a disclosure ──────────────────────────────────────
   Closed by default: it is the longest card on the page and the one you touch
   least, so it opens to its title and an arrow and stays out of the way. */
.flare-d > summary{list-style:none;cursor:pointer;display:flex;align-items:center;gap:.5rem}
.flare-d > summary::-webkit-details-marker{display:none}
.flare-d > summary h2{display:inline;margin:0;border:0}
.flare-d > summary::after{content:"▸";color:var(--blood);font-size:1.1rem;transition:transform .15s ease;margin-left:auto}
.flare-d[open] > summary::after{transform:rotate(90deg)}
.flare-d > summary:hover h2{color:var(--green)}
.flare-d[open] > summary{border-bottom:1px dotted var(--line);margin-bottom:.6rem;padding-bottom:.3rem}
.flare-h{font-size:.7rem;text-transform:uppercase;letter-spacing:.12em;color:var(--muted);
  margin:.9rem 0 .35rem;border-bottom:1px dotted var(--line);padding-bottom:.2rem}
/* One source of truth for the highlight: the radio itself. Marking the saved one server-side as well
   meant two swatches lit up at once — the old one still carrying the class, the new one matching
   :checked. The server only has to mark the right radio checked; this rule covers both the page as it
   loads and every click after. */
.flares label:has(input:checked){border-color:var(--green);box-shadow:inset 0 -3px 0 var(--blood)}
/* Without :has() there would be no indicator at all, since the radio is invisible — so show it. */
@supports not (selector(:has(*))){
  .flares input{position:static;inset:auto;width:auto;height:auto;opacity:1}
  .flares label{display:flex;align-items:center;gap:.5rem;text-align:left}
}

/* ── Admin flares: named for bosses, not for ranks ─────────────────────────── */
/* These four need a box to work in: a transform, and a pseudo-element placed over the name, both
   need the span to be more than an inline run of text. */
.fl-wizardlord,.fl-slimeking,.fl-wormlord,.fl-goldskeleton{display:inline-block;position:relative}

/* An emitted glow has to be a shape, not a box. text-shadow follows the letters, but a filter on an
   element with background-clip:text rasterises the whole element box before clipping — which is what
   drew a rectangle around the name. So anything that emits gets a real ellipse behind it instead, and
   isolate keeps that ellipse behind the text without falling behind the card underneath. */
.fl-wizardlord,.fl-architect,.fl-goldskeleton{position:relative;display:inline-block;isolation:isolate}
.fl-wizardlord::before,.fl-architect::before,.fl-goldskeleton::before{content:"";position:absolute;left:50%;top:50%;translate:-50% -50%;
  width:109%;height:146%;border-radius:50%;pointer-events:none;z-index:-1}

/* Wizard Lord — orange at the core, green at the edge. Dimmer than the letters so it reads as light
   coming off the name rather than the name being white. */
.fl-wizardlord{color:#e8b863;text-shadow:0 0 5px rgba(255,140,20,.45)}
.fl-wizardlord::before{background:radial-gradient(ellipse at center,
  rgba(255,138,16,.30) 0%,rgba(255,138,16,.15) 34%,rgba(66,230,86,.13) 56%,transparent 74%)}
[data-theme=light] .fl-wizardlord{color:#9a5a06;text-shadow:none}
[data-theme=light] .fl-wizardlord::before{background:radial-gradient(ellipse at center,
  rgba(230,110,0,.26) 0%,rgba(20,150,40,.16) 55%,transparent 76%)}

/* Slime King — sits in its own puddle and will not keep still. */
.fl-slimeking{color:#5bd41f;text-shadow:0 1px 0 #1d5a07,0 3px 3px rgba(91,212,31,.55)}
[data-theme=light] .fl-slimeking{color:#2d7a06;text-shadow:0 1px 0 rgba(0,0,0,.2)}

/* Worm Lord — a swelling travels along the name, the way something moves under skin. */
.fl-wormlord{background:linear-gradient(90deg,#4a1d0e 0%,#8d4526 30%,#d98a63 46%,#f3c3a4 50%,#d98a63 54%,#8d4526 70%,#4a1d0e 100%);
  background-size:320% 100%;-webkit-background-clip:text;background-clip:text;color:transparent}
[data-theme=light] .fl-wormlord{background-image:linear-gradient(90deg,#5a2412 0%,#8a3a1c 30%,#b3613a 46%,#7a2f14 50%,#b3613a 54%,#8a3a1c 70%,#5a2412 100%)}

/* Golden Skeleton — bone edged in gold, standing in its own warm light. */
.fl-goldskeleton{color:#efe7cf;text-shadow:0 1px 0 var(--gold),0 -1px 0 var(--gold),1px 0 0 var(--gold),-1px 0 0 var(--gold)}
.fl-goldskeleton::before{background:radial-gradient(ellipse at center,
  rgba(240,221,146,.30) 0%,rgba(168,149,40,.17) 38%,rgba(168,149,40,.07) 60%,transparent 76%)}
[data-theme=light] .fl-goldskeleton{color:#6b6450;text-shadow:0 1px 0 #a8952899,1px 0 0 #a8952899}
[data-theme=light] .fl-goldskeleton::before{background:radial-gradient(ellipse at center,rgba(168,149,40,.22) 0%,transparent 72%)}


/* ── Moderator flares: the four eyes ─────────────────────────────────
   One per equippable eye, and every colour below is sampled from that eye's own icon in data/files
   rather than chosen: Hawk #ffff00→#ffd000 iris on near-black, Healing #00ff81 on #470006, Shield's
   bare #aaaaaa steel, Lizard's #8b612d scales. Each one MOVES the way its ability behaves, because
   four eyes that all shimmer would just be four shimmers — the hunt darts, the heal beats, the shield
   snaps on and off, the lizard goes and comes back. None of the four reuses an admin flare's motion.

   A flare is decoration and grants nothing, so none of this is a permission — but the tier is checked
   at save AND at render, so demoting a moderator plains their name on the very next page.

   Every bone-mode stop below clears 4.5:1 against the bone background. The skin editor warns an owner
   when their palette does not; shipping flares that fail the same test would be a poor advertisement
   for the warning. The dark-mode ramps are far clear of it except the Lizard's deliberate black slit,
   which is a feature of the gradient rather than the colour the name is read in. */
.fl-hawkeye,.fl-healeye,.fl-shieldeye,.fl-lizardeye{display:inline-block;position:relative}
.fl-healeye{isolation:isolate}
.fl-healeye::before{content:"";position:absolute;left:50%;top:50%;translate:-50% -50%;
  width:109%;height:146%;border-radius:50%;pointer-events:none;z-index:-1}

/* Hawk Eye — the yellow iris, on the icon's black sclera (the drop-shadow). Every stop is yellow, so
   the name is yellow at rest and the motion is only a pale highlight crossing it: the bird darts, then
   holds. An earlier version put the dark stops at the ends of the gradient and the name spent 88% of
   the cycle sitting on them, reading brown. */
.fl-hawkeye{background:linear-gradient(90deg,#e0ad00 0%,#ffd000 28%,#ffff3a 44%,#fffde0 50%,#ffff3a 56%,#ffd000 72%,#e0ad00 100%);
  background-size:300% 100%;background-position:100% 0;-webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 1px 0 #000) drop-shadow(0 0 5px rgba(255,208,0,.3))}
[data-theme=light] .fl-hawkeye{background-image:linear-gradient(90deg,#6e5500 0%,#654e00 28%,#4e3c00 44%,#332700 50%,#4e3c00 56%,#654e00 72%,#6e5500 100%);filter:none}

/* Healing Eye — spring green on old blood, and it beats: two pulses close together, then a rest. The
   glow is the ellipse rather than a text-shadow so it stays a shape and not a rectangle. */
.fl-healeye{color:#00ff81;text-shadow:0 1px 0 #470006,0 0 6px rgba(0,255,129,.4)}
.fl-healeye::before{background:radial-gradient(ellipse at center,
  rgba(0,255,129,.34) 0%,rgba(71,0,6,.26) 46%,transparent 72%)}
[data-theme=light] .fl-healeye{color:#065c2b;text-shadow:0 1px 0 rgba(71,0,6,.25)}
[data-theme=light] .fl-healeye::before{background:radial-gradient(ellipse at center,rgba(10,112,56,.16) 0%,transparent 70%)}

/* Shield Eye — bare steel: a pale dome with a hard edge all the way round, which is the only thing
   that tells it apart from ordinary pale text. Once in a while nothing can touch it and that edge
   snaps white for a few frames. Kept to a 1px rim and a 4px halo on purpose — a wide blur reads as a
   smudged box behind the name rather than a frame around it, which is what the first attempt did; and
   the white of the flash is pulled off pure so a 1px rim either side of a thin stroke does not read as
   the name printed twice. */
.fl-shieldeye{color:#dcdcdc;
  text-shadow:1px 0 0 #6f6f6f,-1px 0 0 #6f6f6f,0 1px 0 #4a4a4a,0 -1px 0 #8a8a8a,0 2px 3px rgba(0,0,0,.7)}
/* Bone mode drops the side rims: a white halo either side of a thin dark stroke eats into the glyph
   and the name goes faint. The emboss comes from a highlight underneath and a shadow above instead. */
[data-theme=light] .fl-shieldeye{color:#3f3f3f;
  text-shadow:0 1px 0 rgba(255,255,255,.95),0 -1px 0 rgba(110,110,110,.45)}

/* Lizard Eye — ochre scales with the slit pupil standing in the middle of the name: the dark stop at
   50% is a fixed vertical band, not a travelling one, so it never gets mistaken for the Worm Lord's
   crest. It sets a mark, goes, and comes back to exactly the place it left. */
.fl-lizardeye{background:linear-gradient(95deg,#8b612d 0%,#c2893f 20%,#e0ab62 42%,#1a1208 50%,#e0ab62 58%,#c2893f 80%,#8b612d 100%);
  -webkit-background-clip:text;background-clip:text;color:transparent;
  filter:drop-shadow(0 1px 0 #000)}
[data-theme=light] .fl-lizardeye{background-image:linear-gradient(95deg,#3d270c 0%,#5c3c13 22%,#6b4716 44%,#120c04 50%,#6b4716 56%,#5c3c13 78%,#3d270c 100%);filter:none}

/* ── Motion belongs to the earned flares ───────────────────────────────────
   Nothing in the open set moves: ten names drifting and pulsing in one member
   list is noise, and a name that animates should mean something. Every flare
   below is gated on a role, so movement reads as the tell rather than as
   decoration. All of it stops for anyone who asked for less motion. */
@media(prefers-reduced-motion:no-preference){
  /* the aura swells and swings from orange toward green, while the letters hold steady */
  .fl-wizardlord::before{animation:fl-aura 3.6s ease-in-out infinite}
  @keyframes fl-aura{
    0%,100%{opacity:.85;scale:1;filter:hue-rotate(0deg)}
    50%{opacity:1;scale:1.06;filter:hue-rotate(16deg)}}

  /* a slight, constant jiggle — never far, never still */
  .fl-slimeking{animation:fl-jiggle 1.9s ease-in-out infinite}
  @keyframes fl-jiggle{
    20%{transform:translate(.6px,-.5px) skewX(-2deg) scaleY(1.04)}
    45%{transform:translate(-.5px,.4px) skewX(1.5deg) scaleY(.97)}
    70%{transform:translate(.3px,.5px) skewX(-1deg) scaleY(1.02)}}

  /* the swelling travels the length of the name and starts again */
  .fl-wormlord{animation:fl-burrow 4.2s linear infinite}
  @keyframes fl-burrow{to{background-position:320% 0}}

  /* the gold brightens and settles, slower than the wizard's aura so the two do not beat together */
  .fl-goldskeleton::before{animation:fl-gleam 5.2s ease-in-out infinite}
  @keyframes fl-gleam{
    0%,100%{opacity:.8;scale:1}
    50%{opacity:1;scale:1.05}}

  .fl-architect{animation:fl-architect 9s linear infinite}             /* the sweep, slowest of all */
  @keyframes fl-architect{to{background-position:260% 0}}

  /* the lamp crosses the stone and rests at each end — 16% travelling, 84% held */
  .fl-keystone{animation:fl-keystone 11s ease-in-out infinite}
  @keyframes fl-keystone{
    0%,42%{background-position:0% 0}
    58%,100%{background-position:300% 0}}
  .fl-keystone::before{animation:fl-keystone-bear 6.5s ease-in-out infinite}
  @keyframes fl-keystone-bear{
    0%,100%{opacity:.72;scale:1}
    50%{opacity:1;scale:1.04}}

  /* ---- the four eyes ---- */
  /* the hunt: a fast dart across the name, then a long hold. 12% moving, 88% watching. */
  .fl-hawkeye{animation:fl-hunt 4.6s cubic-bezier(.2,.75,.3,1) infinite}
  @keyframes fl-hunt{
    0%,74%{background-position:100% 0}
    86%,100%{background-position:0 0}}

  /* the heartbeat: two beats close together, then a rest, the way the eye feeds and fills */
  .fl-healeye::before{animation:fl-beat 2.6s ease-out infinite}
  @keyframes fl-beat{
    0%{opacity:.55;scale:1}
    9%{opacity:1;scale:1.09}
    20%{opacity:.7;scale:1.01}
    29%{opacity:.95;scale:1.06}
    44%,100%{opacity:.55;scale:1}}

  /* the immunity frame: the steel rim snaps white, holds a beat, and bleeds back to steel */
  .fl-shieldeye{animation:fl-ward 5.4s ease-out infinite}
  @keyframes fl-ward{
    0%,60%,100%{text-shadow:1px 0 0 #6f6f6f,-1px 0 0 #6f6f6f,0 1px 0 #4a4a4a,0 -1px 0 #8a8a8a,0 2px 3px rgba(0,0,0,.7)}
    66%,72%{text-shadow:1px 0 0 #dfe8f2,-1px 0 0 #dfe8f2,0 1px 0 #b9cde6,0 -1px 0 #fff,0 0 5px rgba(200,228,255,.9)}}
  [data-theme=light] .fl-shieldeye{animation-name:fl-ward-l}
  @keyframes fl-ward-l{
    0%,60%,100%{text-shadow:0 1px 0 rgba(255,255,255,.95),0 -1px 0 rgba(110,110,110,.45)}
    66%,72%{text-shadow:0 1px 0 #fff,0 -1px 0 #2f4f7a,0 0 5px rgba(90,140,210,.5)}}

  /* the teleport: slips aside, thins to nothing, and is back where it started. Eight seconds apart,
     because a name that vanishes often is a name you cannot read. */
  .fl-lizardeye{animation:fl-blink 8s ease-in-out infinite}
  @keyframes fl-blink{
    0%,88%{transform:translateX(0);opacity:1}
    92%{transform:translateX(4px) scaleX(.86);opacity:0}
    94%{transform:translateX(-4px) scaleX(.86);opacity:0}
    97%,100%{transform:translateX(0) scaleX(1);opacity:1}}
}
/* Skin editor: a colour picker beside a hex box, both bound to the same value. The picker is for
   choosing, the box for pasting a hex you already have and for clearing back to the engine's default,
   which a picker has no way to express. */
/* Background images: a swatch showing what is actually behind the wiki right now, beside the control
   that changes it. A list of filenames would tell an owner nothing — the whole question is what it
   looks like, so the answer has to be the thing itself, tiled at the size it tiles at. */
.bgs{display:grid;gap:.7rem;margin:.6rem 0}
.bg{display:flex;gap:.8rem;align-items:flex-start;border:2px solid var(--line);background:var(--panel2);padding:.6rem}
.bg-sw{flex:0 0 108px;height:84px;border:2px solid #000;outline:1px solid var(--line);
  background-color:var(--bg);background-repeat:repeat;image-rendering:pixelated}
.bg-t{min-width:0;flex:1}
.bg-t .row{margin-top:.4rem;align-items:center;flex-wrap:wrap;gap:.5rem}
.bg input[type=file]{max-width:100%;font:inherit;font-size:.8rem;color:var(--muted)}
.bg .muted.bad{color:var(--new)}
@media(max-width:620px){.bg{flex-direction:column}.bg-sw{flex-basis:auto;width:100%;height:70px}}
/* ── The host's front door (home.js) ───────────────────────────────────
   The apex is not a wiki: no sidebar, no search, no account. It borrows the engine's tokens so it
   looks like the places it leads to, and nothing else. */
/* The way back to the host. Quiet in the footer, and a proper target in the sidebar where someone
   actually goes looking for navigation. Absent entirely on a wiki with no host behind it. */
.home-out{margin-right:.6rem}
.side-out{display:block;margin:.5rem 0 0;padding-top:.5rem;border-top:1px dotted var(--line);font-size:.85rem}
.page.host{max-width:52rem;margin:2rem auto;padding:0 1rem}
.wikilist{list-style:none;padding:0;margin:1rem 0;display:grid;gap:.7rem}
.wikilist li{border:2px solid var(--line);background:var(--panel);padding:.7rem .9rem}
.wikilist a{display:flex;gap:.6rem;align-items:baseline;flex-wrap:wrap;text-decoration:none}
.wikilist a b{font-size:1.15rem;font-family:var(--font-display);letter-spacing:.02em}
.wikilist a:hover b{color:var(--green)}
.wikilist p{margin:.3rem 0 0;color:var(--ink)}
.card.join{margin-top:2rem}
/* The placeholder reads as a shut door rather than as an error: nothing has gone wrong, you are just
   not signed in. Dotted, dim, and the same height the form will be. */
.locked{border:2px dotted var(--line);color:var(--muted);padding:.9rem;text-align:center;margin:.6rem 0 0}
.foot{max-width:52rem;margin:3rem auto 2rem;padding:0 1rem;border-top:1px dotted var(--line)}
.skin{border:2px solid var(--line);margin:.8rem 0;padding:.6rem .8rem}
.skin legend{color:var(--green);padding:0 .4rem}
.sw-c{display:inline-flex;align-items:center;gap:.35rem;margin:.25rem .6rem .25rem 0}
.sw-c input[type=color]{width:2rem;height:1.8rem;padding:0;border:2px solid var(--line);background:none;cursor:pointer}
.sw-c input[name]{width:7rem;font-family:var(--font-body)}
.sw-c small{color:var(--muted)}
.skin details{margin-top:.5rem}.skin summary{cursor:pointer;color:var(--muted)}

/* Join codes. The code itself is the thing on the page a person copies by eye or by cursor, so it
   is set in the mono face, spaced out, and given enough room that a hyphen never wraps mid-code. */
.joincode{font-family:var(--font-mono,monospace);font-size:1rem;letter-spacing:.08em;white-space:nowrap;
  border:1px solid var(--line);padding:.1rem .4rem;user-select:all}
.codes{list-style:none;padding:0;margin:.6rem 0 0;display:grid;gap:.35rem}
.codes li{display:flex;align-items:center;gap:.5rem;flex-wrap:wrap}
.codebox{display:flex;align-items:flex-end;gap:.6rem;flex-wrap:wrap}
.codebox label{flex:1 1 14rem;margin:0}
.codebox input{font-family:var(--font-mono,monospace);letter-spacing:.08em;text-transform:uppercase}

/* The front door's logo. A wiki's brand is a sigil beside its name in text; the host's is one piece
   of artwork with the name already in it, so the <span> is gone from that header and the image
   carries both. That changes what the shared .brand img rules should do to it:
   - taller, because a three-to-one lockup at 44px renders its words at about fifteen pixels;
   - not pixelated, which is a flourish for a hand-made sigil and simply blurs a traced wordmark;
   - no purple glow and no pulse, which belong to Blood & Dream and not to a wedge of cheese. */
.hostpage .brand img{height:52px;width:auto;image-rendering:auto;filter:none;animation:none}
.hostpage .brand{gap:0}

/* A sigil is a mark, not a tile: show the whole of it rather than cropping to fill the swatch. */
.bg-sw.mark{background-size:contain;background-repeat:no-repeat;background-position:center}

/* A wiki's own mark in the host's directory. Fixed box so a tall logo and a wide one line up. */
.wikilist .wikimark{width:40px;height:40px;object-fit:contain;vertical-align:middle;margin-right:.6rem;border-radius:4px}
