/* Mystic web client skin.
   Layout: a gilded command bar over three framed columns (room / story / side panes);
   tablets turn the side column into a drawer, phones get one column with a bottom bar.
   ANSI colour classes (color-NNN / bgcolor-NNN) are generated by mystic.js.
   Note: inside a data: URI, write an SVG filter reference with percent-encoded
   brackets (url%28%23id%29). ManifestStaticFilesStorage scans this file for url
   references and would try to resolve a plain one as a static file. */
:root{
  /* THE SPACE BETWEEN ROWS IN ANY LIST (Lizzy, Session D7: use one, and keep to it): buttons, rows, people and cards all
     sit this far apart, and a row never adds a margin of its own inside a list */
  --gap-list:7px;
  --gap-fold:10px;
  --bg:#0c0b14; --bg-glow-1:rgba(92,62,160,.34); --bg-glow-2:rgba(36,74,138,.32); --bg-glow-3:rgba(128,58,140,.16);
  --panel:#0b0a11; --panel-2:#14111d; --panel-3:#1b1727;
  --line:rgba(201,164,106,.26); --line-strong:rgba(201,164,106,.55);
  --gold:#c9a46a; --gold-hi:#ecd29a;
  --violet:#8f63d2; --violet-hi:#c3a2f5; --violet-deep:#2c1b47;
  --text:#ddd5c6; --text-hi:#f3ecdf; --muted:#958b9f;
  --exit:#86bbee; --calm:#93d38c; --echo:#7f7690; --err:#ea7563;
  --hp:#c8343f; --mp:#3a86d8; --xp:#8a5ad8;
  --parch:#cfb789; --parch-dark:#a98a5a; --parch-ink:#4b3622;
  --node-seen:#2f9d93; --node-new:#dccca6; --poi:#d9a63a;
  --focus:#c3a2f5;
  --display:'Cinzel','Trajan Pro','Palatino Linotype',Georgia,serif;
  --serif:'EB Garamond',Garamond,'Palatino Linotype',Georgia,serif;
  --mono:'IBM Plex Mono',Consolas,'DejaVu Sans Mono',monospace;
  --out-font:var(--mono); --out-size:14.5px; --out-lh:1.6;
  color-scheme:dark;
}
/* the help window sits outside .app, so it carries the same font settings */
:is(.app,.helpwin)[data-font="book"]{--out-font:var(--serif); --out-size:18.5px; --out-lh:1.45}
:is(.app,.helpwin)[data-size="s"]{--out-size:13px}
:is(.app,.helpwin)[data-size="l"]{--out-size:16.5px}
:is(.app,.helpwin)[data-font="book"][data-size="s"]{--out-size:16.5px}
:is(.app,.helpwin)[data-font="book"][data-size="l"]{--out-size:21px}

html{height:100%; box-sizing:border-box}
*,*::before,*::after{box-sizing:inherit}
body{
  height:100%; margin:0; overflow:hidden; color:var(--text); font-family:var(--serif); font-size:16px;
  padding-top:env(safe-area-inset-top,0px); padding-bottom:env(safe-area-inset-bottom,0px);
  background:
    radial-gradient(ellipse 60% 50% at 12% 8%, var(--bg-glow-1), transparent 70%),
    radial-gradient(ellipse 55% 60% at 88% 92%, var(--bg-glow-2), transparent 70%),
    radial-gradient(ellipse 50% 40% at 55% 45%, var(--bg-glow-3), transparent 70%),
    var(--bg);
}
/* faint grain over the whole page: a pre-rendered PNG tile (assets/grain.png, made from the
   preview's SVG fractal noise) on its own GPU layer, so it is drawn once and never repainted.
   The live SVG filter and mix-blend-mode versions forced costly repaints on every update. */
body::before{
  content:""; position:fixed; inset:0; pointer-events:none; z-index:60; opacity:.06;
  background:url("assets/grain.b0c28d2a007e.png") repeat; will-change:transform; transform:translateZ(0);
}
button{font:inherit; color:inherit; background:none; border:0; padding:0; cursor:pointer}
button:disabled{cursor:default}
:focus-visible{outline:2px solid var(--focus); outline-offset:2px}
img{display:block}
[hidden]{display:none!important}
.noscript{margin:40px 16px; text-align:center; font-size:18px}

