/* Dungeons of Blood and Dream — design tokens sampled from the title card & in-game screenshot */
:root{
  --font-display:"UnifrakturCook","UnifrakturMaguntia","Pirata One","Grenze Gotisch","Old English Text MT","Blackadder ITC","Luminari",fantasy,serif;
  --font-body:"Courier New","Lucida Console",ui-monospace,monospace;
  --font-read:Georgia,"Times New Roman",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>). 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 (it is texture under a colour, and the colour is what changes between them). */
:root{
  --tex-page:url(/assets/cobble.svg);
  --tex-panel:url(/assets/scales.svg);
  --tex-auth:url(/assets/ingame.png);
}
:root,[data-theme=dark]{
  --bg:#040604; --bg2:#0a110a; --panel:#0d160b; --panel2:#142010; --ink:#c9d6b8; --muted:#7f8c70;
  --green:#44ff1e; --green-d:#2a8f12; --moss:#6b7d1c; --blood:#b01818; --blood-d:#5a0a0a; --violet:#8f72bd; --stone:#8c8c96; --gold:#a89528;
  --sel:#5a0a0a; --sel-ink:#ffffff;   /* the highlight chip: current page, active tab, selected suggestion */
  --link:#44ff1e; --link-v:#8f72bd; --new:#e0382f; --line:#27401a; --glow:0 0 8px rgba(68,255,30,.45); --floor:.55;
}
[data-theme=light]{ /* "bone" mode: same hues, inverted values */
  --bg:#d9d3bd; --bg2:#cfc8ae; --panel:#e7e1cc; --panel2:#ddd6bc; --ink:#14180f; --muted:#4e5544;
  /* every value here clears 4.5:1 against both --bg and --panel; see the light-mode contrast test */
  --green:#176205; --green-d:#114c04; --moss:#59680f; --blood:#8e1010; --blood-d:#c9a39a; --violet:#5b3f8a; --stone:#55555e; --gold:#7a6a14;
  --link:#166005; --link-v:#5b3f8a; --new:#a01010; --line:#8a9a6a; --glow:none; --floor:.1;
  --sel:#8e1010; --sel-ink:#ffffff;   /* --blood-d is a pale tint in this theme, so it cannot be the chip */
}
*{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}
h1.blackletter{color:var(--green);font-size:2.6rem;margin:.2em 0 .3em;text-shadow:0 0 12px rgba(68,255,30,.35),0 3px 0 var(--blood-d);filter:url(#melt)}
[data-theme=light] h1.blackletter{text-shadow:0 2px 0 var(--blood-d)}
.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}
main.page::after{content:"";display:block;height:40px;margin-top:2rem;background:url(/assets/drip.svg) center/100% 100% no-repeat}
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)}
.tablewrap{overflow-x:auto;margin:1rem 0}.wikitable{border-collapse:collapse;min-width:50%}.wikitable th,.wikitable td{border:2px solid var(--line);padding:.35rem .7rem}.wikitable th{background:var(--panel2);color:var(--green);font-family:var(--font-body)}.wikitable caption{color:var(--violet)}
/* 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}
.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}
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)}


/* ── 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 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)}