/* ---------- shell ---------- */
.app{
  height:100%; display:grid; grid-template-rows:auto minmax(0,1fr); grid-template-columns:minmax(0,1fr); gap:10px;
  padding-left:max(16px, env(safe-area-inset-left, 0px)); padding-right:max(16px, env(safe-area-inset-right, 0px));
  padding-block:10px 14px;
}
.frame{border:18px solid transparent; border-image:url("assets/frame-medium.6ce03ae1a94f.png") 30 fill / 18px stretch}
.topbar{
  display:grid; grid-template-columns:auto minmax(0,1fr) auto; align-items:center; gap:18px;
  padding:0 8px; min-height:58px;
  border:16px solid transparent; border-image:url("assets/frame-small.6492901a9b8e.png") 26 fill / 16px stretch;
}
.brand{height:36px; width:auto; margin-left:-6px}
.brandh{margin:0; line-height:0}
.brandlink{display:block; line-height:0; border-radius:4px}
.brandlink:hover .brand{filter:brightness(1.15) drop-shadow(0 0 6px rgba(195,162,245,.5))}
.mainnav{display:flex; gap:2px; overflow-x:auto; scrollbar-width:none; min-width:0}
.mainnav::-webkit-scrollbar{display:none}
.navbtn{
  font:500 16px/1 var(--serif); color:var(--muted); padding:10px 14px; border-radius:4px; white-space:nowrap;
  position:relative; transition:color .15s;
}
.navbtn:hover{color:var(--text-hi)}
.navbtn[aria-current="true"]{color:var(--gold-hi); background:linear-gradient(180deg, rgba(143,99,210,.28), rgba(44,27,71,.5)); box-shadow:inset 0 0 0 1px var(--line-strong)}
.status{display:flex; align-items:center; gap:14px; font-size:15px; color:var(--text); white-space:nowrap}
.status .item{display:flex; align-items:center; gap:6px}
.status .item img{width:22px; height:22px; object-fit:contain}
.status .who{font-family:var(--display); font-weight:600; color:var(--gold-hi); letter-spacing:.03em}
.status .lv{font-size:13px; color:var(--muted); font-family:var(--serif)}
.status .vsep{width:1px; height:22px; background:var(--line)}
.conn{color:var(--muted); font-size:14px}
.conn .dot{width:8px; height:8px; border-radius:50%; background:var(--gold); box-shadow:0 0 8px currentColor}
.conn[data-state="open"] .dot{background:var(--calm); color:var(--calm)}
.conn[data-state="closed"] .dot{background:var(--err); color:var(--err)}
.conn[data-state="slow"] .dot{background:#e0a23a; color:#e0a23a}
.conn[data-state="slow"]{color:#e0a23a}
.conn[data-state="connecting"] .dot{animation:blink-dot 1.2s ease-in-out infinite}
@keyframes blink-dot{50%{opacity:.25}}
/* WHO AM I: once connected the light's label is "Character (account)", so tabs on different
   characters can be told apart; long names are cut short (the full name is in the tooltip) */
.conn[data-me] .lbl{max-width:260px; overflow:hidden; text-overflow:ellipsis; font-size:15px}
/* TOP BAR SKY (renderSky in mystic.js): icon-and-word pairs as in the approved preview, the
   period, the weather and the clock; the rest is in the sky pop-up */
.sky{display:flex; align-items:center; gap:14px; padding:4px 6px; border-radius:4px; font-size:15px; color:var(--text); white-space:nowrap}
.sky:hover,.sky[aria-expanded="true"]{background:rgba(201,164,106,.1)}
.sk{display:flex; align-items:center; gap:6px}
.sk img{width:22px; height:22px; object-fit:contain}
/* the exact temperature beside the weather, for those who can read it (Lizzy: "70° Rainy") */
.sk .deg{color:var(--text-hi); font-variant-numeric:tabular-nums}
.sk.clock{color:var(--muted); font-variant-numeric:tabular-nums}
.iconbtn{display:grid; place-items:center; width:36px; height:36px; border-radius:50%; color:var(--gold)}
.iconbtn:hover{color:var(--gold-hi); background:rgba(201,164,106,.1)}
.iconbtn svg{width:20px; height:20px}
.iconbtn img{width:24px; height:24px; object-fit:contain}

/* Go OOC / Quit menu under the door icon (a sheet above the bottom bar on phones) */
.leavemenu{position:fixed; z-index:55; top:calc(76px + env(safe-area-inset-top, 0px)); right:max(70px, calc(env(safe-area-inset-right, 0px) + 58px));
  width:min(300px, calc(100vw - 32px)); display:flex; flex-direction:column; gap:6px; padding:10px;
  background:var(--panel-2); border:1px solid var(--line-strong); border-radius:6px; box-shadow:0 18px 40px rgba(0,0,0,.65)}
.leaveopt{display:flex; flex-direction:column; gap:3px; text-align:left; padding:10px 12px; border:1px solid var(--line); border-radius:4px; background:rgba(255,255,255,.02)}
.leaveopt:hover{border-color:var(--line-strong); background:rgba(143,99,210,.18)}
.leaveopt b{font:600 14px/1.2 var(--display); letter-spacing:.04em; color:var(--gold-hi)}
.leaveopt span{font-size:14px; color:var(--muted); line-height:1.3}
.leaveopt.quit b{color:#f0a49a}

/* SESSION D2 LAYOUT (Lizzy's Garden of Laminae mockup): you on the left, the story in the
   middle, where you are on the right; both side columns grow a little on wide screens */
.stage{display:grid; grid-template-columns:clamp(270px, 19vw, 330px) minmax(0,1fr) clamp(290px, 21vw, 370px); gap:10px; min-height:0}
.col{min-height:0; min-width:0; display:flex; flex-direction:column}

/* ---------- shared bits ---------- */
.label{font:600 12px/1 var(--display); letter-spacing:.12em; text-transform:uppercase; color:var(--gold)}
.divider{width:min(200px,80%); height:auto; margin:4px auto; opacity:.9}
.tabs{display:flex; gap:3px; overflow-x:auto; scrollbar-width:none; border-bottom:1px solid var(--line-strong); flex-shrink:0}
.tabs::-webkit-scrollbar{display:none}
.tab{
  font:500 15px/1 var(--serif); color:var(--muted); padding:9px 15px 8px; white-space:nowrap;
  background:linear-gradient(180deg,#191522,#0f0d16); border:1px solid var(--line); border-bottom:0; border-radius:6px 6px 0 0;
  display:flex; align-items:center; gap:6px;
}
.tab:hover{color:var(--text-hi)}
.tab[aria-selected="true"]{
  color:var(--gold-hi); border-color:var(--line-strong);
  background:linear-gradient(180deg,#53368a,#2a1a45);
  box-shadow:inset 0 1px 0 rgba(236,210,154,.35), 0 -2px 14px rgba(143,99,210,.25);
}
.tab.add{padding-inline:12px; color:var(--gold)}
/* the channel row's "+" sits just outside the tab list (it isn't a tab), right after the last
   tab; the row draws the rule under both, and the font toggle keeps to the right */
.tabrow:has(> #chantabs){border-bottom:1px solid var(--line-strong)}
#chantabs{flex:0 1 auto; border-bottom:0}
.tabrow > .tab.add{align-self:flex-end; margin-left:-3px}
.tabrow:has(> #chantabs) .fonttoggle{margin-left:auto}
.badge{font:600 11px/1 var(--serif); min-width:18px; padding:3px 5px; border-radius:9px; background:var(--violet); color:#fff; text-align:center; font-variant-numeric:tabular-nums}
.badge[data-n="0"]{display:none}
.chip{
  font:600 11px/1 var(--display); letter-spacing:.06em; color:var(--gold-hi);
  border:1px solid var(--line-strong); border-radius:3px; min-height:30px; padding:0 6px;
  background:linear-gradient(180deg,#1c1828,#0e0c15); box-shadow:inset 0 0 0 1px rgba(0,0,0,.55);
  transition:opacity .2s, box-shadow .15s;
}
.chip:not(:disabled):hover{box-shadow:inset 0 0 0 1px rgba(0,0,0,.55), 0 0 10px rgba(143,99,210,.45); color:#fff}
.chip:disabled{opacity:.3}
.mini{font:600 10.5px/1 var(--display); letter-spacing:.06em; color:var(--gold-hi); border:1px solid var(--line); border-radius:3px; padding:6px 7px; white-space:nowrap}
.mini:hover{border-color:var(--line-strong); background:rgba(143,99,210,.2)}

/* ---------- right column: where you are ---------- */
/* the location card, map, compass and exits keep their size; HERE WITH YOU takes what's left
   and scrolls, and the whole column scrolls only when even that runs out */
.placecol{overflow-y:auto; overflow-x:hidden; gap:10px; scrollbar-width:thin; scrollbar-color:var(--violet-deep) transparent}
.placecol > *{flex-shrink:0}
.scroll{scrollbar-width:thin; scrollbar-color:var(--violet-deep) transparent}
.roomcard{position:relative; border:1px solid var(--line-strong); border-radius:4px; overflow:hidden; flex-shrink:0; background:var(--panel-2)}
.roomcard .art{width:100%; height:150px; object-fit:cover}
.app[data-art="compact"] .roomcard .art{height:86px}
.app[data-art="off"] .roomcard .art{display:none}
/* NIGHT ON THE PAINTING (Lizzy; TOD in mystic.js renderSky): indoors and out, a colour wash multiplied
   over the painting (it keeps its own colours, only darker and bluer) by night, and warm light
   at sunrise and sunset, easing between them. The wash sits under the weather and caption. */
.art{transition:filter 2.5s ease}
:is(.roomcard,.mroom)::before{content:""; position:absolute; inset:0; z-index:1; pointer-events:none; opacity:0; mix-blend-mode:multiply;
  transition:opacity 2.5s ease, background-color 2.5s ease}
/* a filtered painting is layered like a positioned element, so the wash, the weather and the
   caption stack above it explicitly */
:is(.roomcard,.mroom) > :is(.stars,.amb,.fog,.fx,.cap){z-index:2}
:is(.roomcard,.mroom):has(> .art[data-tod="night"])::before{opacity:1; background:#4a5aa6}
:is(.roomcard,.mroom):has(> .art[data-tod="predawn"])::before{opacity:.85; background:#7080c0}
:is(.roomcard,.mroom):has(> .art[data-tod="evening"])::before{opacity:.9; background:#6c5aa8}
:is(.roomcard,.mroom):has(> .art[data-tod="dawn"])::before{opacity:.45; background:#f4b88c}
:is(.roomcard,.mroom):has(> .art[data-tod="dusk"])::before{opacity:.55; background:#ec8f62}
.art[data-tod="night"]{filter:brightness(.78) saturate(.75)}
.art:is([data-tod="predawn"],[data-tod="evening"]){filter:brightness(.88) saturate(.85)}
/* indoors: about two thirds of the outdoor wash, as a lit room is not the open night */
:is(.roomcard,.mroom):has(> .art[data-in][data-tod="night"])::before{opacity:.65}
:is(.roomcard,.mroom):has(> .art[data-in][data-tod="predawn"])::before{opacity:.55}
:is(.roomcard,.mroom):has(> .art[data-in][data-tod="evening"])::before{opacity:.6}
:is(.roomcard,.mroom):has(> .art[data-in][data-tod="dawn"])::before{opacity:.3}
:is(.roomcard,.mroom):has(> .art[data-in][data-tod="dusk"])::before{opacity:.35}
.art[data-in][data-tod="night"]{filter:brightness(.88) saturate(.85)}
.art[data-in]:is([data-tod="predawn"],[data-tod="evening"]){filter:brightness(.93) saturate(.92)}
.roomcard .cap{position:absolute; inset:auto 0 0 0; padding:26px 12px 9px; text-align:center; background:linear-gradient(180deg,transparent,rgba(8,7,12,.88) 55%)}
.app[data-art="off"] .roomcard .cap{position:static; background:none; padding-top:12px}
/* ROOM CARD CAPTION (Lizzy, 2026-10-20): the territory (or an unclaimed room's zone) large over
   the painting, as on the preview's territory tiles, the room name smaller below it */
.terrname{font:700 21px/1.1 var(--display); color:var(--gold-hi); letter-spacing:.05em; text-wrap:balance; text-shadow:0 1px 8px #000, 0 0 2px #000}
.roomname{font:500 15px/1.2 var(--serif); color:var(--text-hi); letter-spacing:.01em; text-wrap:balance; text-shadow:0 1px 6px #000; margin-top:3px}
.roomname:empty,.roommeta:empty{display:none}
.roommeta{font-size:13.5px; color:#cfc5b6; margin-top:2px; font-style:italic}
/* ROW BUTTONS (Lizzy, after the Garden of Laminae mockup's character menu): a full-width
   bordered row with an icon, the label and a chevron at the right: the left column's menu, the
   Lore pane, and the Bestiary under Creatures (.slim). The chevron is drawn by CSS (a masked
   arrow in the text colour), and so is the panes' back arrow */
.rowbtns{display:flex; flex-direction:column; gap:var(--gap-list)}
.rowbtn{display:flex; align-items:center; gap:10px; width:100%; min-height:46px; padding:5px 12px 5px 8px; text-align:left;
  font:600 14px/1.1 var(--display); letter-spacing:.06em; color:var(--gold-hi);
  border:1px solid var(--line-strong); border-radius:4px; background:linear-gradient(180deg,#1c1828,#0e0c15); box-shadow:inset 0 0 0 1px rgba(0,0,0,.55);
  transition:background .15s, border-color .15s}
.rowbtn:hover{border-color:var(--gold); background:linear-gradient(180deg,#2a2040,#141020)}
.rowbtn img{width:32px; height:32px; object-fit:contain; flex-shrink:0; filter:drop-shadow(0 1px 2px #000)}
.rowbtn .nm{flex:1}
.rowbtn.slim{min-height:38px; font-size:13px; margin-top:6px}
.rowbtn.slim img{width:26px; height:26px}
:is(.rowbtn,.exrow)::after,.back::before{content:""; width:14px; height:14px; flex-shrink:0; background:var(--gold);
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 3l5 5-5 5'/%3E%3C/svg%3E") center/contain no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M5 3l5 5-5 5'/%3E%3C/svg%3E") center/contain no-repeat}
.back{display:grid; place-items:center; width:32px; height:32px; margin:-6px 0 -6px -6px; border-radius:50%; flex-shrink:0}
.back::before{transform:scaleX(-1); width:16px; height:16px}
.back:hover{background:rgba(201,164,106,.12)}
/* MAP (Session D2: moved into the right column, always on show): the map every look sends, as
   large as the column allows, with the territory, zone and skyzone under it */
.mapbox{display:flex; flex-direction:column; gap:6px; container-type:inline-size}
/* a little more room for the map (Lizzy), now the exits fold away */
.mapbox .panebody{max-height:190px; overflow:auto}
/* the right column's painting is a little shorter than it was, to leave Here with you room */
.placecol .roomcard .art{height:132px}
.placecol .roomcard > :is(.fx,.fog,.stars,.amb){height:132px}
/* MAP FOOTER (Lizzy): zone and skyzone on one line (the territory is the location card's title) */
/* zone and skyzone side by side, each a small label over its name (Lizzy) */
.mapfoot2{display:grid; grid-template-columns:repeat(auto-fit, minmax(0,1fr)); gap:4px 10px; text-align:center}
.mapfoot2:empty{display:none}
.mfi{display:flex; flex-direction:column; align-items:center; gap:3px; min-width:0; font-family:var(--serif)}
.mfk{color:var(--muted); font-size:12.5px}
.mfv{color:var(--text); font-size:12.5px; text-transform:uppercase; letter-spacing:.06em; overflow-wrap:anywhere}
/* HERE WITH YOU (Lizzy: one scrolling list, no tabs): People, Creatures (with the Bestiary) and
   Objects under headings that fold away, with their counts */
.here{display:flex; flex-direction:column; gap:6px; flex:1 1 auto!important; min-height:150px}
.herelist{flex:1 1 auto; min-height:0; overflow-y:auto; display:flex; flex-direction:column; gap:8px; padding-right:2px}
.hhead{display:flex; align-items:center; gap:8px; width:100%; padding:4px 2px; font:600 11.5px/1 var(--display); letter-spacing:.1em; text-transform:uppercase; color:var(--gold-hi); text-align:left}
.hhead::before{content:""; width:9px; height:9px; flex-shrink:0; border:solid var(--gold); border-width:0 1.6px 1.6px 0; transform:rotate(45deg) translate(-2px,-2px); transition:transform .15s}
.hgroup[data-closed="true"] .hhead::before{transform:rotate(-45deg)}
.hgroup[data-closed="true"] > :not(.hhead){display:none}
.hhead::after{content:""; flex:1; height:1px; background:linear-gradient(90deg,var(--line-strong),transparent)}
.hhead:hover{color:#fff}
.hn{min-width:22px; padding:2px 6px; border-radius:9px; background:rgba(143,99,210,.35); color:var(--text-hi); font:600 11px/1.1 var(--serif); letter-spacing:0; text-align:center; font-variant-numeric:tabular-nums}
.hn[data-n="0"]{background:none; color:var(--muted)}
.hgroup .plist{margin-top:0}
/* EXITS (Session D2; Lizzy: folded by default, under Objects): every exit as a row, its gem or
   door icon, its name, a closed door in words, the destination's #id for Admins, and a chevron;
   a tap opens its menu */
.exitlist{list-style:none; margin:4px 0 0; padding:0; display:flex; flex-direction:column; gap:3px}
.exitlist .empty{color:var(--muted); font-style:italic; font-size:14px; padding:4px 2px}
.exrow{display:flex; align-items:center; gap:9px; width:100%; min-height:30px; padding:1px 10px 1px 4px; border-bottom:1px solid var(--line); text-align:left; font-size:14.5px; color:var(--exit)}
.exrow:hover,.exrow[aria-expanded="true"]{background:rgba(143,99,210,.14); color:#fff}
.exrow img{width:22px; height:22px; object-fit:contain; flex-shrink:0; filter:drop-shadow(0 1px 2px #000)}
.exrow .nm{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; text-decoration:underline; text-decoration-color:rgba(134,187,238,.35); text-underline-offset:3px}
.exrow .exst{color:var(--muted); font-size:12.5px; font-style:italic}
.exrow .id{color:var(--muted); font-size:13px; font-variant-numeric:tabular-nums}
/* the right column's dividers: above Here with you and above the compass (Lizzy) */
.placecol > .divider{margin:8px auto 12px}
/* the location card opens the territory's CITYHELP or FORESTHELP (Lizzy): a button over the whole
   card, its title underlined on hover */
.cardlink{position:absolute; inset:0; z-index:3; width:100%; height:100%; border-radius:4px}
.cardlink:hover ~ .cap .terrname{text-decoration:underline; text-decoration-color:rgba(236,210,154,.5); text-underline-offset:4px}
.cardlink:focus-visible{outline-offset:-3px}
/* WEATHER EFFECTS (Lizzy, 2026-10-20): rain, snow, storms, fog and sand moving over the room
   card's painting, outdoors only, from mystic_weather (renderSky sets data-fx and data-lvl on
   .fx). Only the painting is covered, never the story. Each layer is an oversized
   pseudo-element moved by transform alone, so it never repaints; the reduced-motion rule at
   the end of this file stills it, and the Weather effects setting turns it off */
:is(.fx,.fog){position:absolute; inset:0; overflow:hidden; pointer-events:none}
/* each layer loops by --step, a whole number of its pattern tiles, so the loop never jumps */
.fx::before,.fx::after{content:""; position:absolute; inset:-200px -200px 0 -20px; opacity:0; --step:120px}
.app[data-fx="off"] :is(.fx,.fog,.stars,.amb),.app[data-art="off"] :is(.fx,.fog,.stars,.amb),.fog:not([data-lvl]){display:none}
.roomcard > :is(.fx,.fog,.stars,.amb){bottom:auto; height:150px}
.app[data-art="compact"] .roomcard > :is(.fx,.fog,.stars,.amb){height:86px}
/* STARS (Lizzy; renderSky sets data-lvl): outdoors after dark, under the fog and weather so
   clouds drift over them. Bright (2) on a clear night, faint (1) through light cloud, light fog
   or the evening's last light. A faint field of tiny stars shimmers behind SPARKLES (the <i>s
   mystic.js puts in from STAR_SPOTS; Lizzy: dots alone looked like stuck snow): a bright core
   with two thin crossed rays that fade at their tips, each swelling, glinting and shrinking on
   its own rhythm. All of it fades out down the painting, where the land is. They sit above
   the night wash, so they stay bright on a dark painting. Reduced motion leaves them still. */
.stars{position:absolute; inset:0; pointer-events:none; opacity:0; transition:opacity 2.5s ease;
  -webkit-mask-image:linear-gradient(180deg, #000, #000 28%, transparent 70%); mask-image:linear-gradient(180deg, #000, #000 28%, transparent 70%)}
.stars[data-lvl="1"]{opacity:.4}
.stars[data-lvl="2"]{opacity:1}
.stars::before{content:""; position:absolute; inset:0; opacity:.6; animation:fx-twinkle 4.2s ease-in-out infinite alternate;
  background-image:radial-gradient(circle at 14% 22%, #fff 0 .5px, transparent 1px), radial-gradient(circle at 63% 11%, #fff 0 .5px, transparent 1px),
    radial-gradient(circle at 38% 47%, rgba(255,255,255,.85) 0 .5px, transparent 1px), radial-gradient(circle at 86% 36%, #fff 0 .5px, transparent 1px),
    radial-gradient(circle at 7% 64%, #fff 0 .5px, transparent 1px);
  background-size:53px 37px, 71px 43px, 89px 59px, 61px 47px, 43px 53px}
.stars i{position:absolute; left:var(--x); top:var(--y); width:calc(16px * var(--s)); height:calc(16px * var(--s)); margin:calc(-8px * var(--s));
  background:radial-gradient(circle, #fff 0 1px, rgba(214,224,255,.55) 1.7px, transparent 3.2px),
    linear-gradient(90deg, transparent, rgba(236,241,255,.95) 50%, transparent) center / 100% 1px no-repeat,
    linear-gradient(0deg, transparent, rgba(236,241,255,.95) 50%, transparent) center / 1px 100% no-repeat;
  animation:fx-sparkle var(--d) ease-in-out var(--w) infinite}
@keyframes fx-twinkle{from{opacity:.75}to{opacity:.3}}
@keyframes fx-sparkle{0%,100%{transform:scale(.3) rotate(0deg); opacity:.35} 45%{transform:scale(1) rotate(12deg); opacity:1} 60%{transform:scale(.8) rotate(15deg); opacity:.85}}
/* MOTION (Lizzy, Session D8): Settings > Motion sets data-motion on the page root. Off stills every animation and transition,
   pseudo-elements included (a bare * never reached them, so the old reduced-motion rule left the weather, the stars and the
   unread ping moving). Reduced does the same, then each new touch below puts back a slow fade (soft-glow: opacity only,
   nothing travels) with an !important rule of higher specificity. Weather effects stay still under both. */
@keyframes soft-glow{0%,100%{opacity:0} 50%{opacity:.7}}
/* SCENE TOUCHES (Lizzy, Session D8; renderAmb in mystic.js sets data-kind on .amb and fills it with <i> particles; the layer
   follows Weather effects). Each particle rests at --x / --y (the individual translate property, in layer heights via
   container query units) and its keyframes move it from there by transform, so it only ever composites. They are sparse,
   slow and invisible between cycles, so even with the stars and the weather above them the painting stays calm. Under
   Reduced they only fade in place; under Off they are not shown (they have no still form). */
.amb{position:absolute; inset:0; overflow:hidden; pointer-events:none; container-type:size; opacity:0; transition:opacity 2.5s ease}
.amb[data-kind]{opacity:1}
.amb:not([data-kind]),.amb:not([data-kind]) *{animation-play-state:paused !important}
.amb i{position:absolute; left:var(--x); top:0; translate:0 var(--y); opacity:0}
.app[data-art="compact"] .amb i:nth-child(n+6){display:none}
.amb[data-kind="fireflies"] i{width:calc(5px * var(--s)); height:calc(5px * var(--s)); margin:-2px 0 0 -2px; border-radius:50%; background:#e8f58a;
  box-shadow:0 0 6px 2px rgba(222,240,110,.65); animation:amb-firefly var(--d) ease-in-out var(--w) infinite}
@keyframes amb-firefly{0%{opacity:0; transform:translate(0,0)} 20%{opacity:.9} 45%{opacity:.25} 65%{opacity:.85} 100%{opacity:0; transform:translate(var(--dx), -14px)}}
.amb:is([data-kind="leaves"],[data-kind="blossoms"]) i{width:calc(15px * var(--s)); height:calc(10px * var(--s)); border-radius:0 100% 0 100%;
  background:var(--c, #c8642a); box-shadow:0 0 2px rgba(0,0,0,.4); animation:amb-fall var(--d) linear var(--w) infinite}
.amb[data-kind="leaves"] i:nth-child(3n+2){--c:#d89a2e}
.amb[data-kind="leaves"] i:nth-child(3n){--c:#a8452a}
.amb[data-kind="blossoms"] i{--c:#f6c9d8; width:calc(11px * var(--s)); height:calc(11px * var(--s)); border-radius:60% 0 60% 60%}
.amb[data-kind="blossoms"] i:nth-child(3n+2){--c:#fbe3ea}
.amb[data-kind="blossoms"] i:nth-child(3n){--c:#eeb0c6}
@keyframes amb-fall{0%{opacity:0; transform:translate(0, calc(-1 * var(--y) - 12px)) rotate(0)}
  8%{opacity:.95} 50%{transform:translate(calc(var(--dx) * -.5), calc(55cqh - var(--y) - 6px)) rotate(150deg)}
  90%{opacity:.95} 100%{opacity:0; transform:translate(var(--dx), calc(110cqh - var(--y))) rotate(300deg)}}
.amb[data-kind="bubbles"] i{width:calc(13px * var(--s)); height:calc(13px * var(--s)); border-radius:50%; border:1.5px solid rgba(225,247,255,.9);
  background:radial-gradient(circle at 30% 30%, rgba(255,255,255,.75), rgba(190,230,255,.2) 60%); box-shadow:0 0 4px rgba(190,235,255,.5); animation:amb-rise var(--d) ease-in var(--w) infinite}
@keyframes amb-rise{0%{opacity:0; transform:translate(0, calc(110cqh - var(--y)))} 12%{opacity:.8}
  50%{transform:translate(var(--dx), calc(55cqh - var(--y) - 5px))} 85%{opacity:.7} 100%{opacity:0; transform:translate(calc(var(--dx) * -.6), calc(-1 * var(--y) - 10px))}}
/* candle / firelight: two warm glows low in the painting on their own slow, uneven rhythms; faint by day, fuller after dark */
/* (screen blending lets the glow light the dark painting up instead of just laying a thin tint over it, which the night wash swallowed) */
/* (Lizzy: the big slow glows were too obvious and read as a dimming lamp. Now small flames, each a hot core inside a soft halo, on quick uneven
   jitters of their own: the ::before/::after pair plus the layer's own background, whose two flames are driven by the fast second keyframes) */
/* (Lizzy: still washed the painting out in yellow. Now two low, warm-orange pools sitting in the lower half, much fainter, so the painting keeps its colours) */
.amb[data-kind="flicker"]{opacity:.3; mix-blend-mode:screen;
  background:radial-gradient(ellipse 36% 26% at 24% 72%, rgba(255,150,70,.4), rgba(255,125,50,.2) 40%, rgba(230,95,35,.07) 75%, transparent 100%),
             radial-gradient(ellipse 34% 25% at 78% 68%, rgba(255,155,75,.36), rgba(255,128,52,.18) 40%, rgba(230,95,35,.06) 75%, transparent 100%);
  animation:amb-flame 1.1s steps(1,end) infinite}
.art:is([data-tod="night"],[data-tod="predawn"],[data-tod="evening"],[data-tod="dusk"]) ~ .amb[data-kind="flicker"]{opacity:.55}
.amb[data-kind="flicker"]::before,.amb[data-kind="flicker"]::after{content:""; position:absolute; inset:0; transform-origin:50% 60%}
.amb[data-kind="flicker"]::before{background:radial-gradient(ellipse 50% 36% at 26% 70%, rgba(255,140,60,.18), rgba(240,110,45,.07) 55%, transparent 100%); animation:amb-flicker 2.3s ease-in-out infinite}
.amb[data-kind="flicker"]::after{background:radial-gradient(ellipse 48% 34% at 76% 66%, rgba(255,148,66,.16), rgba(240,115,48,.06) 55%, transparent 100%); animation:amb-flicker 1.7s ease-in-out -.8s infinite}
@keyframes amb-flicker{0%,100%{opacity:.55; transform:scale(1)} 9%{opacity:.9; transform:scale(1.04)} 21%{opacity:.6; transform:scale(.98)} 34%{opacity:1; transform:scale(1.05)}
  47%{opacity:.7; transform:scale(1)} 58%{opacity:.95; transform:scale(1.03)} 73%{opacity:.5; transform:scale(.97)} 86%{opacity:.85; transform:scale(1.02)}}
@keyframes amb-flame{0%{filter:brightness(1)} 13%{filter:brightness(1.25)} 27%{filter:brightness(.8)} 38%{filter:brightness(1.15)} 52%{filter:brightness(.9)} 61%{filter:brightness(1.3)} 79%{filter:brightness(.85)} 90%{filter:brightness(1.1)}}
.app[data-motion="reduced"] .amb:is([data-kind="fireflies"],[data-kind="leaves"],[data-kind="blossoms"],[data-kind="bubbles"]) i{
  animation:soft-glow calc(var(--d) * 1.4) ease-in-out var(--w) infinite !important}
.app[data-motion="reduced"] .amb[data-kind="flicker"]{animation:none !important}
.app[data-motion="reduced"] .amb[data-kind="flicker"]::before,.app[data-motion="reduced"] .amb[data-kind="flicker"]::after{
  animation:amb-breathe 14s ease-in-out infinite !important}
@keyframes amb-breathe{0%,100%{opacity:.6} 50%{opacity:.85}}
/* WIND (Lizzy): data-wind 1-3 on both layers speeds up drifting clouds and fog (--windx divides
   their loop time) and slants the rain (--slant); snow drives sideways in any wind */
[data-wind="1"]{--windx:1.8; --slant:22deg}
[data-wind="2"]{--windx:3; --slant:32deg}
[data-wind="3"]{--windx:5; --slant:42deg}
/* the weather's tint on the painting itself (no animation): darker under rain and storms,
   paler in snow, ochre in a sandstorm */
.fx:is([data-fx="rain"],[data-fx="freezing"]){background:linear-gradient(180deg, rgba(24,32,58,.42), rgba(24,32,58,.18))}
.fx[data-fx="storm"]{background:linear-gradient(180deg, rgba(14,14,32,.58), rgba(14,14,32,.28))}
/* CLOUDY (Lizzy): soft clouds drifting across the top of the painting, two layers at different
   speeds (each tile repeats, so the loop never jumps); heavier cloud is denser and greyer */
.fx[data-fx="clouds"]{background:linear-gradient(180deg, rgba(60,64,80,.2), transparent 70%)}
.fx[data-fx="clouds"]::before,.fx[data-fx="clouds"]::after{inset:0 -100% 0 0; opacity:.75; background-size:50% 100%; animation:fx-drift calc(70s / var(--windx, 1)) linear infinite;
  background-image:radial-gradient(ellipse 17% 13% at 12% 16%, rgba(238,240,248,.9), transparent 70%), radial-gradient(ellipse 22% 11% at 24% 23%, rgba(226,229,242,.8), transparent 70%),
    radial-gradient(ellipse 15% 10% at 58% 10%, rgba(238,240,248,.85), transparent 70%), radial-gradient(ellipse 21% 12% at 71% 19%, rgba(222,226,240,.75), transparent 70%),
    radial-gradient(ellipse 13% 9% at 90% 28%, rgba(232,234,246,.7), transparent 70%)}
.fx[data-fx="clouds"]::after{opacity:.5; animation-duration:calc(42s / var(--windx, 1));
  background-image:radial-gradient(ellipse 19% 12% at 34% 36%, rgba(214,218,232,.8), transparent 70%), radial-gradient(ellipse 14% 9% at 46% 30%, rgba(230,232,244,.75), transparent 70%),
    radial-gradient(ellipse 18% 11% at 84% 42%, rgba(214,218,232,.7), transparent 70%), radial-gradient(ellipse 12% 8% at 4% 40%, rgba(226,229,242,.7), transparent 70%)}
.fx[data-fx="clouds"][data-lvl="2"]{background:linear-gradient(180deg, rgba(50,54,70,.32), transparent 78%)}
.fx[data-fx="clouds"][data-lvl="2"]::before{opacity:.9}
.fx[data-fx="clouds"][data-lvl="3"]{background:linear-gradient(180deg, rgba(40,44,58,.48), rgba(40,44,58,.14))}
.fx[data-fx="clouds"][data-lvl="3"]::before,.fx[data-fx="clouds"][data-lvl="3"]::after{opacity:1; filter:grayscale(1) brightness(.72)}
/* weather after dark is dimmed with the painting (the night tint below), so clouds, fog and
   snow don't glow white over a night scene */
.art:is([data-tod="night"],[data-tod="predawn"],[data-tod="evening"]) ~ .fx{filter:brightness(.5) saturate(.7)}
/* fog covers the whole painting, so dimming it as hard leaves a grey void, and any blur smears
   a dark painting flat: after dark it keeps a lighter dim and a thinner wash, and no blur */
.art:is([data-tod="night"],[data-tod="predawn"],[data-tod="evening"]) ~ .fog[data-lvl]{filter:brightness(.8) saturate(.7);
  background:linear-gradient(180deg, rgba(150,160,190,.14), rgba(150,160,190,.28));
  -webkit-backdrop-filter:saturate(.8); backdrop-filter:saturate(.8)}
.art:is([data-tod="night"],[data-tod="predawn"],[data-tod="evening"]) ~ .fog[data-lvl]::before,
.art:is([data-tod="night"],[data-tod="predawn"],[data-tod="evening"]) ~ .fog[data-lvl]::after{opacity:.45}
.fx[data-fx="snow"]{background:linear-gradient(180deg, rgba(225,232,245,.22), rgba(225,232,245,.08))}
/* a blizzard is a white-out (Lizzy: not dense enough): a heavy pale wash that softens the painting */
.fx[data-fx="blizzard"]{background:linear-gradient(180deg, rgba(228,234,246,.52), rgba(228,234,246,.32));
  -webkit-backdrop-filter:blur(1.5px) saturate(.6); backdrop-filter:blur(1.5px) saturate(.6)}
.fx[data-fx="sand"]{background:rgba(150,100,40,.28)}
/* rain and freezing rain: short slanted streaks falling, faster and thicker with intensity */
.fx:is([data-fx="rain"],[data-fx="storm"],[data-fx="freezing"])::before{opacity:.8;
  background:
    radial-gradient(ellipse 1px 9px at 12% 20%, rgba(228,236,255,.95) 55%, transparent),
    radial-gradient(ellipse 1px 7px at 58% 68%, rgba(228,236,255,.85) 55%, transparent),
    radial-gradient(ellipse 1px 10px at 36% 52%, rgba(228,236,255,.75) 55%, transparent),
    radial-gradient(ellipse 1px 8px at 84% 34%, rgba(228,236,255,.9) 55%, transparent),
    radial-gradient(ellipse 1px 6px at 70% 8%, rgba(228,236,255,.7) 55%, transparent);
  background-size:38px 60px, 38px 60px, 53px 40px, 53px 40px, 29px 30px; animation:fx-rain .6s linear infinite}
.fx[data-lvl="1"]:is([data-fx="rain"],[data-fx="storm"],[data-fx="freezing"])::before{opacity:.5; animation-duration:.8s}
.fx[data-lvl="3"]:is([data-fx="rain"],[data-fx="storm"],[data-fx="freezing"])::before{opacity:1; animation-duration:.42s}
.fx[data-fx="freezing"]::before{filter:hue-rotate(-25deg) brightness(1.3)}
/* rain slanting in the wind turns further, so its layer reaches further past the painting */
.fx[data-wind]:is([data-fx="rain"],[data-fx="storm"],[data-fx="freezing"])::before{inset:-320px}
/* storm: a lightning flash every few seconds */
.fx[data-fx="storm"]::after{inset:0; background:radial-gradient(ellipse at 30% 10%, rgba(235,235,255,.85), rgba(200,200,255,.25) 60%, transparent); animation:fx-flash 7s linear infinite}
.fx[data-fx="storm"][data-lvl="3"]::after{animation-duration:4s}
/* snow and blizzard: two layers of flakes at different speeds; a blizzard drives sideways */
.fx:is([data-fx="snow"],[data-fx="blizzard"])::before,.fx:is([data-fx="snow"],[data-fx="blizzard"])::after{opacity:.85;
  background:radial-gradient(circle at 20% 30%, #fff 0 2px, transparent 2.7px), radial-gradient(circle at 70% 60%, #fff 0 1.6px, transparent 2.3px),
             radial-gradient(circle at 45% 85%, rgba(255,255,255,.85) 0 1.3px, transparent 2px), radial-gradient(circle at 88% 15%, #fff 0 1.8px, transparent 2.5px),
             radial-gradient(circle at 8% 70%, rgba(255,255,255,.75) 0 1.2px, transparent 1.9px);
  background-size:60px 60px; animation:fx-fall 4.5s linear infinite}
.fx:is([data-fx="snow"],[data-fx="blizzard"])::after{background-size:90px 90px; --step:180px; opacity:.6; animation-duration:9s}
.fx[data-fx="snow"][data-lvl="1"]::after{display:none}
.fx[data-fx="snow"][data-lvl="3"]::before{background-size:40px 40px}
.fx[data-fx="blizzard"]::before,.fx[data-fx="blizzard"]::after{animation-name:fx-drive; animation-duration:1.3s; opacity:.95; background-size:36px 36px; --step:108px}
.fx[data-fx="blizzard"]::after{animation-duration:2.1s; background-size:54px 54px; --step:162px; opacity:.75}
/* .fx3: a third layer (mystic.js puts it in every .fx). A blizzard's finest, fastest flakes */
.fx3{display:none; position:absolute; pointer-events:none}
.fx[data-fx="blizzard"] .fx3{display:block; inset:-200px -200px 0 -20px; opacity:.8; --step:72px; animation:fx-drive .8s linear infinite;
  background:radial-gradient(circle at 25% 35%, #fff 0 1px, transparent 1.6px), radial-gradient(circle at 75% 80%, rgba(255,255,255,.9) 0 .9px, transparent 1.5px),
             radial-gradient(circle at 60% 15%, #fff 0 1.1px, transparent 1.7px);
  background-size:24px 24px}
/* TORNADO (Lizzy: make it ominous): a sick green-grey sky, dark clouds racing over the top, a
   dark funnel twisting down the painting, and hail and debris driving through. The funnel's
   shape is a blurred SVG mask (soft edges; Lizzy: a clip-path's edges were too sharp) that also
   fades it into the clouds and the ground. Inside it, near-horizontal bands slide sideways,
   which reads as spin (Lizzy: vertical stripes didn't), shaded darker through the middle so it
   looks round; the whole funnel sways. Reduced motion leaves it still */
/* (Lizzy: darker up top, lighter below) */
.fx[data-fx="tornado"]{background:radial-gradient(ellipse 24% 20% at 65% 97%, rgba(66,56,40,.75), transparent 70%),
  linear-gradient(180deg, rgba(40,48,32,.72), rgba(84,96,62,.4) 45%, rgba(48,48,38,.24))}
/* a dense, dark cloud deck racing over the top (Lizzy: bigger, more cover), with the wind's
   streaks through the middle; both faster in a stronger wind */
.fx[data-fx="tornado"]::before{inset:0 -100% 0 0; opacity:.95; background-size:50% 100%; animation:fx-drift calc(9s / var(--windx, 1)) linear infinite;
  background-image:radial-gradient(ellipse 34% 26% at 10% 4%, rgba(22,26,24,.97), transparent 72%), radial-gradient(ellipse 38% 24% at 38% 8%, rgba(30,34,30,.95), transparent 72%),
    radial-gradient(ellipse 34% 26% at 66% 3%, rgba(18,22,20,.97), transparent 72%), radial-gradient(ellipse 30% 22% at 92% 9%, rgba(34,38,32,.93), transparent 72%),
    radial-gradient(ellipse 26% 14% at 24% 22%, rgba(44,48,40,.8), transparent 70%), radial-gradient(ellipse 28% 13% at 80% 24%, rgba(44,48,40,.75), transparent 70%),
    radial-gradient(ellipse 16% 2px at 20% 48%, rgba(220,226,210,.55), transparent), radial-gradient(ellipse 20% 2px at 62% 58%, rgba(220,226,210,.5), transparent),
    radial-gradient(ellipse 13% 1.5px at 88% 40%, rgba(220,226,210,.5), transparent)}
/* hailstones and dark debris driving down through the scene, over the funnel */
.fx[data-fx="tornado"]::after{opacity:.85; --step:180px; animation:fx-drive .7s linear infinite; background-size:90px 90px;
  background-image:radial-gradient(circle at 11% 17%, rgba(240,245,255,.95) 0 1.4px, transparent 2px), radial-gradient(circle at 47% 8%, rgba(235,240,250,.85) 0 1px, transparent 1.6px),
    radial-gradient(circle at 78% 39%, rgba(240,245,255,.9) 0 1.3px, transparent 1.9px), radial-gradient(circle at 29% 63%, rgba(235,240,250,.8) 0 1.1px, transparent 1.7px),
    radial-gradient(circle at 66% 81%, rgba(240,245,255,.95) 0 1.5px, transparent 2.1px), radial-gradient(circle at 91% 72%, rgba(30,26,20,.85) 0 1.3px, transparent 1.9px),
    radial-gradient(circle at 54% 46%, rgba(30,26,20,.8) 0 1px, transparent 1.6px), radial-gradient(circle at 6% 88%, rgba(30,26,20,.85) 0 1.2px, transparent 1.8px)}
.fx[data-fx="tornado"] .fx3{display:block; left:49%; top:-4%; width:32%; height:108%; transform-origin:50% 0; animation:fx-sway 5s ease-in-out infinite alternate;
  -webkit-mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-opacity='0'/%3E%3Cstop offset='.14' stop-opacity='1'/%3E%3Cstop offset='.86' stop-opacity='1'/%3E%3Cstop offset='1' stop-opacity='0'/%3E%3C/linearGradient%3E%3Cfilter id='b' x='-20%25' y='-20%25' width='140%25' height='140%25'%3E%3CfeGaussianBlur stdDeviation='3'/%3E%3C/filter%3E%3C/defs%3E%3Cpath d='M4 4H96C84 18 70 36 63 58C59 72 58 86 57 96H45C44 86 43 72 39 58C32 36 16 18 4 4Z' fill='url%28%23g%29' filter='url%28%23b%29'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
  mask:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100' preserveAspectRatio='none'%3E%3Cdefs%3E%3ClinearGradient id='g' x1='0' y1='0' x2='0' y2='1'%3E%3Cstop offset='0' stop-opacity='0'/%3E%3Cstop offset='.14' stop-opacity='1'/%3E%3Cstop offset='.86' stop-opacity='1'/%3E%3Cstop offset='1' stop-opacity='0'/%3E%3C/linearGradient%3E%3Cfilter id='b' x='-20%25' y='-20%25' width='140%25' height='140%25'%3E%3CfeGaussianBlur stdDeviation='3'/%3E%3C/filter%3E%3C/defs%3E%3Cpath d='M4 4H96C84 18 70 36 63 58C59 72 58 86 57 96H45C44 86 43 72 39 58C32 36 16 18 4 4Z' fill='url%28%23g%29' filter='url%28%23b%29'/%3E%3C/svg%3E") center / 100% 100% no-repeat}
/* a solid dark body, with faint bands sliding over it: they repeat every 14px across their
   slant, so sliding 14px / sin(8deg) loops without a jump */
.fx[data-fx="tornado"] .fx3{background:rgba(64,68,58,.86)}
.fx[data-fx="tornado"] .fx3 span{position:absolute; inset:0 -110px 0 0; animation:fx-spin .45s linear infinite;
  background:repeating-linear-gradient(172deg, rgba(0,0,0,.28) 0, rgba(200,206,186,.09) 5px, rgba(0,0,0,.16) 9px, rgba(0,0,0,.28) 14px)}
.fx[data-fx="tornado"] .fx3::after{content:""; position:absolute; inset:0;
  background:linear-gradient(90deg, rgba(160,170,140,.3), transparent 26%, rgba(0,0,0,.3) 52%, transparent 80%, rgba(160,170,140,.22))}
.fx[data-fx="tornado"][data-lvl="1"] .fx3{opacity:.75}
@keyframes fx-sway{from{transform:translateX(-7%) skewX(5deg)}to{transform:translateX(7%) skewX(-5deg)}}
@keyframes fx-spin{from{transform:translateX(0)}to{transform:translateX(calc(-14px / .139173))}}
.fx[data-fx="snow"][data-wind]::before,.fx[data-fx="snow"][data-wind]::after{animation-name:fx-drive}
/* sandstorm: ochre haze driving across */
.fx[data-fx="sand"]::before{inset:0 -100% 0 0; opacity:.75; background-size:50% 100%;
  background-image:radial-gradient(ellipse 30% 40% at 20% 60%, rgba(220,220,230,.75), transparent 70%), radial-gradient(ellipse 35% 35% at 70% 40%, rgba(220,220,230,.6), transparent 70%),
    linear-gradient(180deg, transparent 20%, rgba(210,210,220,.45) 80%);
  filter:sepia(1) saturate(2.4) hue-rotate(-12deg) brightness(.85); animation:fx-drift 6s linear infinite}
/* WINDY (Lizzy): faint streaks sweeping across the painting, faster and fuller as it rises */
.fx[data-fx="wind"]::before,.fx[data-fx="wind"]::after{inset:0 -100% 0 0; opacity:.55; background-size:50% 100%; animation:fx-drift 2.6s linear infinite;
  background-image:radial-gradient(ellipse 15% 2px at 18% 22%, rgba(240,244,252,.95), transparent), radial-gradient(ellipse 20% 2px at 63% 40%, rgba(240,244,252,.85), transparent),
    radial-gradient(ellipse 12% 2px at 37% 62%, rgba(240,244,252,.9), transparent), radial-gradient(ellipse 17% 2px at 84% 77%, rgba(240,244,252,.85), transparent),
    radial-gradient(ellipse 10% 1.5px at 6% 86%, rgba(240,244,252,.8), transparent)}
.fx[data-fx="wind"]::after{opacity:.4; animation-duration:4s;
  background-image:radial-gradient(ellipse 13% 1.5px at 8% 48%, rgba(240,244,252,.9), transparent), radial-gradient(ellipse 18% 1.5px at 50% 15%, rgba(240,244,252,.8), transparent),
    radial-gradient(ellipse 14% 1.5px at 74% 58%, rgba(240,244,252,.85), transparent), radial-gradient(ellipse 9% 1.5px at 92% 32%, rgba(240,244,252,.8), transparent)}
.fx[data-fx="wind"][data-lvl="1"]::before{opacity:.4; animation-duration:3.8s}
.fx[data-fx="wind"][data-lvl="1"]::after{display:none}
.fx[data-fx="wind"][data-lvl="3"]::before{opacity:.75; animation-duration:1.4s}
.fx[data-fx="wind"][data-lvl="3"]::after{opacity:.55; animation-duration:2.2s}
/* FOG, its own layer under the weather (the game's fog is separate from its weather, so rain,
   snow or clouds show with it): a pale wash that also softens and greys the painting behind
   it (Lizzy: wisps alone were too faint), with two mist banks drifting at different speeds,
   faster in wind; dense fog is thicker */
.fog::before,.fog::after{content:""; position:absolute; inset:0 -100% 0 0; background-size:50% 100%}
.fog[data-lvl]{background:linear-gradient(180deg, rgba(205,210,220,.24), rgba(205,210,220,.46));
  -webkit-backdrop-filter:blur(1px) saturate(.75); backdrop-filter:blur(1px) saturate(.75)}
/* dense fog, eased a touch (Lizzy: with a storm on top you could barely see the painting) */
.fog[data-lvl="3"]{background:linear-gradient(180deg, rgba(205,210,220,.33), rgba(205,210,220,.56));
  -webkit-backdrop-filter:blur(2px) saturate(.62); backdrop-filter:blur(2px) saturate(.62)}
.fog[data-lvl]::before{opacity:.55; animation:fx-drift calc(26s / var(--windx, 1)) linear infinite;
  background-image:radial-gradient(ellipse 30% 40% at 20% 60%, rgba(220,220,230,.75), transparent 70%), radial-gradient(ellipse 35% 35% at 70% 40%, rgba(220,220,230,.6), transparent 70%),
    linear-gradient(180deg, transparent 20%, rgba(210,210,220,.45) 80%)}
.fog[data-lvl]::after{opacity:.6; animation:fx-drift calc(44s / var(--windx, 1)) linear infinite;
  background-image:radial-gradient(ellipse 40% 22% at 30% 78%, rgba(225,228,236,.85), transparent 70%),
    radial-gradient(ellipse 34% 18% at 78% 70%, rgba(225,228,236,.75), transparent 70%)}
.fog[data-lvl="3"]::before,.fog[data-lvl="3"]::after{opacity:.74}
@keyframes fx-fall{from{transform:translateY(0)}to{transform:translateY(var(--step))}}
@keyframes fx-rain{from{transform:rotate(var(--slant, 12deg)) translateY(0)}to{transform:rotate(var(--slant, 12deg)) translateY(var(--step))}}
@keyframes fx-drive{from{transform:translate(0,0)}to{transform:translate(calc(-1 * var(--step)),var(--step))}}
@keyframes fx-drift{from{transform:translateX(0)}to{transform:translateX(-50%)}}
@keyframes fx-flash{0%,91%,95%,100%{opacity:0}92%{opacity:.9}93%{opacity:.15}94%{opacity:.7}}
/* exits the compass can't show ("gate", "portal") and a conjured portal: an icon (Lizzy's picks:
   exit-gate, exit-door, exit-portal) with the exit's name, under both compasses; a tap opens
   their menu (renderExits, TAP MENUS in mystic.js). EXIT BUTTONS (Lizzy, 2026-10-20): smaller,
   two to a row at equal widths (one alone spans the row, an odd last one sits centred at the
   same width); on phones they stack, all the same width */
.xexits{display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:6px}
.xexits:empty{display:none}
.xexit:only-child{grid-column:1 / -1}
.xexit:last-child:nth-child(odd):not(:only-child){grid-column:1 / -1; justify-self:center; width:calc(50% - 3px)}
/* side by side as on desktop (Lizzy: stacked, each was a huge full-width button) */
.floatcompass .xexits{margin:2px 0 4px}
.xexit{display:flex; align-items:center; gap:5px; min-width:0; min-height:32px; padding:2px 8px 2px 3px; font:600 10.5px/1.1 var(--display); letter-spacing:.06em; color:var(--gold-hi);
  border:1px solid var(--line-strong); border-radius:4px; background:linear-gradient(180deg,#1c1828,#0e0c15); box-shadow:inset 0 0 0 1px rgba(0,0,0,.55); transition:box-shadow .15s}
.xexit span{min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.xexit img{width:26px; height:26px; flex-shrink:0; object-fit:contain; filter:drop-shadow(0 1px 2px #000)}
.xexit:hover,.xexit[aria-expanded="true"]{box-shadow:inset 0 0 0 1px rgba(0,0,0,.55), 0 0 10px rgba(143,99,210,.45); color:#fff}
/* long presses open menus, so phones mustn't pop their own callout or select the text */
[data-menu]{-webkit-touch-callout:none; -webkit-user-select:none; user-select:none}
.exitgrid{display:grid; grid-template-columns:repeat(5,1fr); gap:6px}
.plist{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--gap-list)}
.plist li{display:flex; align-items:center; gap:8px; padding:7px 9px; border:1px solid var(--line); border-radius:4px; background:rgba(255,255,255,.02); font-size:15px}
.plist li > span{flex:1; min-width:0}
.plist .acts{display:flex; gap:4px}
.plist .empty{color:var(--muted); border-style:dashed; font-style:italic}
.plist .id{color:var(--muted); font-size:13px}
.plist li > span:first-child{overflow-wrap:anywhere}
/* TAPPABLE ROWS (the Who list, the room's People, Creatures and Objects): tap or right-click a
   row for its menu below */
.plist li:has(> .wn){padding:0}
.plist .wn{flex:1; min-width:0; text-align:left; padding:7px 9px; font-size:15px; overflow-wrap:anywhere}
/* HERE WITH YOU rows: the name, then the (#id) at the row's right edge (Lizzy) */
.plist .wn:has(> .nm){display:flex; align-items:baseline; gap:8px}
.plist .wn > .nm{flex:1; min-width:0}
.plist .wn > .id{flex-shrink:0; white-space:nowrap}
.plist .wn:hover,.plist .wn[aria-expanded="true"]{color:var(--gold-hi)}
.plist li:has(.wn[aria-expanded="true"]){border-color:var(--line-strong); background:rgba(143,99,210,.12)}
/* HERE WITH YOU (Lizzy): the room column's lists are about this room, not the realm */
/* HERE WITH YOU's heading reads as a section, above its People / Creatures / Objects headings (Lizzy) */
.herelabel{margin:0; font-size:14px; letter-spacing:.14em; color:var(--gold-hi)}
/* the right column's headings are h2s (Phase 6: a heading per region), styled as labels */
h2.label{margin:0}
/* the tap menu, under what was tapped or at the pointer */
.ctxmenu{position:fixed; z-index:56; min-width:190px; max-width:min(280px, calc(100vw - 16px)); padding:6px; display:flex; flex-direction:column;
  background:var(--panel-2); border:1px solid var(--line-strong); border-radius:6px; box-shadow:0 14px 34px rgba(0,0,0,.7)}
.ctxmenu .mh{padding:5px 8px 7px; margin-bottom:3px; border-bottom:1px solid var(--line); font:600 13px/1.25 var(--display); color:var(--gold-hi); letter-spacing:.03em; overflow-wrap:anywhere}
.ctxmenu button{display:flex; justify-content:space-between; gap:12px; padding:7px 8px; border-radius:4px; font-size:15px; text-align:left; color:var(--text)}
.ctxmenu button:hover,.ctxmenu button:focus-visible{background:rgba(143,99,210,.22); color:var(--text-hi)}
.ctxmenu button small{color:var(--muted); font-size:12.5px; font-style:italic}
/* (the highlighted choice's hint is brighter: the menu's first choice can carry one, and its violet needs the contrast) */
.ctxmenu button:hover small,.ctxmenu button:focus-visible small{color:var(--text)}
.ctxmenu .mh ~ .mh{margin-top:4px}
/* a heading's quieter note (a skill's rank, Lizzy) */
.ctxmenu .mh small{font:400 13.5px/1 var(--serif); letter-spacing:0; color:var(--muted)}
/* the Set as variable list inside a tap menu: it scrolls rather than running off the screen, and its search and name fields are the pane's */
.ctxmenu .csmlist{display:flex; flex-direction:column; max-height:min(46vh, 320px); overflow-y:auto; scrollbar-width:thin; scrollbar-color:var(--violet-deep) transparent}
.ctxmenu .csmlist button[hidden]{display:none}
.ctxmenu .csmfind,.ctxmenu .csmnew .cstext{margin:2px 0 6px; font-size:14px}
.ctxmenu .csmbtns{display:flex; gap:6px}
.ctxmenu .csmbtns button{flex:1; justify-content:center; font:600 12px/1 var(--display); letter-spacing:.08em; border:1px solid var(--line-strong); padding:9px 8px}
.ctxmenu .csmnew .csmsg{padding:0 8px; font-size:14px}
.ctxmenu .mnote{margin:4px 8px 6px; color:var(--muted); font-style:italic; font-size:14px}

/* compass */
.compass-wrap{display:flex; flex-direction:column; gap:8px}
.compass{--cs:186px; --bs:46px; display:flex; align-items:center; justify-content:center; gap:4px}
.ring{position:relative; width:var(--cs); height:var(--cs); flex-shrink:0; border-radius:50%;
  background:radial-gradient(circle, rgba(143,99,210,.22), transparent 62%)}
.ring::before{content:""; position:absolute; inset:22%; border-radius:50%; border:1px solid var(--line-strong);
  box-shadow:0 0 0 5px rgba(0,0,0,.35), inset 0 0 22px rgba(0,0,0,.85)}
.ring::after{content:""; position:absolute; inset:9%; border-radius:50%; border:1px dashed rgba(201,164,106,.18)}
.cdir{position:absolute; left:50%; top:50%; width:var(--bs); height:var(--bs); margin:calc(var(--bs) / -2) 0 0 calc(var(--bs) / -2); z-index:1;
  transform:rotate(var(--a)) translateY(calc(var(--cs) / -2 + var(--bs) / 2)) rotate(calc(-1 * var(--a))); transition:opacity .2s, filter .2s}
.cdir img{width:100%; height:100%; object-fit:contain; transition:filter .15s}
/* COMPASS STAR (LOOK): fills the inner circle (22% inset = 56% of the ring), its spikes just
   crossing the circle's edge, under the gems. A violet aura breathes behind it and a field of
   motes turns slowly around it. Only transform and opacity animate (no repaint per frame), and
   the reduced-motion rule at the end of this file stills both. */
.cdir.center{--ctr:calc(var(--cs) * .6); z-index:0; transform:none; width:var(--ctr); height:var(--ctr); margin:calc(var(--ctr) / -2) 0 0 calc(var(--ctr) / -2)}
.cdir.center img{position:relative; filter:drop-shadow(0 0 5px rgba(195,162,245,.55))}
.cdir.center::before{content:""; position:absolute; inset:-14%; border-radius:50%; pointer-events:none;
  background:radial-gradient(circle, rgba(214,190,255,.55) 0, rgba(143,99,210,.32) 30%, rgba(92,62,160,.12) 52%, transparent 70%);
  animation:star-aura 4.8s ease-in-out infinite alternate}
.cdir.center::after{content:""; position:absolute; inset:-6%; border-radius:50%; pointer-events:none;
  background:
    radial-gradient(circle at 18% 30%, #fff 0 1px, transparent 1.6px), radial-gradient(circle at 80% 22%, #e8d8ff 0 1.2px, transparent 2px),
    radial-gradient(circle at 70% 82%, #fff 0 .8px, transparent 1.5px), radial-gradient(circle at 26% 76%, #ecd29a 0 1px, transparent 1.7px),
    radial-gradient(circle at 50% 8%, #e8d8ff 0 .8px, transparent 1.4px), radial-gradient(circle at 92% 55%, #fff 0 .9px, transparent 1.5px),
    radial-gradient(circle at 8% 52%, #ecd29a 0 .8px, transparent 1.4px), radial-gradient(circle at 44% 94%, #fff 0 1px, transparent 1.6px);
  animation:star-motes 38s linear infinite, star-twinkle 3.1s ease-in-out infinite alternate}
@keyframes star-aura{from{opacity:.5; transform:scale(.9)}to{opacity:1; transform:scale(1.07)}}
@keyframes star-motes{to{transform:rotate(360deg)}}
@keyframes star-twinkle{from{opacity:.35}to{opacity:.95}}
.cdir.center:hover img{filter:drop-shadow(0 0 9px rgba(214,190,255,.95)) brightness(1.18)}
/* side gems (in/out, up/down) carry a text label under the gem (Lizzy's pick, 2026-10-20):
   the gem art alone didn't tell IN from OUT, or OUT from UP */
.cdir.side{position:static; transform:none; margin:0; height:auto; display:flex; flex-direction:column; align-items:center; gap:1px}
.cdir.side img{width:var(--bs); height:var(--bs)}
.clab{font:700 10px/1 var(--display); letter-spacing:.07em; color:var(--gold-hi); text-shadow:0 1px 2px #000}
.placecol .clab{font-size:9px}
.cdir:disabled{opacity:.24; filter:grayscale(.9)}
/* a gem whose exit has a door wears a small door (faded while it stands open); a tap still
   walks, a right-click or long press opens the door's menu */
.cdir.side[data-door]{position:relative}
.cdir[data-door]::after{content:""; position:absolute; right:-3px; bottom:-3px; width:46%; height:46%; pointer-events:none;
  background:url("assets/exit-door.5d11715954b0.png") center/contain no-repeat; filter:drop-shadow(0 1px 2px #000)}
.cdir.side[data-door]::after{bottom:9px}
.cdir[data-door="open"]::after{opacity:.5}
.cdir:not(:disabled):hover img{filter:drop-shadow(0 0 6px rgba(195,162,245,.85)) brightness(1.15)}
/* side stacks: in/out on the left, up/down on the right */
.cside{display:flex; flex-direction:column; align-items:center; gap:12px; flex-shrink:0}
/* directions missing from the last "You notice exits:" line are dimmed but still clickable,
   since that line is only a hint until Phase 2 sends each room's real exits */
.cdir.noexit{opacity:.28; filter:grayscale(.9)}
.cdir.noexit:hover{opacity:.7}
/* the room column is about 212-226px inside its frame, so its compass is a size smaller */
/* the right column is 290-370px, so its compass is a size smaller than the phone's */
/* as big as the column allows (Lizzy: bigger still), up to 190px, with room for the side gems */
/* (Lizzy: room under the S gem to match the space above the N gem, so the compass isn't bottom heavy) */
.placecol .compass-wrap{container-type:inline-size; gap:22px}
.placecol .compass{--bs:44px; --cs:min(190px, calc(100cqi - 2 * var(--bs) - 20px))}
.app[data-compass="off"] .compass-wrap{display:none}

/* ---------- story column ---------- */
.storycol{gap:0}
.tabrow{display:flex; align-items:flex-end; gap:6px; flex-shrink:0}
.tabrow .tabs{flex:1; min-width:0}
.fonttoggle{font:600 14px/1 var(--display); color:var(--gold); border:1px solid var(--line); border-radius:4px; padding:7px 9px; margin-bottom:4px}
.fonttoggle:hover{color:var(--gold-hi); border-color:var(--line-strong)}
.outwrap{position:relative; flex:1; min-height:0; display:flex; flex-direction:column; container-type:inline-size}
/* "Wide text: Fit" shrinks the fixed-width font just enough that the game's 78-column
   tables (SCORE, STATUS, MAP) fit without wrapping; a monospace glyph is about .6em wide */
.app[data-fit="on"]:not([data-font="book"]) .output{font-size:min(var(--out-size), calc((100cqi - 44px) / 48.6))}
.output{
  flex:1; min-height:0; overflow-y:auto; padding:16px 22px 10px; font-family:var(--out-font); font-size:var(--out-size); line-height:var(--out-lh);
  scrollbar-width:thin; scrollbar-color:var(--violet-deep) transparent; overscroll-behavior:contain;
  /* a new line only re-lays-out and repaints the story, never the frames and backgrounds around it */
  contain:content;
}
/* server text keeps its own spacing: newlines arrive as <br>, runs of spaces must survive for tables */
.ln{white-space:pre-wrap; overflow-wrap:anywhere; margin:0 0 .35em; max-width:100ch}
/* ROOM LOOKS (roomLook in mystic.js): the room's name as a title and a divider between rooms,
   as in the approved preview; the server's own colours inside the name still win */
.ln-title{font:600 calc(var(--out-size) * 1.3)/1.15 var(--display); color:var(--gold-hi); letter-spacing:.03em; margin:.5em 0 .3em; white-space:normal}
/* centred across the whole story pane (Lizzy), not the text's 78 columns */
.ln-div{display:flex; justify-content:center; margin:.6em 0 .8em; max-width:none}
.ln-div img{width:200px; height:auto; opacity:.85}
.ln-echo{color:var(--echo); margin-top:.7em}
/* Timestamps setting: the time each line arrived (browser's local clock), in a left
   gutter so 78-column tables keep their full width */
.app[data-stamps="on"] .output{padding-left:84px}
.app[data-stamps="on"] .output .ln{position:relative}
/* a line's time (.ts, real text, so it can be selected and copied): hidden unless a setting shows it. With Timestamps on it stands in the
   margin, as it always looked: the inline box is as wide as the margin it hangs in, so the line's own text starts where it always did */
.ln > .ts{display:none}
.app[data-stamps="on"] .output .ln > .ts{display:inline-block; width:70px; margin-left:-70px; vertical-align:baseline; white-space:pre; color:var(--muted); font:400 11.5px/1.4 var(--mono); font-style:normal}
.app[data-stamps="on"][data-fit="on"]:not([data-font="book"]) .output{font-size:min(var(--out-size), calc((100cqi - 106px) / 48.6))}
.ln-sys{color:var(--muted); font-style:italic}
.ln-err{color:var(--err)}
.ln .retry{margin-left:8px; vertical-align:1px}
.exitlink{color:var(--exit); text-decoration:underline; text-decoration-color:rgba(134,187,238,.35); text-underline-offset:3px; display:inline; font:inherit}
.exitlink:hover{text-decoration-color:var(--exit)}
.output a,.helptext a,.panebody a,.promptline a{color:var(--exit); text-decoration:underline; text-decoration-color:rgba(134,187,238,.35); text-underline-offset:3px}
.output a:hover,.helptext a:hover,.panebody a:hover,.promptline a:hover{text-decoration-color:var(--exit)}
.underline{text-decoration:underline}
.blink{animation:ansi-blink 1s steps(5, start) infinite}
@keyframes ansi-blink{to{visibility:hidden}}
.emptynote{position:absolute; top:16px; left:22px; right:22px; color:var(--muted); font-style:italic; pointer-events:none}

.vitals{display:flex; align-items:center; gap:14px; padding:7px 4px 8px; border-top:1px solid var(--line); flex-shrink:0; flex-wrap:wrap}
.promptline{flex:1; min-width:0; min-height:1.4em; font:13.5px/1.4 var(--mono); color:var(--text); white-space:pre-wrap; overflow-wrap:anywhere}
.promptline:empty::before{content:"\00a0"}
/* Phase 2: health and mana bars, state icons */
/* LOW-HEALTH HEARTBEAT (Lizzy, Session D8; Other animations): the health bar's heart icon, and the mana bar's drop, beat twice and
   rest (transform only; Lizzy: the glow around the whole row was too much) while the value is under a quarter (mystic.js sets
   data-low="hp" or "mp" on the row). The icon keeps a steady red (blue for mana) glow while it is low. Reduced: it fades slowly instead of beating */
.vbar[data-low] > img{filter:drop-shadow(0 0 3px rgba(226,82,92,.9))}
.vbar[data-low="mp"] > img{filter:drop-shadow(0 0 3px rgba(90,166,240,.95))}
.app:not([data-anim="off"]) .vbar[data-low] > img{animation:heartbeat 2.8s ease-in-out infinite}
@keyframes heartbeat{0%{transform:scale(1)} 8%{transform:scale(1.3)} 16%{transform:scale(1.05)} 24%{transform:scale(1.22)} 40%,100%{transform:scale(1)}}
.app[data-motion="reduced"]:not([data-anim="off"]) .vbar[data-low] > img{animation:heart-fade 5s ease-in-out infinite !important}
@keyframes heart-fade{0%,100%{opacity:1} 50%{opacity:.55}}
.vbar{position:relative; display:flex; align-items:center; gap:7px; flex:1 1 120px; min-width:0; font-size:13px; color:var(--muted); font-variant-numeric:tabular-nums}
.vbar img{width:18px; height:18px; object-fit:contain; flex-shrink:0}
.track{flex:1; height:9px; border-radius:5px; background:#07060b; border:1px solid rgba(201,164,106,.4); box-shadow:inset 0 1px 3px #000; overflow:hidden; min-width:40px}
.fill{height:100%; border-radius:4px; transition:width .4s ease; box-shadow:inset 0 1px 0 rgba(255,255,255,.3)}
.fill.hp{background:linear-gradient(180deg,#e2525c,var(--hp))}
.fill.mp{background:linear-gradient(180deg,#5aa6f0,var(--mp))}
.fill.xp{background:linear-gradient(180deg,#a57df0,var(--xp))}
.vnum{min-width:62px; text-align:right}
.states{display:flex; gap:6px}
.state{position:relative; width:26px; height:26px; border-radius:50%; display:grid; place-items:center; border:1px solid var(--line); transition:opacity .2s, filter .2s}
/* SOFT PULSE (Lizzy, Session D8; Other animations): one ring spreading from a circle as its balance or equilibrium returns
   (mystic.js pulseState rests each circle 8 seconds between pulses, since a fight returns them every few seconds). Reduced
   swaps the spreading ring for a plain fade of it */
.state::after{content:""; position:absolute; inset:-2px; border-radius:50%; border:2px solid var(--gold); opacity:0; pointer-events:none}
.app:not([data-anim="off"]) .state[data-pulse]::after{animation:state-pulse 2.2s ease-out 1}
@keyframes state-pulse{0%{transform:scale(1); opacity:.7} 100%{transform:scale(1.55); opacity:0}}
.app[data-motion="reduced"]:not([data-anim="off"]) .state[data-pulse]::after{animation:soft-glow 2.2s ease-out 1 !important}
.state img{width:18px; height:18px; object-fit:contain}
.state[data-on="false"]{opacity:.3; filter:grayscale(1)}
/* POTION BUTTONS (Lizzy): quick buttons that lead the quick row, close together; sip health /
   mana / trueheal, dimmed (and "recovering" in their label) while that potion balance recovers */
.pots{display:flex; gap:0; margin-right:3px; flex-shrink:0}
.pot{width:26px; height:30px; border-radius:4px; display:grid; place-items:center; transition:opacity .2s, filter .2s, background .15s}
.pot img{width:20px; height:26px; object-fit:contain; filter:drop-shadow(0 1px 2px #000)}
.pot:hover{background:rgba(143,99,210,.22)}
.pot[data-busy="true"]{opacity:.35; filter:grayscale(.85)}
/* VITALS ROW (onVitals in mystic.js): hidden until the first mystic_vitals arrives, and the raw
   prompt line under it hides then too unless the Game prompt setting says Show */
.vstats{display:flex; align-items:center; gap:8px 14px; flex:1 1 100%; flex-wrap:wrap; min-width:0}
.app:not([data-vitals="on"]) .vstats{display:none}
.app[data-vitals="on"][data-prompt="hide"] .promptline{display:none}
.app[data-vitals="on"] .promptline{flex-basis:100%}
/* one line (Lizzy): bars, state circles, cooldowns and the target; the defences live in the
   side column on full-width screens and at the end of this row below that */
.vstats .vbar{flex:1 1 150px; min-width:130px; max-width:260px}
.vstats .vnum{min-width:0}
/* on a desktop or tablet the state circles and recovery icons sit at the row's right, their last one level with the input bar's right edge (Lizzy);
   the row's 4px side padding is taken back so it lines up */
/* (the recovery icons come first, so the fixed circles never move: whichever leads takes the push right) */
@media (min-width:760px){ .vstats .states,.vstats .cds:not(:empty){margin-left:auto} .vstats .cds:not(:empty) + .states{margin-left:0} .vstats{margin-right:-4px} }
.qstats.bar{margin-bottom:10px}
/* (a slim row button has its own 6px top margin for when it stands alone, but in a list it sat 13px from the next: a list uses
   the gap alone, tight or not) */
.rowbtns .rowbtn.slim{margin-top:0}
/* stance: dim when passive, lit when aggressive, and ringed in red while engaged */
.state[data-stance="engaged"]{border-color:var(--err); box-shadow:0 0 8px rgba(234,117,99,.55)}
/* cooldowns: only those still recovering show, as the prompt shows their letters */
.cds,.defs{display:flex; flex-wrap:wrap; gap:5px}
.cds:empty,.defs:empty{display:none}
.cd{position:relative; width:26px; height:26px; border-radius:50%; display:grid; place-items:center; border:1px dashed var(--line-strong); opacity:.75}
.cd img{width:17px; height:17px; object-fit:contain; filter:grayscale(.5)}
.cd b{position:absolute; right:-3px; bottom:-3px; min-width:13px; height:13px; padding:0 2px; border-radius:7px; background:var(--panel-3); border:1px solid var(--line-strong); font:600 9.5px/11px var(--mono); color:var(--gold-hi); text-align:center}
/* active defences and the target. STATS & DEFENCES (Lizzy): a box at the foot of the left column
   on full-width screens, always in view under whichever pane is open, and in the phone's
   Character view; tablets keep the defences at the end of the vitals row (their left column is
   a drawer) */
@media (min-width:1220px), (max-width:759px){ .vstats .defs{display:none} }
@media (min-width:760px) and (max-width:1219px){ .sidedefs{display:none} }
.dchip,.tchip{display:inline-flex; align-items:center; gap:4px; height:26px; padding:0 8px; border-radius:13px; font:600 10.5px/1 var(--display); letter-spacing:.05em; text-transform:uppercase; white-space:nowrap}
.dchip{color:#7fd0c4; border:1px solid rgba(127,208,196,.5); background:rgba(127,208,196,.07)}
.dchip img{width:18px; height:18px; object-fit:contain; margin-left:-3px}
.tchip{padding:0 3px 0 6px; color:var(--text-hi); border:1px solid rgba(234,117,99,.55); background:rgba(234,117,99,.08); text-transform:none; letter-spacing:.02em; font-size:12.5px}
.tchip img{width:17px; height:17px; object-fit:contain}
.tchip .tn{max-width:150px; overflow:hidden; text-overflow:ellipsis}
.tchip .tx{width:20px; height:20px; border-radius:50%; color:var(--muted); font:16px/1 var(--serif)}
.tchip .tx:hover{color:var(--text-hi); background:rgba(255,255,255,.08)}

.inputwrap{position:relative; flex-shrink:0; padding-top:4px}
.inputbar{
  display:flex; align-items:center; gap:2px; padding:3px 6px 3px 12px;
  background:#08070d; border:1px solid var(--line-strong); border-radius:4px;
  box-shadow:inset 0 0 14px rgba(0,0,0,.9), 0 0 0 1px rgba(0,0,0,.6);
}
.inputbar:focus-within{border-color:var(--gold); box-shadow:inset 0 0 14px rgba(0,0,0,.9), 0 0 14px rgba(143,99,210,.35)}
.inputbar .chev{color:var(--gold); width:16px; height:16px; flex-shrink:0}
.inputbar input{flex:1; min-width:0; background:none; border:0; outline:none; color:var(--text-hi); font:italic 17px var(--serif); padding:11px 8px}
.inputbar input::placeholder{color:#7d7488}
.waiting{display:flex; align-items:center; gap:8px; flex-shrink:0; font:italic 14px var(--serif); color:var(--gold); white-space:nowrap; padding-right:4px}
.ibtn{width:38px; height:38px; display:grid; place-items:center; border-radius:4px; color:var(--gold); position:relative; flex-shrink:0}
.ibtn:hover{color:var(--gold-hi); background:rgba(201,164,106,.1)}
.ibtn svg{width:20px; height:20px}
.ibtn.send{color:var(--gold-hi)}
.ibtn.latest.has-new::after{content:""; position:absolute; top:7px; right:7px; width:7px; height:7px; border-radius:50%; background:var(--violet-hi); box-shadow:0 0 8px var(--violet-hi)}
.quickrow{display:flex; flex-wrap:wrap; gap:5px; padding-top:8px; flex-shrink:0}
.quickrow .chip{min-width:38px}
.app[data-quick="off"] .quickrow{display:none}

.pop{
  position:absolute; bottom:calc(100% + 6px); right:0; width:min(340px,100%); max-height:280px; overflow-y:auto; z-index:20;
  background:var(--panel-2); border:1px solid var(--line-strong); border-radius:6px; padding:8px; box-shadow:0 12px 30px rgba(0,0,0,.6);
}
.pop h3{margin:2px 6px 8px}
.pop button{display:flex; width:100%; text-align:left; gap:10px; padding:7px 8px; border-radius:4px; font-size:15px; align-items:baseline}
.pop button:hover,.pop button:focus-visible{background:rgba(143,99,210,.2)}
.pop code{font-family:var(--mono); font-size:13px; color:var(--gold-hi); white-space:nowrap}
.pop .hint{color:var(--muted); font-size:14px}
.pop .none{color:var(--muted); font-style:italic; padding:6px 8px}

/* phone-only pieces, hidden on larger screens */
.mroom,.compassfab,.floatcompass{display:none}

/* ---------- left column: you ---------- */
/* the identity card, then the menu or the open pane (scrolling), then the defences */
.youcol{gap:10px}
/* wide screens: the menu (or pane) right under the card, Stats and Defences at the foot, near the
   vitals and the input (Lizzy); phones keep the page order (card, status, stats, defences, menu) */
.youbody{order:1}
.youcol > :is(.ystats,.sidedefs){order:2}
@keyframes flash{0%{filter:drop-shadow(0 0 0 rgba(195,162,245,0))}35%{filter:drop-shadow(0 0 18px rgba(195,162,245,.8))}100%{filter:drop-shadow(0 0 0 rgba(195,162,245,0))}}
.col.flash{animation:flash .9s ease-out}
.closeyou{display:none}
/* IDENTITY CARD (renderCard in mystic.js, from mystic_score): the portrait (a tap: prefix,
   suffix, description, portrait), the full name, level and XP bar, then SCORE's summary rows,
   (standing and wealth have their own rows). The health and mana bars show here on phones only */
.idcard{display:flex; flex-direction:column; gap:8px; padding-bottom:10px; border-bottom:1px solid var(--line); flex-shrink:0}
.idtop{position:relative; display:flex; gap:12px; align-items:center}
/* SPARKLE (Lizzy, Session D8; Other animations): stars over the card on a new level or accolade (mystic.js sparkle fills
   .spark and sets data-go): gold four-point stars that pop, turn and fade once. Reduced: they only fade */
/* (Lizzy: the first stars looked cartoony. Now a star twinkling in the sky: a thin four-point cross with long fine rays and a white-hot centre,
   inside a faint round halo, that swells, dims and swells again in place, with no spinning) */
.spark{position:absolute; inset:-6px; pointer-events:none}
.spark i{position:absolute; left:var(--x); top:var(--y); width:calc(14px * var(--s)); height:calc(14px * var(--s)); margin:calc(-7px * var(--s)); opacity:0}
.spark i::before{content:""; position:absolute; inset:-30%; border-radius:50%; background:radial-gradient(circle, rgba(255,244,205,.5), rgba(255,230,160,.16) 40%, transparent 70%)}
.spark i::after{content:""; position:absolute; inset:-25%; background:radial-gradient(circle, #fff 0, #fff 8%, rgba(255,246,214,.9) 22%, rgba(255,232,170,.5) 55%, rgba(255,226,150,.15) 100%);
  clip-path:polygon(50% 0, 53% 45%, 100% 50%, 53% 55%, 50% 100%, 47% 55%, 0 50%, 47% 45%)}
.app:not([data-anim="off"]) .spark[data-go] i{animation:spark-pop 2.6s ease-in-out var(--w) 1 both}
@keyframes spark-pop{0%{transform:scale(.2); opacity:0} 18%{transform:scale(1); opacity:1} 36%{transform:scale(.62); opacity:.45} 54%{transform:scale(.95); opacity:.95} 100%{transform:scale(.3); opacity:0}}
.app[data-motion="reduced"]:not([data-anim="off"]) .spark[data-go] i{animation:soft-glow 2.4s ease-out var(--w) 1 both !important}
.portrait{width:84px; height:100px; flex-shrink:0; border:1px solid var(--line-strong); border-radius:4px; overflow:hidden; display:block;
  background:#0b0a11; box-shadow:inset 0 0 0 1px rgba(0,0,0,.6)}
.portrait img{width:100%; height:100%; object-fit:cover; object-position:50% 20%}
.portrait:hover,.portrait[aria-expanded="true"]{border-color:var(--gold); box-shadow:0 0 12px rgba(143,99,210,.45)}
.idtext{min-width:0; flex:1; display:flex; flex-direction:column; gap:3px}
.idname{margin:0 0 2px; font:700 17px/1.15 var(--display); color:var(--gold-hi); letter-spacing:.04em; overflow-wrap:anywhere}
.idlvl{font-size:15px; color:var(--text-hi)}
/* the gender symbol after the level (genderIcon) */
.idgender{display:inline-flex; margin-left:7px; vertical-align:-3px; color:var(--gold)}
.idgender svg{width:16px; height:16px}
.idprof{font-size:14.5px; color:var(--text)}
.idcity{font-size:14px; color:var(--violet-hi); font-style:italic}
:is(.idlvl,.idprof,.idcity):empty{display:none}
/* XP (Lizzy): a heading, then a bar shaped like the Status section's health and mana bars, the
   card's full width under the portrait */
.xpsect{display:flex; flex-direction:column; gap:8px}
.xpsect[hidden]{display:none}
.xpsect .vbar{flex:none}
/* the left column's sections (Status on phones, Stats, Defences), each under one of the
   mockup's ornamental dividers (Lizzy: more dividers in this pane) */
.ysect{flex-shrink:0; display:flex; flex-direction:column; gap:8px; padding-top:24px;
  background:url("assets/div-3.3753519565b2.png") center top / min(200px, 80%) auto no-repeat}
.ystatus{display:none}
/* more room under the divider above Stats on wide screens (Lizzy) */
.ystats{padding-top:34px}
.ystatus .vbar{flex:none; font-size:13px; color:var(--muted)}
/* padding-right: a recovering balance's letter badge sits past its circle's edge, and touching
   the column's edge made the phone's Character view scroll sideways (Lizzy) */
.statesrow{display:flex; flex-wrap:wrap; align-items:center; gap:6px 8px; padding-right:5px; font-size:13.5px; color:var(--muted)}
.statesrow .stk{margin-right:6px}
.statesrow .cds{margin-left:auto}
/* STATS (Lizzy: their own section, given a tile each): an icon, the value large, the name */
.stattiles{display:grid; grid-template-columns:repeat(3, minmax(0,1fr)); gap:6px}
.stile{display:flex; flex-direction:column; align-items:center; gap:2px; padding:8px 4px 7px; border:1px solid var(--line-strong); border-radius:5px;
  background:linear-gradient(180deg,#1c1828,#0e0c15); box-shadow:inset 0 0 0 1px rgba(0,0,0,.55)}
.stile img{width:28px; height:28px; object-fit:contain; filter:drop-shadow(0 1px 2px #000)}
.stile b{font:600 18px/1.1 var(--display); color:var(--gold-hi); font-variant-numeric:tabular-nums}
.stile span{font:600 10px/1 var(--display); letter-spacing:.1em; text-transform:uppercase; color:var(--muted); text-align:center}
.sidedefs .defs:empty::before{content:"No defences are up."; color:var(--muted); font-style:italic; font-size:13.5px}
/* Stats and Defences share the one divider above Stats (Lizzy), with room between them */
.ystats + .sidedefs{background:none; padding-top:22px}
/* AFFILIATIONS (Social): a row each for citizenship (with hometown and residencies), guild, clan
   and order, the standing under the name; an order has no command, so its row is plain */
.rowbtn .nm small{display:block; margin-top:3px; font:400 13.5px/1.25 var(--serif); letter-spacing:0; text-transform:none; color:var(--text)}
.rowbtn .nm small + small{color:var(--muted); font-size:12.5px}
div.rowbtn{cursor:default}
div.rowbtn::after{display:none}
div.rowbtn:hover{border-color:var(--line-strong); background:linear-gradient(180deg,#1c1828,#0e0c15)}
/* YOUR TIES (Social): the label muted before the name, the rank at the right */
.plist .tk{color:var(--muted); font-size:13px; margin-right:4px}
.plist span.wn{display:flex; align-items:baseline; gap:8px}
.label.subhead{margin:6px 0 0; font-size:11.5px; color:var(--gold-hi); display:flex; gap:8px; align-items:baseline}
.label.subhead small{font:400 13px var(--serif); letter-spacing:0; text-transform:none; color:var(--muted)}
.youbody{flex:1; min-height:0; overflow-y:auto; display:flex; flex-direction:column}
.ypane{display:flex; flex-direction:column; gap:10px; container-type:inline-size}
/* the pane's title and back arrow stay at the top while a long page scrolls under them (Lizzy), so Back is always in reach */
.panehead{display:flex; align-items:center; gap:8px; position:sticky; top:0; z-index:4; background:var(--panel); padding:4px 0; margin:-4px 0; box-shadow:0 6px 8px -6px rgba(0,0,0,.7)}
.panehead .label{flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.panehead small{color:var(--muted); font-size:14px}
/* until Phase 3, a pane shows its command's own output: lines stay unwrapped, and the
   text shrinks (to 9px at least) so the game's 80-column tables fit the pane; only
   narrower panes than that still scroll sideways */
.panebody{font-family:var(--mono); font-size:min(12px, max(9px, calc((100cqi - 8px) / 48.6))); line-height:1.45; white-space:pre; overflow-x:auto; padding-bottom:6px; scrollbar-width:thin; scrollbar-color:var(--violet-deep) transparent}
.panebody .paneempty{font-family:var(--serif); font-size:15px; white-space:normal}
.panebody .ln{max-width:none; margin:0 0 .3em; white-space:pre; overflow-wrap:normal}
/* the map, trimmed of its padding (trimBlock in mystic.js), sits centred as large as fits
   (Lizzy): 14px, or smaller only as far as its widest line (--cols, set by showMap) needs */
/* up to 18px (Lizzy: a little bigger), so a small map grows when the column has room */
/* Lizzy, 2026-10-21 (fitting its height too made it too small): its widest line across the
   column, at most 16px; the box keeps its 190px, and placeMap (mystic.js) sets the map's top so a
   tall one is centred on you with its top and bottom cut off (no scroll bar), fading where cut */
#pb-map{font-size:min(16px, max(7px, calc((100cqi - 8px) / (var(--cols, 40) * .6)))); line-height:1.35; height:190px; overflow:hidden; display:flex; flex-direction:column}
#pb-map .mapclip{position:relative; flex:1 1 auto; min-height:0; overflow:hidden; --fade-top:#000; --fade-bottom:#000}
#pb-map .mapclip .ln{position:absolute; top:0; left:50%; transform:translateX(-50%); margin:0}
#pb-map .mapclip.cut-top{--fade-top:transparent}
#pb-map .mapclip.cut-bottom{--fade-bottom:transparent}
#pb-map .mapclip:is(.cut-top, .cut-bottom){-webkit-mask-image:linear-gradient(var(--fade-top), #000 14px, #000 calc(100% - 14px), var(--fade-bottom)); mask-image:linear-gradient(var(--fade-top), #000 14px, #000 calc(100% - 14px), var(--fade-bottom))}
#pb-map .ln{width:max-content; margin-inline:auto}
#pb-map .mapnote{white-space:normal; font-size:12px; margin-top:.8em; flex:none}

/* Phase 4: parchment map */
.mapwrap{position:relative; flex:1; min-height:220px; border:1px solid var(--parch-ink); border-radius:3px; overflow:hidden;
  background:
    radial-gradient(ellipse at center, transparent 45%, rgba(74,48,20,.55) 100%),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.025' numOctaves='4' seed='7'/%3E%3CfeColorMatrix values='0 0 0 0 .42 0 0 0 0 .3 0 0 0 0 .15 0 0 0 .55 0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url%28%23p%29'/%3E%3C/svg%3E"),
    var(--parch);
  box-shadow:inset 0 0 30px rgba(60,38,14,.6);
}
.mapwrap > svg{position:absolute; inset:0; width:100%; height:100%}
.mapzone{position:absolute; top:10px; left:10px; z-index:2; font:500 14px/1 var(--serif); color:var(--text-hi); background:rgba(11,10,17,.88); border:1px solid var(--line-strong); border-radius:3px; padding:7px 10px; display:flex; gap:8px; align-items:center}
.mapzone svg{width:12px; height:12px; color:var(--gold)}
.mapctl{position:absolute; top:10px; right:10px; z-index:2; display:flex; flex-direction:column; gap:6px}
.mapctl button{width:32px; height:32px; display:grid; place-items:center; color:var(--gold-hi); background:rgba(11,10,17,.9); border:1px solid var(--line-strong); border-radius:3px; font:600 18px/1 var(--serif)}
.mapctl button:hover{background:#231b35}
.mapctl svg{width:16px; height:16px}
.node{cursor:pointer}
.node:focus{outline:none}
.node:focus-visible circle.base,.node:hover circle.base{stroke:#2b1b45; stroke-width:3.5}
@keyframes pulse{0%{r:14; opacity:.55}100%{r:26; opacity:0}}
.pulse{animation:pulse 2.2s ease-out infinite; fill:var(--violet)}
.legend{display:grid; grid-template-columns:1fr 1fr; gap:6px 10px; font-size:13.5px; color:var(--text)}
.legend span{display:flex; align-items:center; gap:7px}
.legend i{width:14px; height:14px; border-radius:50%; flex-shrink:0; display:inline-block}
.legend .lg-you{background:url("assets/dir-center.3feba7b682c0.png") center/contain no-repeat; width:18px; height:18px}
.legend .lg-exit{height:3px; border-radius:2px; background:var(--parch-dark); width:16px}
.legend .lg-seen{background:var(--node-seen); border:2px solid #1e3c38}
.legend .lg-new{background:var(--node-new); border:2px solid #7a6646}
.legend .lg-poi{background:var(--node-new); border:3px solid var(--poi)}
.app[data-legend="off"] .legend{display:none}
.mapfoot{display:flex; align-items:center; gap:10px; padding:9px 11px; border:1px solid var(--line); border-radius:4px; background:rgba(255,255,255,.02); font-size:14px}
.mapfoot .zn{font-family:var(--display); font-weight:600; font-size:13px; color:var(--gold-hi); flex:1; min-width:0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis}
.mapfoot .track{flex:0 0 70px; height:7px}
.mapfoot .pct{color:var(--muted); font-size:13px; font-variant-numeric:tabular-nums; white-space:nowrap}

/* Phase 3: character, inventory, skills, quests */
.charhead{display:flex; align-items:center; gap:12px; padding-bottom:6px}
.crest{width:52px; height:52px; border-radius:50%; display:grid; place-items:center; border:1px solid var(--line-strong); background:radial-gradient(circle,#2c1b47,#0b0a11)}
.crest img{width:34px; height:34px}
.charname{font:700 20px/1.1 var(--display); color:var(--gold-hi); letter-spacing:.04em}
.charsub{color:var(--muted); font-size:15px}
.cbars{display:flex; flex-direction:column; gap:9px}
.cbars .vbar{font-size:14px; color:var(--text)}
.cbars .vbar .nm{width:76px}
.cbars .vbar img{width:22px; height:22px}
.cstates{display:grid; grid-template-columns:1fr 1fr; gap:6px}
.cstate{display:flex; align-items:center; gap:8px; padding:6px 8px; border:1px solid var(--line); border-radius:4px; font-size:14px; transition:opacity .2s}
.cstate img{width:22px; height:22px; object-fit:contain}
.cstate[data-on="false"]{opacity:.35}
.invgrid{display:grid; grid-template-columns:repeat(4,1fr); gap:6px}
.invcat{display:flex; flex-direction:column; align-items:center; gap:3px; padding:8px 2px 6px; border:1px solid var(--line); border-radius:4px; background:rgba(255,255,255,.02); font-size:12.5px; color:var(--muted)}
.invcat:hover{border-color:var(--line-strong); color:var(--text-hi)}
.invcat img{width:30px; height:30px; object-fit:contain}
.invcat b{font-weight:500; color:var(--text-hi); font-variant-numeric:tabular-nums}
.secthead{display:flex; align-items:center; gap:10px; margin-top:6px}
.secthead::after{content:""; flex:1; height:1px; background:linear-gradient(90deg,var(--line-strong),transparent)}
.filters{display:grid; grid-template-columns:repeat(9,minmax(0,1fr)); gap:4px}
.filt{height:34px; min-width:0; display:grid; place-items:center; border:1px solid var(--line); border-radius:4px; background:linear-gradient(180deg,#1c1828,#0e0c15)}
.filt img{width:22px; height:22px; object-fit:contain}
.filt.all{padding:0; font:600 10.5px/1 var(--display); letter-spacing:.08em; color:var(--gold-hi)}
.filt[aria-pressed="true"]{border-color:var(--gold); box-shadow:0 0 10px rgba(143,99,210,.45); background:linear-gradient(180deg,#4a3079,#2a1a45)}
.itemlist{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:5px}
.itemlist li{display:flex; align-items:center; gap:9px; padding:6px 8px; border:1px solid var(--line); border-radius:4px; background:rgba(255,255,255,.02); font-size:15px}
.itemlist li > img{width:24px; height:24px; object-fit:contain; flex-shrink:0}
.itemlist .nm{flex:1; min-width:0}
.itemlist .id{color:var(--muted); font-size:13px}
.itemlist .worn{font:600 9.5px/1 var(--display); letter-spacing:.08em; color:#7fd0c4; border:1px solid currentColor; border-radius:3px; padding:3px 4px; margin-left:6px; vertical-align:2px}
.itemlist .acts{display:flex; gap:4px}
.paneempty{color:var(--muted); font-style:italic; padding:8px; border:1px dashed var(--line); border-radius:4px}
/* PHASE 3 DATA PANES (Standing, Wealth, Skills; Inventory below): ordinary text, not the
   captured panes' fixed-width output */
.databody{display:flex; flex-direction:column; gap:10px}
/* STANDING and WEALTH (renderStatus, renderWealth): label / value rows */
.facts{display:flex; flex-direction:column; border:1px solid var(--line); border-radius:4px; background:rgba(255,255,255,.02)}
.facts:empty{display:none}
.fact{display:flex; align-items:baseline; gap:10px; padding:7px 10px; font-size:15px}
.fact + .fact{border-top:1px solid var(--line)}
.fk{color:var(--muted); font-size:14px; flex:1; min-width:0}
.fv{color:var(--text-hi); text-align:right; font-variant-numeric:tabular-nums}
.fv small{color:var(--muted); font-size:13px}
.fact.total .fk,.fact.total .fv{color:var(--gold-hi); font-family:var(--display); font-size:13.5px; letter-spacing:.04em}
.fact.owed .fv,.fact .owed{color:#ea7563}
.fact .fk small{display:block; margin-top:2px; font-size:13px}
.fact:has(> .fi){align-items:center}
.fact .fi{width:22px; height:22px; object-fit:contain; flex-shrink:0; margin-right:-2px}
.facts .panenote{padding:8px 10px}
.descr{margin:0; font-size:15px; line-height:1.45; color:var(--text); white-space:pre-wrap; overflow-wrap:anywhere}
.descset{align-self:flex-start}
/* SKILLS (renderSkills; after the approved preview's cards): who you are, then a card per skill */
.skwho{display:flex; align-items:center; gap:10px; padding:8px 10px; border:1px solid var(--line-strong); border-radius:5px; background:linear-gradient(180deg,#1c1828,#0e0c15)}
.skwho img{width:34px; height:34px; object-fit:contain; flex-shrink:0}
.skwho .nm{font:600 15px/1.2 var(--display); color:var(--gold-hi); letter-spacing:.04em}
.skwho small{display:block; margin-top:3px; font:400 14px/1.2 var(--serif); letter-spacing:0; color:var(--text)}
.skpts{display:flex; flex-wrap:wrap; align-items:baseline; gap:4px 12px; font-size:14px; color:var(--muted)}
.skptk{font:600 11px/1 var(--display); letter-spacing:.1em; text-transform:uppercase; color:var(--gold)}
.skpt b{color:var(--text-hi); font-weight:500}
.skcards{list-style:none; margin:4px 0 0; padding:0; display:flex; flex-direction:column; gap:7px}
.skcard{width:100%; display:flex; flex-direction:column; gap:5px; padding:9px 11px; text-align:left; border:1px solid var(--line); border-radius:5px;
  background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.01))}
.skcard:hover,.skcard[aria-expanded="true"]{border-color:var(--line-strong); background:rgba(143,99,210,.12)}
.skhead{display:flex; align-items:center; gap:8px}
.skhead img{width:22px; height:22px; object-fit:contain; flex-shrink:0}
.skhead .nm{flex:1; min-width:0; font:600 14.5px/1.2 var(--display); color:var(--gold-hi); letter-spacing:.03em}
.skhead .rank{font-size:14px; color:var(--text); white-space:nowrap}
.skcard .track{display:block; height:7px; flex:none}
.skcard .fill{display:block}
.skcard .meta{font-size:13px; color:var(--muted); font-variant-numeric:tabular-nums}
/* INVENTORY PANE (renderInv): a search box, filter chips, folding sections of rows (an icon, the
   name, a hand or WORN tag, the id or a count), coins at the foot */
.invsearch{width:100%; background:#08070d; border:1px solid var(--line-strong); border-radius:4px; color:var(--text-hi); font:italic 15px var(--serif); padding:7px 10px; outline:none}
.invsearch:focus{border-color:var(--gold); box-shadow:0 0 12px rgba(143,99,210,.35)}
/* one row that scrolls sideways (fourteen chips wrapped to five rows), its right edge fading
   while more lie past it; no bar, since a swipe or the wheel moves it */
.invfilters{display:flex; gap:5px; overflow-x:auto; scrollbar-width:none; overscroll-behavior-x:contain; padding:2px 24px 4px 0;
  -webkit-mask-image:linear-gradient(90deg, #000 calc(100% - 28px), transparent); mask-image:linear-gradient(90deg, #000 calc(100% - 28px), transparent)}
.invfilters::-webkit-scrollbar{display:none}
.invfilters:empty{display:none}
.invchip{flex-shrink:0; min-height:28px; padding:0 9px; font-size:10.5px}
.invchip[aria-pressed="true"]{border-color:var(--gold); color:#fff; background:linear-gradient(180deg,#4a3079,#2a1a45); box-shadow:0 0 10px rgba(143,99,210,.45)}
.invbody{display:flex; flex-direction:column; gap:10px}
.plist.invlist .wn:has(> .nm){align-items:center}
.invlist .wn img{width:22px; height:22px; flex-shrink:0; object-fit:contain; filter:drop-shadow(0 1px 2px #000)}
/* a small outlined tag: an inventory row's hand or WORN, the Skills pane's ARTISAN / FARMER */
.itag{flex-shrink:0; font:600 9.5px/1 var(--display); letter-spacing:.08em; color:#7fd0c4; border:1px solid currentColor; border-radius:3px; padding:3px 4px}
.sktag{display:inline-block; margin-left:8px; vertical-align:2px}
.invlist .qty{flex-shrink:0; color:var(--muted); font-size:13px; font-variant-numeric:tabular-nums; text-align:right}
button.coins{width:100%; text-align:left; color:var(--text)}
button.coins:hover,button.coins[aria-expanded="true"]{border-color:var(--line-strong); color:var(--gold-hi)}
.coins{display:flex; align-items:center; gap:8px; padding:8px 10px; border:1px solid var(--line); border-radius:4px; font-size:15px; margin-top:auto}
.coins img{width:24px; height:24px; object-fit:contain}
.coins b{color:#e5c072; font-weight:500; font-variant-numeric:tabular-nums; margin-left:auto}
/* QUESTS PANE (renderQuests): the Stats tiles for active / completed / share of all, with its bar,
   then rows (icon, name, status) that open into their details in place: giver, description,
   journal entries with tick boxes, reward chips, your completions (Standing's label / value rows),
   FULL ENTRY and a quiet Ignore. Ignored unfinished quests are small grey rows */
.qstats{display:flex; flex-direction:column; gap:7px}
.qstats .track{display:block; height:7px; flex:none}
.qstats .fill{display:block}
.stile.qon b{color:#93d38c}
.qcards{list-style:none; margin:4px 0 0; padding:0; display:flex; flex-direction:column; gap:6px}
.qcard{border:1px solid var(--line); border-radius:5px; background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.01))}
/* an open quest glows (Lizzy): green for an active one, violet for a completed one */
.qcard:has(> .qhead[aria-expanded="true"]){border-color:var(--line-strong); background:rgba(143,99,210,.1); box-shadow:0 0 14px rgba(143,99,210,.4)}
.qcard.on:has(> .qhead[aria-expanded="true"]){border-color:rgba(143,240,138,.5); box-shadow:0 0 14px rgba(143,240,138,.28), inset 0 0 14px rgba(143,240,138,.05)}
.qhead{width:100%; display:flex; align-items:center; gap:8px; padding:8px 10px; text-align:left; border-radius:4px}
/* keyboard focus inside the card's edge, not as lines across it */
.qhead:focus-visible{outline-offset:-2px}
.qhead img{width:22px; height:22px; object-fit:contain; flex-shrink:0}
.qhead .nm{flex:1; min-width:0; font:600 14px/1.25 var(--display); color:var(--gold-hi); letter-spacing:.03em; overflow-wrap:anywhere}
.qhead:hover .nm{color:#fff}
.qst{font-size:13px; color:var(--muted); white-space:nowrap}
/* active quests (Lizzy): green, and a bright ACTIVE tag while the details show */
.qst.on{color:#93d38c}
.qhead[aria-expanded="true"] .qst.on{font:600 10px/1 var(--display); letter-spacing:.1em; text-transform:uppercase; color:#8ff08a;
  border:1px solid currentColor; border-radius:3px; padding:4px 5px; box-shadow:0 0 8px rgba(143,240,138,.3)}
.qtag{color:var(--muted)}
.qbody{display:flex; flex-direction:column; gap:7px; padding:0 11px 10px 40px; font-size:14.5px; line-height:1.4; color:var(--text)}
.qbody p{margin:0}
.qfrom{color:var(--muted); font-style:italic}
.qjournal{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:5px}
.qjournal li{display:flex; gap:8px; align-items:baseline}
/* (Lizzy: the ornate checkboxes, check-off / check-on) */
.qjournal img{width:20px; height:20px; flex-shrink:0; object-fit:contain; transform:translateY(4px)}
.qjournal li.done{color:var(--muted)}
.qrewards{display:flex; flex-wrap:wrap; align-items:center; gap:5px}
.qlabel{flex-basis:100%; font:600 10.5px/1 var(--display); letter-spacing:.1em; text-transform:uppercase; color:var(--gold)}
.qchip{display:inline-flex; align-items:center; gap:5px; padding:3px 8px 3px 5px; border:1px solid var(--line); border-radius:12px; font-size:13px; color:var(--text-hi); background:rgba(255,255,255,.03)}
.qchip img{width:16px; height:16px; object-fit:contain}
.qfacts .fact{padding:5px 9px; font-size:13.5px}
.qfacts .fk{font-size:13px}
.qacts{display:flex; flex-wrap:wrap; align-items:center; gap:6px; margin-top:2px}
.qlink{margin-left:auto; font-size:13px; color:var(--muted); text-decoration:underline; text-decoration-color:transparent; text-underline-offset:3px; padding:4px 2px}
.qlink:hover{color:var(--text); text-decoration-color:currentColor}
/* POP OUT (data-popout): the pane's cards in the window, each the window's full width with its
   details showing (Lizzy: two to a row laid out unevenly), the window scrolling; the tiles keep to
   a sensible width */
.popout{display:inline-grid; place-items:center; padding:5px 6px}
.popout svg{width:13px; height:13px}
.wincards{display:flex; flex-direction:column; gap:12px; padding-bottom:6px; font-family:var(--serif)}
.wincards .qstats{max-width:520px}
.wincards .qcards{gap:10px}
.wincards .qcard.on{border-color:rgba(143,240,138,.35)}
.helpwin:focus{outline:none}
/* ACADEMY and TUTORIAL PANES (renderAcademy, renderTutorial; Session D5, Lizzy: no boxes inside boxes).
   A pane is a stack of flat views: a Back button and title (.viewhead), then rows. A row (.navrow) is one
   full-width button; finished or locked ones are grey (a locked one dashed, with nothing to tap), and the
   one you're on glows green with an ACTIVE tag (.tagon; amber for Ready to submit / complete). The tasks
   and steps (.steps) are a quest-log list with the ornate checkboxes, no boxes but the active step's
   glow, and its hint under it */
.vtitle{display:flex; align-items:center; gap:8px; margin:2px 0 2px}
.vdiv{height:1px; margin:6px 0; background:linear-gradient(90deg,var(--gold),transparent); opacity:.55}
/* a tutorial chapter's steps are boxed (Lizzy): the one you're on glows, the others keep the plain box, with room between */
.steps.boxed{gap:7px}
.steps.boxed .step{border-color:var(--line); background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.01))}
.steps.boxed .step.current{border-color:rgba(143,240,138,.5); margin-bottom:6px}
.steps.boxed .qstep{padding-bottom:14px}
.vtitle > div{flex:1; min-width:0}
.vtitle h3{margin:0; font:600 19px/1.2 var(--display); letter-spacing:.04em; color:var(--gold-hi); overflow-wrap:anywhere}
.cblurb{border-left:2px solid var(--line-strong); padding-left:10px; font-style:italic}
.srow .itag{margin-left:6px}
.steps.dim .nm{color:var(--muted)}
.qacts.end{justify-content:flex-end}
.qnpc{color:var(--muted); font-size:13.5px}
.qnpc b{color:var(--text-hi); font-weight:600}
.qstep p{margin:0 0 5px}
.qstep .qacts{margin-top:6px}
.helprow{margin-top:2px}
.acadhead{display:flex; align-items:center; gap:12px}
.acadhead img{width:52px; max-height:64px; object-fit:contain; flex-shrink:0; filter:drop-shadow(0 1px 3px #000)}
.acadhead h3{margin:0; font:600 17px/1.2 var(--display); letter-spacing:.04em; color:var(--gold-hi); overflow-wrap:anywhere}
.acadhead p{margin:2px 0 0; font-size:14px; line-height:1.35; color:var(--muted)}
.acadhead p.ok{color:#93d38c}
.kicker{display:block; margin-bottom:3px; font:600 10px/1 var(--display); letter-spacing:.12em; text-transform:uppercase; color:var(--gold)}
.barrow{display:flex; justify-content:space-between; align-items:baseline; gap:8px; font-size:14px; color:var(--muted)}
.barrow b{font-weight:600; color:var(--text-hi); font-variant-numeric:tabular-nums}
.cblurb{margin:0; font-size:15px; line-height:1.45; color:var(--text)}
.classmates .nm small{display:block; margin-top:1px; font-size:13px; color:var(--muted)}
.navrows{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:var(--gap-list)}
.navrow{width:100%; display:flex; align-items:center; gap:10px; padding:9px 11px; text-align:left; border:1px solid var(--line); border-radius:5px;
  background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.01))}
button.navrow:hover{border-color:var(--line-strong)}
.navrow img{width:24px; height:24px; flex-shrink:0; object-fit:contain}
.navrow .nm{flex:1; min-width:0; font:600 14px/1.25 var(--display); letter-spacing:.03em; color:var(--gold-hi); overflow-wrap:anywhere}
.navrow .nm small{display:block; margin-top:2px; font:400 13px/1.35 var(--serif); letter-spacing:0; color:var(--muted)}
.navrow.on{border-color:rgba(143,240,138,.5); box-shadow:0 0 12px rgba(143,240,138,.25), inset 0 0 12px rgba(143,240,138,.05)}
.navrow.done .nm,.navrow.locked .nm{color:var(--muted)}
.navrow.done img{filter:grayscale(.6); opacity:.8}
.navrow.locked{border-style:dashed; background:none; cursor:default}
.navrow.locked img{filter:grayscale(1) brightness(.8)}
.navrow .tick{width:22px; height:22px}
/* an organisation's name at the end of an Affiliations row: it may be long, so it wraps (Session D6) */
.navrow .qst.wrap{white-space:normal; text-align:right; max-width:55%; overflow-wrap:anywhere}
/* the org banner's name is a link to About; a promotion you're eligible for glows green like what you're on (Session D6) */
.acadhead h3 .hlink{font:inherit; letter-spacing:inherit; color:inherit; text-align:left; padding:0; border:0; background:none; cursor:pointer}
.acadhead h3 .hlink:hover,.acadhead h3 .hlink:focus-visible{color:#fff}
.promo{display:flex; align-items:center; gap:10px; padding:9px 11px; border:1px solid rgba(143,240,138,.5); border-radius:5px; color:#8ff08a;
  font:600 13px/1.3 var(--display); letter-spacing:.05em; box-shadow:0 0 12px rgba(143,240,138,.25), inset 0 0 12px rgba(143,240,138,.05)}
.promo img{width:22px; height:22px; flex-shrink:0; object-fit:contain}
/* CLIENT SETTINGS (Phase 5, Round 3): the editors for your buttons, macros, aliases and variables. Fields are the Inventory search box's
   look at a size that never zooms a phone; a list row can carry a pair of move arrows beside it */
.csitem{display:flex; align-items:stretch; gap:6px}
.csbox{flex:1; min-width:0; display:flex; align-items:center; gap:8px; padding-right:8px; border:1px solid var(--line); border-radius:5px;
  background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.01))}
.csbox:hover{border-color:var(--line-strong)}
.csbox .navrow{flex:1; min-width:0; border:0; background:none}
/* an alias's ON / OFF on its list row: the ACTIVE tag's look (.tagon) as a button, a grey border and no glow when it is off */
/* the standard chips on the button row step aside once the player has their own buttons (renderQuickButtons) */
.quickrow .chip.std[hidden]{display:none}
/* the grip beside a row that can be dragged to a new place; touch-action none so a touch on it drags and does not scroll */
.csgrip{align-self:center; flex-shrink:0; padding:10px 3px; margin-left:-3px; font:700 15px/1 var(--display); letter-spacing:-.15em; color:var(--muted); cursor:grab; touch-action:none; user-select:none}
.csgrip:hover{color:var(--gold-hi)}
.csitem.dragging{opacity:.85; position:relative; z-index:2}
.csitem.dragging .csbox{border-color:var(--gold); box-shadow:0 0 14px rgba(143,99,210,.5)}
.csitem.dragging .csgrip{cursor:grabbing; color:var(--gold-hi)}
/* what a row also is: a key bound to it, or a place on the button row */
.cskeytag,.cstag{flex-shrink:0; font:600 10px/1 var(--display); letter-spacing:.08em; text-transform:uppercase; border:1px solid var(--line-strong); border-radius:3px; padding:4px 5px; color:var(--gold-hi)}
.cstag{color:var(--muted)}
/* a row's name with its tags at the right of the same line (the key, BUTTON), so a row stays short; @target's two stack */
/* a row is a small grid: the name, then its commands right under it (one to a line), with the tags at the right beside both */
.navrow .nm.csnm{display:grid; grid-template-columns:minmax(0,1fr) auto; column-gap:10px; align-items:start}
.csnm .csname{grid-column:1; grid-row:1}
.csnm .cstags{grid-column:2; grid-row:1 / span 2; align-self:start}
.csnm small{grid-column:1; grid-row:2}
.csname{min-width:0; overflow-wrap:anywhere}
.cstags{display:flex; gap:5px; flex-shrink:0; align-items:center; margin-left:auto; justify-self:end}
.cstags.stack{flex-direction:column; align-items:flex-end; gap:4px}
.cstag,.cskeytag{display:inline-flex; align-items:center; white-space:nowrap}
/* (.navrow img sizes every picture in a row to 24px, so this one says its own size more firmly) */
.navrow .cstagicon,.cstagicon{width:12px; height:12px; margin-right:4px; object-fit:contain; flex-shrink:0}
/* an alias's ON / OFF: at the top right of its row, level with its name, with ON or OFF written to the left of the switch (.sw is the
   Settings switch). The row keeps clear of it so a long name never runs underneath */
.csbox{position:relative}
/* the alias editor: the ON / OFF switch at the top right of the title, level with the alias's name */
.cstitlewrap{position:relative}
.cstoggle.cstitlesw{top:15px; right:0}
.cstitlewrap h3{padding-right:96px}
.csbox.hasextra .navrow{padding-right:92px}
.cstoggle{position:absolute; top:8px; right:11px; display:flex; align-items:center; gap:8px}
.cstogglelabel{min-width:22px; text-align:right; font:600 10px/1 var(--display); letter-spacing:.1em; color:var(--muted)}
.cstogglelabel.on{color:#8ff08a}
/* GROUP FOLDERS in the Client Settings lists: the heading row carries the group's own ON / OFF at its right. The row stands where .hhead
   would, so the usual gap under an open folder's heading (--gap-fold) is kept, and the rule that hides a closed folder's body leaves it be */
.navrows + .hgroup.csfold,.hgroup.csfold + .hgroup.csfold{margin-top:var(--gap-list)}
.cshrow{display:flex; align-items:center; gap:10px}
.cshrow .hhead{flex:1; width:auto; min-width:0}
.cshrow .cstoggle{position:static; flex-shrink:0}
.hgroup.csfold:not([data-closed="true"]) > .cshrow{margin-bottom:var(--gap-fold)}
.hgroup[data-closed="true"] > .cshrow{display:flex}
.csfold[data-off="true"] > .navrows{opacity:.6}
/* A GROUP'S ON / OFF (Lizzy: so it is not mistaken for the slider on each item): a teal capsule with its own words. On a row of the Groups
   list it sits at the top right like an item's switch; in a folder's heading it stands in the row */
.gsw{display:inline-flex; flex-shrink:0; cursor:pointer; border:1px solid var(--line-strong); border-radius:5px; background:#08070d; padding:0;
  font:600 10px/1 var(--display); letter-spacing:.1em; color:var(--muted)}
.gsw span{padding:6px 9px; border:1px solid transparent; transition:background .15s, box-shadow .15s}
.gsw .gon{border-radius:4px 0 0 4px}
.gsw .goff{border-radius:0 4px 4px 0}
/* ON: the glowing green outline the lit rows use (.navrow.on), white letters, no fill */
.gsw[aria-checked="true"] .gon{color:#fff; border-color:rgba(143,240,138,.6); box-shadow:0 0 10px rgba(143,240,138,.35), inset 0 0 10px rgba(143,240,138,.08)}
/* OFF: a filled, quiet segment */
.gsw[aria-checked="false"] .goff{color:var(--text-hi); background:#2a2430}
.gsw:focus-visible{outline:2px solid var(--gold-hi); outline-offset:2px}
/* (the same distance from the right edge as an item's own slider, so they line up down the list) */
.csbox .gsw{position:absolute; top:8px; right:11px}
.csbox.hasgsw .navrow{padding-right:100px}
/* (in a folder's heading and on a group's page it sits flush right, level with the move arrows and RENAME) */
.cshrow .gsw,.csgstate .gsw{position:static}
/* how many you have of how many you may: a line, and a thin bar that turns amber near the limit and red when full */
.csusage{display:flex; flex-direction:column; gap:5px; margin:4px 0 8px}
.csuhead{display:flex; justify-content:space-between; gap:10px; font:600 10px/1 var(--display); letter-spacing:.1em; text-transform:uppercase; color:var(--gold)}
.csuhead b{font-weight:600; color:var(--text-hi)}
.csubar{height:4px; border-radius:2px; background:#08070d; border:1px solid var(--line); overflow:hidden}
.csubar i{display:block; height:100%; background:linear-gradient(90deg,#4a3079,var(--violet-hi))}
.csusage.near .csubar i{background:#d9a63a}
.csusage.near .csuhead b{color:#e8c36a}
.csusage.full .csubar i{background:var(--err)}
.csusage.full .csuhead b{color:var(--err)}
/* SELECT ALL at the left and CLEAR at the right, over a divider, above a list of ticks */
.csallrow{display:flex; justify-content:space-between; padding-bottom:8px; border-bottom:1px solid var(--line)}
/* one group's page: the name with RENAME beside it, the count and the switch under it, then what is in it */
.vtitle .csrename{flex:none; align-self:center}
.csgstate{display:flex; align-items:center; justify-content:space-between; gap:10px; margin:6px 0 2px}
.csgstate .cblurb{margin:0}
.csinline{margin:0 0 var(--gap-fold)}
.csconfirm{display:flex; flex-direction:column; gap:12px; margin-top:var(--gap-fold)}
.csconfirm p{margin:0}
.hgroup + .csbtns,.hgroup + .csconfirm,.paneempty + .csbtns,.paneempty + .csconfirm{margin-top:24px}
/* the new group's name box, and what a name may hold, appear under the Group choice once Create New Group… is chosen */
.csnewgroup{display:flex; flex-direction:column; gap:6px}
.csnewgroup[hidden]{display:none}
.csnewlead{font:400 14px/1.3 var(--serif); letter-spacing:0; text-transform:none; color:var(--text-hi)}
select.cstext option.csoff{color:#7d7487}
/* the quick row's own options (hide the exit buttons / the potion icons), under the Buttons list */
.csopts{display:flex; flex-direction:column; gap:14px; margin-top:26px; padding-top:16px; border-top:1px solid var(--line)}
.csopts .csrow > span{display:flex; flex-direction:column; gap:3px}
.csopts .csrow small{font:400 13px/1.35 var(--serif); letter-spacing:0; color:var(--muted)}
/* copying some groups: a tick list for the chosen character */
.csgroupchoices{display:flex; flex-direction:column; gap:8px}
.csgroupchoices[hidden],.csfield[hidden]{display:none}
.csgroupchoices .cscheck span small{display:block; font:400 13px/1.3 var(--serif); color:var(--muted)}
.csgroupchoices .csall{align-self:flex-start}
/* hidden by the Buttons options */
#quickrow[data-hide-exits="true"] .chip.dir,#quickrow[data-hide-pots="true"] .pots{display:none}
.csmove{display:flex; flex-direction:column; gap:4px; flex-shrink:0}
.csmove .mini{flex:1; padding:3px 9px}
/* the editors: room between the fields (--cs-gap), and an empty message line takes none */
.csform{--cs-gap:22px; display:flex; flex-direction:column; gap:var(--cs-gap)}
.cslabel{display:flex; flex-direction:column; gap:5px; font:600 11px/1.2 var(--display); letter-spacing:.08em; text-transform:uppercase; color:var(--gold)}
.cslabel small{font:400 13px/1.4 var(--serif); letter-spacing:0; text-transform:none; color:var(--muted)}
.cstext{width:100%; background:#08070d; border:1px solid var(--line-strong); border-radius:4px; color:var(--text-hi); font:14px/1.35 var(--serif); padding:7px 10px; outline:none; resize:vertical}
textarea.cstext{font-family:var(--mono, var(--serif)); font-size:13px; min-height:72px}
/* (a phone zooms the whole page when a field's text is smaller than 16px, so touch screens keep that size) */
@media (pointer:coarse), (max-width:759px){.cstext,textarea.cstext,.ctxmenu .csmfind,.ctxmenu .csmnew .cstext{font-size:16px}}
.cstext:focus{border-color:var(--gold); box-shadow:0 0 12px rgba(143,99,210,.35)}
.cskeyrow{display:flex; gap:6px; align-items:stretch}
.cskeyrow .cstext{flex:1; min-width:0; cursor:pointer}
.csrow{display:flex; align-items:center; justify-content:space-between; gap:10px; font:600 13px/1.3 var(--display); letter-spacing:.04em; color:var(--text-hi)}
.csfield{margin:0; padding:8px 10px; border:1px solid var(--line); border-radius:5px; display:flex; flex-direction:column; gap:6px}
.csfield legend{padding:0 6px; font:600 11px/1 var(--display); letter-spacing:.08em; text-transform:uppercase; color:var(--gold)}
.cscheck{display:flex; align-items:center; gap:8px; font:15px/1.3 var(--serif); color:var(--text-hi); cursor:pointer}
.csform[data-csform="copy"]{--cs-gap:22px}
.csbig{display:block; width:100%; padding:15px 16px; font:600 14px/1 var(--display); letter-spacing:.1em; color:var(--gold-hi); cursor:pointer;
  border:1px solid var(--gold); border-radius:5px; background:linear-gradient(180deg,#4a3079,#2a1a45)}
.csbig:hover{background:linear-gradient(180deg,#5a3a92,#33205a)}
.csbig:focus-visible{outline:2px solid var(--gold-hi); outline-offset:2px}
.csbig.quiet{color:var(--text); border-color:var(--line-strong); background:none}
.csbtns{display:flex; flex-direction:column; gap:10px}
.csbig.danger{color:#ffb4a8; border-color:rgba(255,130,110,.55); background:none}
.csbig.danger:hover{background:rgba(255,130,110,.12)}
.cslist{margin:0; padding-left:20px; font:15px/1.5 var(--serif); color:var(--text)}
.csstrong{font-weight:600; margin:0}
/* the help view in Client Settings: short sections, each with an example and a button that adds it */
.cshelp{display:flex; flex-direction:column; gap:8px; padding:10px 0 4px; border-top:1px solid var(--line)}
.cshelp h4{margin:0; font:600 15px/1.2 var(--display); letter-spacing:.05em; color:var(--gold-hi)}
.cshelp p{margin:0; font-size:15px; line-height:1.5; color:var(--text)}
.cshelp code{font:13px/1.4 var(--mono, monospace); color:var(--text-hi); background:rgba(255,255,255,.06); border-radius:3px; padding:1px 5px}
.csex{display:flex; flex-direction:column; align-items:flex-start; gap:7px; padding:9px 11px; border:1px solid var(--line); border-radius:5px; background:rgba(255,255,255,.02)}
.csex .csexshow{color:var(--text-hi)}
/* @target: the target itself right under the name, with the target icon the input bar uses */
/* (.navrow .nm small is display:block, so this says it more firmly) */
.navrow .nm small.cstargetline{display:flex; align-items:center; gap:6px}
.navrow .nm small.cstargetline .cstagicon{width:15px; height:15px; margin:0}
/* the help view: roomier sections, an icon and an IN SHORT line for each, examples in a box, and tips */
.cshelp{gap:14px; padding:32px 0 18px}
.cshelp h4{display:flex; align-items:center; gap:10px}
.cshelp h4 img{width:24px; height:24px; object-fit:contain}
.cshelp kbd{font:600 12px/1 var(--mono, monospace); color:var(--text-hi); background:#15131f; border:1px solid var(--line-strong); border-bottom-width:2px; border-radius:4px; padding:2px 6px}
.csshort{display:flex; flex-direction:column; align-items:flex-start; gap:6px; font:600 16px/1.35 var(--serif); color:var(--text-hi)}
/* IN SHORT, TRY IT and TIP are labels (a small picture and gold letters, no box), so they are not mistaken for the buttons */
.cslabeltag{display:inline-flex; align-items:center; gap:5px; font:600 10px/1 var(--display); letter-spacing:.14em; color:var(--gold)}
.cslabeltag svg{flex-shrink:0}
.cslabel-tip{color:#e5c072}
.csaddbtn{align-self:flex-start; display:inline-flex; align-items:center; gap:6px; padding:9px 12px; font:600 11px/1 var(--display); letter-spacing:.08em; color:var(--gold-hi); cursor:pointer;
  border:1px solid var(--gold); border-radius:4px; background:linear-gradient(180deg,#4a3079,#2a1a45)}
.csaddbtn::before{content:"+"; font:700 14px/1 var(--display)}
.csaddbtn:hover{background:linear-gradient(180deg,#5a3a92,#33205a)}
.csaddbtn:focus-visible{outline:2px solid var(--gold-hi); outline-offset:2px}
.cssub{display:flex; flex-direction:column; gap:10px; margin-top:6px; padding:12px; border-left:2px solid var(--gold); background:rgba(255,255,255,.025); border-radius:0 5px 5px 0}
.cssub h5{margin:0; font:600 14px/1.3 var(--display); letter-spacing:.04em; color:var(--gold-hi)}
.cstip{display:flex; flex-direction:column; align-items:flex-start; gap:6px; color:var(--muted); font-style:italic}
.csinfo{align-self:flex-start; padding:0; font:italic 14px/1.4 var(--serif); color:var(--gold-hi); text-decoration:underline; cursor:pointer}
.csinfo:hover{color:#fff}.csexnote{font-size:14px; color:#8ff08a}
.csexnote.cserr{color:#ffb4a8}
.csexnote:empty{display:none}
.csmsg{min-height:1.4em; font-size:14px; line-height:1.4; color:var(--text)}
.csform .csmsg:empty{min-height:0; margin-top:calc(-1 * var(--cs-gap))}
.cserr{color:#ffb4a8}
.cshint{color:var(--muted)}
/* a hometown is not a citizenship: its fact bars carry a smaller icon beside the label */
.fact .fk:has(> .fi){display:flex; align-items:center; gap:8px}
/* room around the residencies section, and under an organisation's mates heading before its tiles (Session D6) */
.databody .facts + .qrewards{margin-top:14px}
.databody .qrewards:has(+ .rowbtns){margin-bottom:14px}
.hgroup[data-hgroup^="aff-mates-"] > .qstats{margin-bottom:14px}
.fact .fk > .fi{width:18px; height:18px; object-fit:contain; flex-shrink:0; margin:0}
/* a mate's rank mark: a special rank's badge, or the numeric rank as CWHO prints it */
.plist.classmates .wn:has(> .nm),.plist.classmates span.wn{align-items:center}
.rkb,.rkn{flex-shrink:0; width:26px; height:26px; display:grid; place-items:center; align-self:center}
.rkb svg{width:19px; height:19px; color:var(--gold-hi)}
.rkn{border:1px solid var(--line-strong); border-radius:50%; font:600 12px/1 var(--display); color:var(--muted); font-variant-numeric:tabular-nums}
.tagon{flex-shrink:0; font:600 10px/1 var(--display); letter-spacing:.1em; text-transform:uppercase; color:#8ff08a; border:1px solid currentColor; border-radius:3px; padding:4px 5px;
  box-shadow:0 0 8px rgba(143,240,138,.3)}
.tagon.amber{color:#e5c072; box-shadow:0 0 8px rgba(229,192,114,.25)}
.steps{list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:2px}
.step{border:1px solid transparent; border-radius:5px}
.srow{width:100%; display:flex; align-items:flex-start; gap:10px; padding:8px; text-align:left; border-radius:5px; font-size:15px; line-height:1.3}
.srow img{width:22px; height:22px; flex-shrink:0; object-fit:contain}
.srow .nm{flex:1; min-width:0; overflow-wrap:anywhere; color:var(--text-hi)}
.srow .nm small{display:block; margin-top:2px; font:400 13.5px/1.35 var(--serif); color:var(--muted)}
button.srow:hover .nm{color:#fff}
.step.done .nm,.step.locked .nm{color:var(--muted)}
.step .qst,.step .tagon{align-self:center; margin-left:auto; white-space:nowrap}
.step.current{border-color:rgba(143,240,138,.5); box-shadow:0 0 12px rgba(143,240,138,.25), inset 0 0 12px rgba(143,240,138,.05)}
.qstep{margin:0; padding:0 10px 10px 40px; font-size:14px; line-height:1.4; color:var(--text)}
.qignored .wn{font-size:13.5px; color:var(--muted); padding:5px 9px}
/* an ignored quest's icon is the active quest's, greyed (Lizzy) */
.plist.qignored .wn:has(> .nm){align-items:center}
.qignored .wn img{width:18px; height:18px; flex-shrink:0; object-fit:contain; filter:grayscale(1) brightness(.7); opacity:.75}
.qignored li{background:none; border-style:dashed}
.cards{display:flex; flex-direction:column; gap:var(--gap-list)}
.card{padding:10px 12px; border:1px solid var(--line); border-radius:5px; background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.01)); display:flex; flex-direction:column; gap:6px}
.card h4{margin:0; font:600 15px/1.2 var(--display); color:var(--gold-hi); letter-spacing:.03em; display:flex; align-items:center; gap:8px}
.card h4 img{width:20px; height:20px; object-fit:contain}
.card h4 small{margin-left:auto; font:500 13px/1 var(--serif); color:var(--muted); letter-spacing:0}
.card p{margin:0; font-size:15px; line-height:1.4}
.card .track{height:7px; flex:none}
.card .meta{font-size:13px; color:var(--muted); font-variant-numeric:tabular-nums}
.card.done h4{color:var(--text)}
.card.done{opacity:.72}

/* channels */
.chrow{display:flex; align-items:center; gap:10px; padding:9px 10px; border:1px solid var(--line); border-radius:4px; background:rgba(255,255,255,.02)}
/* a channel's dot: green while you are listening (gray when muted, below) */
.chrow .dot{width:10px; height:10px; border-radius:50%; flex-shrink:0; background:#8ff08a; box-shadow:0 0 8px rgba(143,240,138,.7)}
.chrow .nm{flex:1; min-width:0; font-size:15px; color:var(--text-hi)}
.chrow .nm small{display:block; color:var(--muted); font-size:13px}
/* CHANNELS pane (Session D7): a muted channel dims, its name opens its tab, and the toggles stay together on a narrow pane */
.chrow .nm{overflow-wrap:anywhere}
.chrow.muted .nm{opacity:.6}
.chrow.muted .dot{background:var(--muted); box-shadow:none}
.chgo{font:inherit; color:inherit; text-align:left; padding:0}
.chgo:hover,.chgo:focus-visible{color:var(--gold-hi); text-decoration:underline; text-decoration-color:rgba(236,210,154,.5); text-underline-offset:3px}
.chrow .tog{flex-shrink:0}
/* a channel's row (Lizzy): the unread count where the dot is, then its name, what it is across the whole row, and under that its
   switches, each with its words beside it (.chopt). The Tells tab on Social is the same row */
/* (a grid: the dot or count is centred down the whole card, the name and its buttons are the first line, and the description sits
   right under the name, lined up with it) */
.chrow.chan{display:grid; grid-template-columns:22px minmax(0,1fr) auto; grid-template-areas:"lead nm opts" "lead desc desc"; column-gap:10px; row-gap:1px; align-items:center}
.chrow.chan .lead{grid-area:lead}
.chrow.chan .nm{grid-area:nm}
.chrow.chan .chopts{grid-area:opts}
.chrow .lead{display:grid; place-items:center; width:22px; height:22px; flex-shrink:0}
.chrow .lead .badge{min-width:22px}
.chrow:not(.muted) .lead:has(.badge:not([data-n="0"])) .dot{display:none}
/* UNREAD COUNTS (a channel's; Lizzy: make them stand out, but not obnoxiously): red wherever they show, plain in the tab strip, and
   in the Channels pane a soft glow and a ring that pings out gently and then rests. The ring is the "Other animations" setting's
   (data-anim), and stops for anyone whose device asks for reduced motion */
.badge[data-badge]{background:linear-gradient(180deg,#f0806e,#c4432f); color:#fff}
.chrow .badge[data-badge]{position:relative; box-shadow:0 0 6px rgba(234,117,99,.55)}
.chrow .badge[data-badge]::after{content:""; position:absolute; inset:-1px; border-radius:inherit; border:2px solid rgba(240,128,110,.7); opacity:0; pointer-events:none}
.app:not([data-anim="off"]) .chrow .badge[data-badge]:not([data-n="0"])::after{animation:badge-ping 3.4s ease-out infinite}
@keyframes badge-ping{0%{transform:scale(1); opacity:.55} 30%,100%{transform:scale(1.6); opacity:0}}
.app[data-motion="reduced"]:not([data-anim="off"]) .chrow .badge[data-badge]:not([data-n="0"])::after{animation:soft-glow 5s ease-in-out infinite !important}
/* a channel's notifications button: a bell, lit while its messages count and alert, struck through when off */
.chopts .tog.bell{padding:3px 6px}
.tog.bell svg{display:block; width:14px; height:14px; fill:none; stroke:currentColor; stroke-width:1.8; stroke-linecap:round; stroke-linejoin:round}
.tog.bell[aria-pressed="true"] .off{display:none}
/* a muted channel has no unread count, only its gray dot */
.chrow.muted .lead .badge{display:none}
.chrow.chan .chdesc{grid-area:desc; color:var(--muted); font-size:13px; line-height:1.3}
.chopts{display:flex; gap:6px; flex-shrink:0}
/* MUTE and TAB: small word buttons; MUTE lights red while the channel is muted */
.chopts .tog{padding:4px 8px; font-size:10px; min-height:0}
.chopts .tog.mute[aria-pressed="true"]{color:var(--err); border-color:var(--err); background:rgba(234,117,99,.12)}
.chopt{display:inline-flex; align-items:center; gap:6px; font:600 10.5px/1 var(--display); letter-spacing:.04em; text-transform:uppercase; color:var(--muted); white-space:nowrap}
/* a switch: on is lit violet with its knob to the right */
.sw{position:relative; width:40px; height:22px; flex-shrink:0; border-radius:11px; border:1px solid var(--line-strong); background:#08070d; transition:background .15s, border-color .15s}
.sw span{position:absolute; top:2px; left:2px; width:16px; height:16px; border-radius:50%; background:var(--muted); transition:transform .15s, background .15s}
.sw[aria-checked="true"]{border-color:var(--gold); background:linear-gradient(180deg,#4a3079,#2a1a45)}
.sw[aria-checked="true"] span{transform:translateX(18px); background:var(--gold-hi)}
.sw:focus-visible{outline:2px solid var(--gold-hi); outline-offset:2px}
/* THE CHANNEL HEADER above a channel's, All Channels' or Tells' text (renderChanHead): the name large between rules. A channel's
   has its Listening switch at the right, so the left gets a space as wide, which keeps the name (and the line under it) centred */
.chanhead{flex-shrink:0; padding:8px 4px 6px; border-bottom:1px solid var(--line)}
.chanhead .chtitle{display:flex; align-items:center; gap:10px}
.chanhead.hassw .chtitle::before{content:""; width:118px; flex-shrink:0}
.chanhead h3{flex:1; min-width:0; display:flex; align-items:center; gap:12px; margin:0; font:600 19px/1.2 var(--display); letter-spacing:.14em; text-transform:uppercase; color:var(--gold-hi); overflow-wrap:anywhere}
.chanhead h3::before,.chanhead h3::after{content:""; flex:1; height:1px; min-width:12px; background:linear-gradient(90deg,transparent,var(--gold))}
.chanhead h3::after{background:linear-gradient(270deg,transparent,var(--gold))}
.chanhead .chsw{display:flex; align-items:center; justify-content:flex-end; gap:8px; width:118px; font:600 10.5px/1 var(--display); letter-spacing:.06em; text-transform:uppercase; color:var(--muted); flex-shrink:0}
.chanhead p{margin:4px 0 0; text-align:center; font-size:13.5px; color:var(--muted)}
.chanhead.muted h3{color:var(--muted)}
/* on a phone (Lizzy) the name is on the left with the Listening switch at the right, the rules go, and the line about the channel
   sits left-aligned beneath them both */
@media (max-width:759px){
  .chanhead.hassw .chtitle::before{display:none}
  .chanhead .chsw{width:auto}
  .chanhead h3{justify-content:flex-start}
  .chanhead h3::before,.chanhead h3::after{display:none}
  .chanhead p{text-align:left}
}
/* the Tells tab row on Social: its name and Show tab switch on one line, the line about it under both at full width */
.tellrow{display:block}
.tellhead{display:flex; align-items:center; justify-content:space-between; gap:10px}
.tellhead .nm{font-size:15px; color:var(--text-hi)}
.tellrow > small{display:block; margin-top:6px; color:var(--muted); font-size:13px; line-height:1.35}
/* MESSAGE TIMES (Lizzy): a channel's live lines carry their time as the history's do, unless the Timestamps setting already
   puts a time in every line's margin; the history's own are wrapped (showHistory) so the setting hides them */
.app[data-chtime="show"][data-stamps="off"] .ln[data-ch^="c"]:not([data-hist]) > .ts{display:inline; white-space:pre; color:var(--muted); font:400 11.5px/1.4 var(--mono); font-style:normal}
/* (the same small gray time as the margin ones, so there is one look for a time; the history's only has hours and minutes) */
.hm .hmt{white-space:pre; color:var(--muted); font:400 11.5px/1.4 var(--mono); font-style:normal}/* with Timestamps on, a history line's time hangs in the margin too, in line with the live ones */.app[data-stamps="on"] .output .hm .hmt{display:inline-block; width:70px; margin-left:-70px; vertical-align:baseline}
.app[data-chtime="hide"] .hm{display:none}
/* a row of filter chips or tabs that scrolls sideways fades at its right edge while more lies past it (data-hscroll, markMore) */
#chantabs[data-more="true"]{-webkit-mask-image:linear-gradient(90deg, #000 calc(100% - 28px), transparent); mask-image:linear-gradient(90deg, #000 calc(100% - 28px), transparent)}
/* a channel tab's history (loaded above what arrived live), and the button for the page before it (centred across the whole
   output, not the 100-column width a line is held to) */
.ln[data-hist]{opacity:.85}
.ln-older{text-align:center; max-width:none}
/* THE GAP UNDER A FOLD'S HEADING, one for every collapsible heading (Lizzy: a regular ask; check it whenever a new one is made):
   open folds only, so a folded heading sits as close to the next as ever. Inside Social's and the Channels pane's folds the
   items are spaced by the same amount */
.hgroup:not([data-closed="true"]) > .hhead{margin-bottom:var(--gap-fold)}
.hgroup:is([data-hgroup^="social-"],[data-hgroup^="channels-"]) > :not(.hhead) + *{margin-top:var(--gap-fold)}
.tog{font:600 10.5px/1 var(--display); letter-spacing:.06em; padding:7px 8px; border-radius:3px; border:1px solid var(--line); color:var(--muted); white-space:nowrap}
.tog[aria-pressed="true"]{color:var(--gold-hi); border-color:var(--gold); background:linear-gradient(180deg,#4a3079,#2a1a45)}
.tog:disabled{opacity:.45}
.panenote{font-size:14px; color:var(--muted); margin:0; line-height:1.4}

/* ---------- bottom bar, settings, toasts ---------- */
.bottomnav{display:none}
.settings{
  position:fixed; z-index:40; top:calc(76px + env(safe-area-inset-top, 0px)); right:max(28px, calc(env(safe-area-inset-right, 0px) + 16px)); width:min(360px, calc(100vw - 32px));
  max-height:calc(100% - 100px); overflow-y:auto;
  background:var(--panel-2); border:1px solid var(--line-strong); border-radius:6px; padding:16px; box-shadow:0 18px 40px rgba(0,0,0,.65);
  display:flex; flex-direction:column; gap:14px;
}
.settings header{display:flex; align-items:center; justify-content:space-between}
.settings h2{margin:0; font:600 16px/1 var(--display); color:var(--gold-hi); letter-spacing:.06em}
.setrow{display:flex; flex-direction:column; gap:6px}
.setrow > span{font-size:14px; color:var(--muted)}
.seg{display:flex; border:1px solid var(--line-strong); border-radius:4px; overflow:hidden}
.seg button{flex:1; padding:8px 6px; font-size:14.5px; color:var(--muted); border-left:1px solid var(--line)}
.seg button:first-child{border-left:0}
.seg button[aria-pressed="true"]{color:var(--gold-hi); background:linear-gradient(180deg,#4a3079,#2a1a45)}
.settings .note{font-size:13.5px; color:var(--muted); margin:-6px 0 0; line-height:1.4}
.scrim{position:fixed; inset:0; z-index:35; background:rgba(0,0,0,.35)}
/* ATMOSPHERE POP-UP (was "The Sky"): the top bar's time and weather in words, wide enough that
   the date ("12th of Artus, 512 years AGA") keeps to one line (Lizzy) */
.skypop{position:fixed; z-index:40; top:calc(76px + env(safe-area-inset-top, 0px)); right:max(110px, calc(env(safe-area-inset-right, 0px) + 16px)); width:min(370px, calc(100vw - 32px));
  background:var(--panel-2); border:1px solid var(--line-strong); border-radius:6px; padding:12px 14px; box-shadow:0 18px 40px rgba(0,0,0,.65);
  display:flex; flex-direction:column; gap:8px}
.skypop h2{margin:0 0 2px; font:600 14px/1 var(--display); color:var(--gold-hi); letter-spacing:.08em}
.skrow{display:grid; grid-template-columns:26px 88px minmax(0,1fr); align-items:center; gap:8px; font-size:15px}
.skrow img{width:24px; height:24px; object-fit:contain}
.skrow span{color:var(--muted); font-size:14px}
.skrow b{font-weight:500; color:var(--text-hi)}

.toasts{position:fixed; z-index:50; top:calc(78px + env(safe-area-inset-top, 0px)); right:max(30px, calc(env(safe-area-inset-right, 0px) + 16px)); display:flex; flex-direction:column; gap:8px; width:min(330px, calc(100vw - 32px)); pointer-events:none}
.toast{pointer-events:auto; display:flex; gap:10px; align-items:flex-start; padding:10px 12px; border-radius:6px;
  background:linear-gradient(180deg,#2a1d42,#17111f); border:1px solid var(--line-strong); box-shadow:0 0 18px rgba(143,99,210,.35), 0 10px 26px rgba(0,0,0,.6);
  animation:toastin .25s ease-out; cursor:pointer}
.toast img{width:30px; height:30px; object-fit:contain; flex-shrink:0}
.toast div{flex:1; min-width:0; font-size:15px; line-height:1.3; overflow:hidden; display:-webkit-box; -webkit-line-clamp:3; -webkit-box-orient:vertical}
.toast b{display:block; color:#ee9fc6; font-weight:500}
.toast button{color:var(--muted); font-size:20px; line-height:1; padding:0 2px}
@keyframes toastin{from{transform:translateY(-8px); opacity:0}to{transform:none; opacity:1}}

/* help window */
.helpwin{position:fixed; z-index:45; left:50%; top:50%; transform:translate(-50%,-50%); width:min(1100px, calc(100vw - 32px)); height:min(700px, calc(100% - 64px));
  display:flex; flex-direction:column; border:22px solid transparent; border-image:url("assets/frame-large.a5ee19e5c3ec.png") 34 fill / 22px stretch;
  box-shadow:0 24px 50px rgba(0,0,0,.7); animation:helpin .2s ease-out}
@keyframes helpin{from{opacity:0}to{opacity:1}}
/* (a div, not a <header>: inside the dialog a header read as a second page banner) */
.helpwin > .winhead{display:flex; align-items:center; gap:10px; padding:0 2px 10px; border-bottom:1px solid var(--line)}
.helpwin > .winhead img{width:28px; height:28px; object-fit:contain}
.helpwin h2{margin:0; font:600 19px/1 var(--display); color:var(--gold-hi); letter-spacing:.08em}
/* Back and Index sit at the right, beside the search box and the close button (Lizzy) */
.helpwin > .winhead .chip{padding:0 10px}
#helpback{margin-left:auto}
.helpsearch{width:min(250px,42%); background:#08070d; border:1px solid var(--line-strong); border-radius:4px; color:var(--text-hi); font:italic 15.5px var(--serif); padding:8px 10px; outline:none}
.helpsearch:focus{border-color:var(--gold); box-shadow:0 0 12px rgba(143,99,210,.35)}
.helpbody{flex:1; min-height:0; display:grid; grid-template-columns:170px minmax(0,1fr); gap:16px; padding-top:12px}
.helptopics{display:flex; flex-direction:column; gap:3px; overflow-y:auto; scrollbar-width:none}
.helptopics .label{padding:4px 10px 6px}
.helptopics button{text-align:left; padding:8px 10px; border-radius:4px; font-size:15.5px; color:var(--muted); border:1px solid transparent; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.helptopics button:hover{color:var(--text-hi); background:rgba(255,255,255,.03)}
.helptopics button[aria-current="true"]{color:var(--gold-hi); border-color:var(--line-strong); background:linear-gradient(180deg,#4a3079,#2a1a45)}
.helptopics .none{color:var(--muted); font-style:italic; padding:8px 10px}
/* the entry scrolls; the Previous/Next bar under it (.helpnav) stays in view */
.helpmain{display:flex; flex-direction:column; min-height:0; min-width:0}
.helptext{flex:1; min-height:0; overflow-y:auto; padding-right:8px; scrollbar-width:thin; scrollbar-color:var(--violet-deep) transparent; container-type:inline-size}
.helptext .helpcontent{font-family:var(--out-font); font-size:var(--out-size); line-height:var(--out-lh); white-space:pre-wrap; overflow-wrap:anywhere; color:var(--text-hi)}
/* help is full of 78-column tables, so on desktop and tablet the fixed-width font fits 80
   columns; phones keep the normal text size and wrap instead (see the phone rules below) */
@media (min-width:760px){
  .helpwin:not([data-font="book"]) .helptext .helpcontent{font-size:min(var(--out-size), calc((100cqi - 8px) / 48.6))}
}
.helplink{display:inline; font:inherit; color:inherit; text-decoration:underline; text-decoration-color:rgba(195,162,245,.45); text-underline-offset:3px; cursor:pointer}
.helplink:hover{color:var(--violet-hi); text-decoration-color:var(--violet-hi)}
/* FLEX DIVIDERS (flexRules in mystic.js): a line-ending dash run, drawn as dashes 1ch apart in
   the line's own colour, as long as the original run (--run) or what's left of the line after
   column --at, whichever is shorter, so it shrinks rather than wraps */
.hrule{display:inline-block; vertical-align:middle; height:1px; margin-top:-1px;
  width:min(calc(var(--run) * 1ch), calc(100% - (var(--at) + 1) * 1ch));
  background:repeating-linear-gradient(90deg, currentColor 0 .62ch, transparent .62ch 1ch)}
/* the spacing between a topic table's names: columns on wide screens, a plain gap on phones */
.hgap{white-space:pre}
/* HELP PREV/NEXT: the entry's neighbours in its category, in a bar under the text */
.helpnav{flex-shrink:0; display:flex; justify-content:space-between; gap:10px; margin-top:10px; padding-top:10px; border-top:1px solid var(--line)}
.helpnav[hidden]{display:none}
.helpnav .chip{padding:0 12px; max-width:48%; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
@media (max-width:759px){
  .hgap{white-space:normal; display:inline-block; width:1.2em}
  .helpwin{width:calc(100vw - 16px); height:calc(100% - 20px); border-width:14px; border-image-width:14px}
  .helpwin > .winhead{flex-wrap:wrap}
  .helpsearch{order:3; width:100%}
  .helpbody{grid-template-columns:minmax(0,1fr); grid-template-rows:auto minmax(0,1fr); gap:10px; padding-top:10px}
  .helptopics{flex-direction:row; overflow-x:auto}
  .helptopics .label{display:none}
  .helptopics button{flex-shrink:0; max-width:60vw}
}

/* ---------- medium (tablets): the left column (you) becomes a drawer ---------- */
@media (max-width:1219px){
  .stage{grid-template-columns:minmax(0,1fr) 280px}
  .youcol{display:none}
  .app[data-view="you"] .youcol{
    display:flex; position:fixed; z-index:30; width:min(380px, calc(100vw - 32px));
    top:calc(84px + env(safe-area-inset-top, 0px)); bottom:calc(14px + env(safe-area-inset-bottom, 0px)); left:max(16px, env(safe-area-inset-left, 0px));
    box-shadow:0 18px 40px rgba(0,0,0,.6);
  }
  .closeyou{display:grid; position:absolute; top:-6px; right:-6px; z-index:2}
  .status .lv{display:none}
  /* below full width the sky keeps its icons, the degrees and the clock; the words are in the pop-up */
  .sky .sk:not(.clock) span:not(.deg){display:none}
  .sky{gap:10px}
}
@media (max-width:1000px){ .navbtn{padding-inline:10px} .conn .lbl{display:none} }

/* ---------- phone ---------- */
@media (max-width:759px){
  .app{grid-template-rows:auto minmax(0,1fr) auto; gap:8px; padding-block:6px 0}
  .topbar{border:0; border-image:none; padding:0; min-height:44px; grid-template-columns:minmax(0,1fr) auto; gap:8px; border-bottom:1px solid var(--line)}
  .brand{height:26px; margin:0}
  .mainnav,.status .item:not(.conn),.status .vsep{display:none}
  /* the phone's top bar holds the logo, the connection light, the sky and the Leave door
     (Lizzy); Settings is the bottom bar's fourth tab, so the gear gives the sky the room */
  .status{gap:4px}
  .status #gear{display:none}
  .status .leavebtn{width:28px; height:30px}
  /* the degrees stay in the Atmosphere pop-up on phones (Lizzy: the bar crowded the logo) */
  .sk .deg{display:none}
  /* PHONES (Lizzy): one view at a time from the bottom bar: Game (the story), Here (the right
     column; its compass stays the floating gem), Character (the left column) and Settings */
  .stage{grid-template-columns:minmax(0,1fr)}
  .frame{border:0; border-image:none; background:none}
  .storycol,.placecol{display:none}
  .app[data-view="game"] .storycol,.app[data-view="here"] .placecol{display:flex}
  .app[data-view="you"] .youcol{position:static; width:auto; box-shadow:none; overflow-y:auto}
  /* the phone's Character view scrolls as one page (Lizzy): the card, Status, Stats and Defences,
     then the menu; an open pane takes the page on its own, so it isn't below all of that */
  .youcol .youbody{flex:none; overflow:visible}
  .app[data-view="you"] .youcol{overflow-x:hidden}
  .youbody,.youcol > :is(.ystats,.sidedefs){order:0}
  /* the ornamental dividers alone mark the sections here (Lizzy: no plain rule under the card).
     Status follows the XP bar with no divider, and its heading matches the XP label's size;
     Stats keeps its divider, Defences follows it with none, and one divider with room to
     breathe sits above the menu (hidden while a pane is open) */
  .idcard{border-bottom:0; padding-bottom:0}
  .ystatus{display:flex; background:none; padding-top:8px}
  /* (Lizzy: no divider between the balance icons and Stats either) */
  .ystats{background:none; padding-top:12px}
  .ystats + .sidedefs{padding-top:14px}
  .youcol[data-pane=""] .youbody{margin-top:26px; padding-top:44px; background:url("assets/div-3.3753519565b2.png") center top / min(200px, 80%) auto no-repeat}
  /* finger scrolling needs no bar (Lizzy: it looked awful) */
  .app[data-view="you"] .youcol{scrollbar-width:none}
  .app[data-view="you"] .youcol::-webkit-scrollbar{display:none}
  .youcol:not([data-pane=""]) > :is(.idcard,.ysect){display:none}
  .closeyou{display:none}
  .placecol .compass-wrap,.placecol > .divider:has(+ .compass-wrap){display:none}
  .mroom{display:block; position:relative; border:1px solid var(--line-strong); border-radius:5px; overflow:hidden; flex-shrink:0; margin-bottom:8px}
  .mroom .art{width:100%; height:112px; object-fit:cover}
  .app[data-art="compact"] .mroom .art{height:80px}
  .app[data-art="off"] .mroom{display:none}
  .mroom .cap{position:absolute; inset:auto 0 0 0; padding:22px 10px 7px; text-align:center; background:linear-gradient(180deg,transparent,rgba(8,7,12,.9) 60%)}
  .mroom .terrname{font-size:18px}
  .mroom .roomname{font-size:14px}
  /* phone top bar: the period (or clock) and weather only; a tap opens the pop-up */
  .sky{gap:8px; padding:3px 6px}
  .skypop{top:calc(52px + env(safe-area-inset-top, 0px)); right:max(16px, env(safe-area-inset-right, 0px))}
  /* phone vitals (Lizzy): health and mana share the first line; the target, state circles,
     cooldowns and defences share the second */
  .vstats .vbar{order:-1; flex:1 1 calc(50% - 14px); min-width:0; max-width:none; font-size:12px; gap:5px}
  .vstats .vnum{display:none}
  .tab{font-size:14px; padding:8px 11px 7px}
  .fonttoggle{display:none}
  .output{padding:12px 4px 58px}
  .app[data-fit="on"]:not([data-font="book"]) .output{font-size:min(var(--out-size), calc((100cqi - 8px) / 48.6))}
  .app[data-stamps="on"] .output{padding-left:58px}
  .app[data-stamps="on"] .output .ln > .ts{width:54px; margin-left:-54px; font-size:10px}
  .app[data-stamps="on"] .output .hm .hmt{width:54px; margin-left:-54px; font-size:10px}
  .app[data-stamps="on"][data-fit="on"]:not([data-font="book"]) .output{font-size:min(var(--out-size), calc((100cqi - 62px) / 48.6))}
  .emptynote{left:4px; right:4px; top:12px}
  .vitals{gap:8px; padding:6px 0}
  .promptline{font-size:12.5px}

  /* floating compass */
  .compassfab{display:grid; place-items:center; position:absolute; right:0; bottom:8px; z-index:6; width:50px; height:50px; border-radius:50%;
    background:radial-gradient(circle,#36215a,#0b0a11 75%); border:1px solid var(--line-strong);
    box-shadow:0 4px 14px rgba(0,0,0,.75), 0 0 12px rgba(143,99,210,.35)}
  .compassfab img{width:36px; height:34px}
  .compassfab[aria-expanded="true"]{border-color:var(--gold-hi); box-shadow:0 0 18px rgba(195,162,245,.65)}
  /* same layout as the room column compass (in/out left, ring, up/down right) on a
     pill-shaped backdrop, so the side buttons never crowd the diagonal gems */
  .floatcompass{display:block; position:absolute; right:-4px; bottom:62px; z-index:5; padding:8px 10px;
    border-radius:999px; border:1px solid var(--line);
    background:radial-gradient(ellipse at center, rgba(10,8,16,.94) 58%, rgba(10,8,16,.78) 100%);
    -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px); box-shadow:0 8px 24px rgba(0,0,0,.6);
    transform-origin:85% 100%; transition:transform .18s ease, opacity .18s}
  .floatcompass[data-open="false"]{transform:scale(.3); opacity:0; pointer-events:none}
  .floatcompass .compass{--cs:172px; --bs:40px; gap:6px}
  /* a bigger star on the phone compass (Lizzy), its spikes reaching in among the gems */
  .floatcompass .cdir.center{--ctr:calc(var(--cs) * .72)}
  .ln-title{font-size:calc(var(--out-size) * 1.2)}
  .app[data-compass="off"] .compassfab,.app[data-compass="off"] .floatcompass{display:none}
  .app[data-compass="off"] .output{padding-bottom:6px}

  /* quick commands: one swipeable row above the input */
  .quickrow{order:1; flex-wrap:nowrap; overflow-x:auto; scrollbar-width:none; padding:2px 0 6px}
  .quickrow::-webkit-scrollbar{display:none}
  .quickrow .chip{flex:1 0 auto; min-height:36px; padding:0 12px; font-size:12px}
  .app[data-compass="on"] .quickrow .chip.dir{display:none}
  .inputwrap{order:2; padding-bottom:8px; padding-top:0}
  .inputbar input{font-size:16px; padding:10px 4px}
  .ibtn.hist,.ibtn.sugg{display:none}

  .bottomnav{display:grid; grid-template-columns:repeat(5,1fr); border-top:1px solid var(--line-strong); margin-inline:-16px; padding-inline:max(8px, env(safe-area-inset-left, 0px)) max(8px, env(safe-area-inset-right, 0px));
    background:linear-gradient(180deg,#15111e,#0a0910)}
  .bnav{display:flex; flex-direction:column; align-items:center; gap:4px; padding:9px 0 10px; font-size:13px; color:var(--muted); position:relative}
  .bnav img,.bnav svg{width:24px; height:24px; object-fit:contain; opacity:.75}
  /* Social's badge is who's online, over the corner of its icon */
  .bnav .badge{position:absolute; top:3px; left:calc(50% + 6px); min-width:16px; padding:2px 4px; font-size:10px}
  .bnav svg{color:var(--gold)}
  .bnav[aria-current="true"]{color:var(--gold-hi)}
  .bnav[aria-current="true"] img,.bnav[aria-current="true"] svg{opacity:1; filter:drop-shadow(0 0 6px rgba(195,162,245,.7))}
  .bnav[aria-current="true"]::before{content:""; position:absolute; top:-1px; left:22%; right:22%; height:2px; background:var(--violet-hi); box-shadow:0 0 10px var(--violet-hi)}
  .settings{top:auto; bottom:calc(70px + env(safe-area-inset-bottom, 0px)); right:max(16px, env(safe-area-inset-right, 0px)); width:calc(100vw - 32px); max-height:70%}
  .leavemenu{top:auto; bottom:calc(70px + env(safe-area-inset-bottom, 0px)); right:max(16px, env(safe-area-inset-right, 0px)); width:calc(100vw - 32px)}
  .toasts{top:calc(56px + env(safe-area-inset-top, 0px)); right:max(16px, env(safe-area-inset-right, 0px))}
}

/* ---------- accessibility mode ---------- */
.app[data-contrast="on"]{--text:#ffffff; --text-hi:#ffffff; --muted:#d0d0d0; --gold:#ffd75e; --gold-hi:#ffe48f; --exit:#7cc7ff; --line:#9a9a9a; --line-strong:#ffffff; --echo:#c8c8c8; --out-size:17px}
.app[data-contrast="on"] .frame,.app[data-contrast="on"] .topbar{border:2px solid #fff; border-image:none; background:#000; padding:10px}
.app[data-contrast="on"] .art,.app[data-contrast="on"] .divider{display:none}
.app[data-contrast="on"] .roomcard .cap,.app[data-contrast="on"] .mroom .cap{position:static; background:#000}
.app[data-contrast="on"] .tab[aria-selected="true"],.app[data-contrast="on"] .navbtn[aria-current="true"]{background:#fff; color:#000}
.app[data-contrast="on"] .exitlink,.app[data-contrast="on"] .output a{text-decoration-color:currentColor}
body:has(.app[data-contrast="on"]){background:#000}

/* MOTION: Reduced and Off both still everything (the touches above put their own slow fades back under Reduced) */
:root:is([data-motion="reduced"],[data-motion="off"]) *,:root:is([data-motion="reduced"],[data-motion="off"]) *::before,
:root:is([data-motion="reduced"],[data-motion="off"]) *::after{transition:none!important; animation:none!important}
/* a link inside a pane's sentence (wildcards and variables → the alias help) */
.cblurb .hlink{display:inline; font:inherit; letter-spacing:inherit; color:var(--gold-hi); text-decoration:underline; text-underline-offset:2px; background:none; border:0; padding:0; cursor:pointer}
.cblurb .hlink:hover,.cblurb .hlink:focus-visible{color:#fff}
/* CHANNEL ORDER (Lizzy): a Channels row is a box with the grip to its left and the ▲ ▼ arrows to its right, outside it, as on Client
   Settings' Buttons. In the box: count, name ▾, then MUTE and the bell side by side with TAB under them (the same width as the pair). What
   a channel is shows, under a divider, when its little arrow is tapped. A tab can also be dragged along the strip with a mouse */
.chitem{display:flex; align-items:stretch; gap:6px}
.chitem .chrow{flex:1; min-width:0}
.chrow.chan{grid-template-columns:18px minmax(0,1fr) auto; grid-template-areas:"lead nm opts" "desc desc desc"; column-gap:6px; row-gap:0; padding:7px 8px 7px 11px}
.chrow.chan .lead{width:18px; height:18px}
.chrow.chan .lead .badge{min-width:18px; height:18px; padding:0 3px; font-size:10px; line-height:18px}
.chrow.chan .dot{width:8px; height:8px}
.chrow.chan .chopts{grid-area:opts; display:grid; grid-template-columns:auto auto; gap:4px}
.chrow.chan .chopts .tog{padding:4px 6px}
.chrow.chan .chopts .tog.bell{padding:3px 6px}
.chitem .csmove .mini{padding:3px 7px}
.chrow.chan .chopts .chtabbtn{grid-column:1 / -1}
.chrow.chan .chdesc{grid-area:desc; display:none}
.chrow.chan.open .chdesc{display:block; margin-top:8px; padding:8px 4px 2px; border-top:1px solid var(--line)}
.chinfo{display:inline-grid; place-items:center; width:18px; height:18px; margin-left:4px; padding:0; vertical-align:middle; color:var(--muted); border:1px solid var(--line); border-radius:50%; cursor:pointer; transition:transform .15s}
.chinfo:hover,.chinfo:focus-visible{color:var(--gold-hi); border-color:var(--line-strong)}
.chrow.open .chinfo{transform:rotate(180deg)}
.chitem .csgrip{align-self:center}
.chitem.dragging{opacity:.85; position:relative; z-index:2}
.chitem.dragging .chrow{border-color:var(--gold); box-shadow:0 0 14px rgba(143,99,210,.5)}
.chitem.dragging .csgrip{cursor:grabbing; color:var(--gold-hi)}
.tabs .tab.dragging{opacity:.9; border-color:var(--gold); box-shadow:0 0 12px rgba(143,99,210,.5); position:relative; z-index:2}
.tabs:has(.tab.dragging){cursor:grabbing}
/* a tab's close mark sits at its top right (room is kept for it), and the + lists the tabs that are closed */
.tab:has(.tabx){position:relative; padding-right:22px}
.tab .tabx{position:absolute; top:2px; right:3px; padding:1px 4px; font-size:13px; line-height:1; color:var(--muted); border-radius:3px; cursor:pointer}
.tab .tabx:hover{color:#fff; background:rgba(234,117,99,.35)}

/* OUTPUT TRIGGERS (Phase 5b Round 2): the colour picker (favourites first, then "+ More colours…" with a name search and family chips),
   swatches on list rows, and coloured text in the story. Spacing follows the pane's: --gap-list between rows of things. */
.cssw{display:inline-block; width:11px; height:11px; border-radius:3px; border:1px solid rgba(255,255,255,.35); margin-right:5px; flex:none; vertical-align:-1px}
.cscolour{gap:var(--gap-list)}
.cscnow{display:flex; align-items:center; gap:8px; min-height:26px; font:600 14px/1.2 var(--serif); letter-spacing:0; text-transform:none; color:var(--text-hi)}
.cscnow .cssw{width:20px; height:20px; margin:0; border-radius:4px}
.cscnone{font-weight:400; color:var(--muted)}
.cscclear{margin-left:auto; padding:3px 8px; line-height:1}
.cscfavs{margin-bottom:0}
.cscgroup{font:600 11px/1.2 var(--display); letter-spacing:.08em; text-transform:uppercase; color:var(--gold); margin-top:8px}
.cscrow,.cscgrid{display:grid; grid-template-columns:repeat(auto-fill,minmax(34px,1fr)); gap:6px}
.cscbtn{height:34px; padding:0; border-radius:5px; border:2px solid rgba(255,255,255,.14); cursor:pointer}
.cscbtn:hover{border-color:var(--gold-hi)}
.cscbtn:focus-visible{outline:2px solid var(--focus); outline-offset:2px}
.cscbtn[aria-pressed="true"]{border-color:#fff; box-shadow:0 0 0 2px var(--violet)}
.cscmore{margin-top:12px; align-self:flex-start; font:600 13px/1.2 var(--serif); letter-spacing:0; text-transform:none; color:var(--gold-hi); text-decoration:underline; text-underline-offset:2px; background:none; border:0; padding:2px 0; cursor:pointer}
.cscall{display:flex; flex-direction:column; gap:var(--gap-list)}
.cscall .cstext{margin:10px 0}
.cscall[hidden]{display:none}
.cscfam{display:flex; gap:6px; overflow-x:auto; padding-bottom:2px}
.cscchip{flex:none; border:1px solid var(--line); border-radius:12px; background:var(--panel-2); color:var(--text); padding:3px 11px; font:13px var(--serif); letter-spacing:0; text-transform:none; cursor:pointer}
.cscchip[aria-pressed="true"]{background:var(--violet-deep); border-color:var(--violet-hi); color:var(--text-hi)}
.cscsample{font:13.5px/1.4 var(--mono); letter-spacing:0; text-transform:none; background:#08070d; border-radius:4px; padding:6px 10px}
.cscpreview{display:flex; flex-direction:column; gap:var(--gap-list); margin-top:8px}
.cscpreview[hidden]{display:none}
@media (min-width:760px){.cscfam{flex-wrap:wrap; overflow:visible}}
.cstag .cssw{margin-right:4px}
/* text a trigger coloured (the colour is set on the span itself) */
.trg{font:inherit}
