/* Grand Tour town layer */
.tw-root{position:relative;overflow:hidden;background:#1a1712;touch-action:none;user-select:none;-webkit-user-select:none;cursor:grab}
.tw-root:active{cursor:grabbing}
.tw-world{position:absolute;left:0;top:0;transform-origin:0 0;will-change:transform}
.tw-plate{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;image-rendering:auto}
.tw-hot{position:absolute;left:0;top:0}
/* the polygon is the click area only: no highlight (the pill names the place) */
.tw-hs{fill:rgba(255,215,94,0);stroke:rgba(255,215,94,0);stroke-width:3;cursor:pointer}
.tw-author .tw-hs{stroke:rgba(255,215,94,.6);stroke-dasharray:6 4}
.tw-author .tw-hs:hover{fill:rgba(255,215,94,.14);stroke:rgba(255,215,94,.9)}
.tw-layer{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
/* the sign on a place: always showing, the same size on screen at any zoom
   (counter-scaled by --tw-inv, which paintZoomUi keeps current), a way in.
   It rests FAINT and only comes up clear under the pointer (Lionel,
   2026-09-13: the cards were "so spelled out") — the polygon's own hover adds
   .on, so hovering the building lights its sign too. A long name WRAPS rather
   than laying a banner across the plate: --tw-pill-w is the width it wraps at,
   and the radius is half a single line's height, so one line is still a pill
   and two read as a rounded card. A touch screen has no hover to reveal them
   with, so they rest a little clearer there. */
.tw-hslabel{--tw-pill-dim:.55;--tw-pill-w:206px;
  position:absolute;transform:translate(-50%,-50%) scale(var(--tw-inv,1));transform-origin:50% 50%;display:flex;align-items:center;gap:5px;
  background:rgba(20,16,10,.86);color:#ffd75e;font:700 16px/1.2 "Avenir Next","Segoe UI",system-ui,sans-serif;padding:9px 15px 9px 12px;border-radius:20px;
  border:1.5px solid rgba(255,215,94,.6);box-shadow:0 3px 8px rgba(0,0,0,.5);z-index:5000;pointer-events:auto;cursor:pointer;
  box-sizing:border-box;max-width:var(--tw-pill-w);white-space:normal;text-align:center;overflow-wrap:anywhere;text-wrap:balance;
  opacity:var(--tw-pill-dim);transition:opacity .15s,background .15s,color .15s}
.tw-hslabel i{font-style:normal;font-size:17px;line-height:1;flex:none}
.tw-hslabel:hover,.tw-hslabel.on,.tw-hslabel:focus-visible{opacity:1;background:#ffd75e;color:#221;border-color:#ffd75e}
@media (hover:none){.tw-hslabel{--tw-pill-dim:.74}}
.tw-night .tw-hslabel{background:rgba(12,10,8,.9)}
.tw-night .tw-hslabel:hover,.tw-night .tw-hslabel.on{background:#ffd75e}
/* the 🔇 sign over the concert grand: the hall's own mute for the open grand's
   music, in the hotspot pill's shape. `.off` (muted) wears the ♫ button's
   struck-through look so the two controls read as the same language. */
.tw-concert-mute{padding:8px 11px;gap:0}
.tw-concert-mute i{position:relative;font-size:18px}
.tw-concert-mute.off{color:rgba(255,215,94,.5);border-color:rgba(255,215,94,.35)}
.tw-concert-mute.off i::after{content:"";position:absolute;left:50%;top:50%;width:2px;height:24px;background:#ff8a6b;transform:translate(-50%,-50%) rotate(45deg);border-radius:1px}
.tw-concert-mute.off:hover{color:#221;border-color:#ffd75e}
.tw-concert-mute.off:hover i::after{background:#8a2f18}
.tw-sprite{position:absolute;aspect-ratio:2/3;pointer-events:none}
/* the feet are the pivot: a lean or a plant-squash must not lift the sprite off
   the ground (a plain scaleX(-1) flip reads the same about either origin) */
.tw-sprite img{position:absolute;left:0;top:0;width:100%;height:100%;object-fit:contain;pointer-events:none;transform-origin:50% 100%}
.tw-contact{transition:none}
.tw-shadow{transform-origin:50% 100% !important;filter:brightness(0) blur(1px);opacity:var(--tw-shadow-op,.3)}
.tw-contact{position:absolute;left:22%;right:22%;bottom:-3px;height:10px;border-radius:50%;background:radial-gradient(ellipse at center,rgba(20,14,8,.42),rgba(20,14,8,0) 70%)}
.tw-shade{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:5}
/* the walking line: drawn in screen space (counter-scaled against the camera)
   but parented to the world, so occluders cover it and it stays under sprites */
.tw-fx{position:absolute;left:0;top:0;transform-origin:0 0;pointer-events:none;z-index:6}
.tw-shade .tw-flat{z-index:3}
.tw-shade .tw-flat .tw-body{position:absolute;left:0;top:0;width:100%;height:100%}
.tw-shade .tw-shadow{z-index:5;position:absolute;object-fit:contain;transform-origin:50% 100%}
.tw-shade .tw-propshadow{filter:brightness(0) blur(3px);opacity:var(--tw-propshadow-op,.18)}
.tw-prop{position:absolute;pointer-events:none}
.tw-prop .tw-baseboard{position:absolute;overflow:visible;pointer-events:none}
.tw-prop .tw-depth-slice{position:absolute;background-repeat:no-repeat;pointer-events:none}
.tw-contour-depth > .tw-body,.tw-contour-depth > .tw-baseboard{visibility:hidden}
.tw-author .tw-contour-depth.is-selected > .tw-body{visibility:visible;z-index:3000}
.tw-prop .tw-body{position:absolute;left:0;top:0;width:100%;height:100%;object-fit:contain;pointer-events:none}
/* a prop's ground blob: the walking piano's (above), sized by town.js from the drawn width */
.tw-prop .tw-propcontact{left:auto;right:auto;height:auto;border-radius:50%;background:radial-gradient(ellipse at center,rgba(20,14,8,.55) 0 40%,rgba(20,14,8,.32) 64%,rgba(20,14,8,0) 92%)}
.tw-author .tw-prop{pointer-events:auto;cursor:move}
.tw-author .tw-prop .tw-body{pointer-events:auto}
.tw-occ{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none}
.tw-occluder{position:absolute;background-repeat:no-repeat}
/* Behind a building a character is GONE — an occluder is an opaque cut-out of
   the plate. The x-ray layer holds a silhouette of the people you steer and
   sits above every occluder (whose z-index is 10 + its base y, so the layer is
   parked at plate height + 30, set in town.js) and under the hotspot labels. */
.tw-xray{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;z-index:4000}
.tw-ghost .tw-body{filter:brightness(0) invert(1) drop-shadow(0 0 2px rgba(0,0,0,.9)) drop-shadow(0 0 7px rgba(0,0,0,.5));opacity:var(--tw-ghost-op,.5)}
.tw-ground-ghost{opacity:var(--tw-ghost-op,.5)}
.tw-ground-ghost .tw-body{opacity:1}
.tw-ghost-cover{position:absolute;inset:0;mask-repeat:no-repeat;mask-mode:alpha;pointer-events:none}
.tw-npc,.tw-player{pointer-events:auto;cursor:pointer}
.tw-npc .tw-body,.tw-player .tw-body{pointer-events:auto}
.tw-player:hover .tw-name{opacity:1}
.tw-ring{position:absolute;left:15%;right:15%;bottom:-6px;height:18px;border-radius:50%;border:3px solid rgba(255,215,94,.95);box-shadow:0 0 0 2px rgba(0,0,0,.35),0 0 14px rgba(255,215,94,.6);opacity:0;transform:scale(.6);transition:opacity .15s,transform .15s}
.tw-sprite.sel .tw-ring{opacity:1;transform:scale(1)}
.tw-name{position:absolute;left:50%;top:-16px;transform:translateX(-50%);font:700 11px/1 "Avenir Next","Segoe UI",system-ui,sans-serif;color:#fff;text-shadow:0 1px 2px #000,0 0 4px #000;white-space:nowrap;opacity:0;transition:opacity .15s}
.tw-npc:hover .tw-name,.tw-sprite.sel .tw-name,.tw-root.tw-names .tw-name{opacity:1}
/* the level pill beside a name (vienna.js sets npc.level; Lionel 2026-08-29) */
.tw-lv{display:inline-block;margin-left:4px;padding:1px 4px 1px;border-radius:7px;background:rgba(0,0,0,.6);color:#fff;font:700 9px/1.2 "Avenir Next","Segoe UI",system-ui,sans-serif;font-style:normal;letter-spacing:.2px;text-shadow:none;vertical-align:1px;box-shadow:0 0 0 1px rgba(255,215,94,.45)}
.tw-npc.ghost .tw-lv{box-shadow:0 0 0 1px rgba(127,196,255,.6)}
.tw-npc.shiny .tw-lv{box-shadow:0 0 0 1px #fff07a}
/* who is who: townsfolk yellow, you blue, other players green (Settings → names) */
.tw-npc .tw-name{color:#ffd75e}
.tw-hero .tw-name{color:#7cc4ff}
.tw-player .tw-name{color:#7de08a}
.tw-companion .tw-name{color:#e9c77b}
/* your own piano answers a tap on the piano itself, not on its box: its ground
   blob (42% of the box, 12% below its feet), its ring and its unseen name took
   taps meant for walking, round the owner's feet (2026-09-29) */
.tw-companion.tw-mine .tw-body{pointer-events:auto;cursor:pointer}
/* night without a painted night plate: the plate and its occluder cut-outs dim together */
/* the CSS night for a town with no painted night plate. NO hue-rotate: the
   old 185° swing turned the vineyard's green rows magenta (Lionel,
   2026-08-29, "vineyard road looks like this now"); a plain darken + desaturate
   with a touch of blue keeps every colour what it was, only dimmer */
.tw-night .tw-plate, .tw-night .tw-occluder{filter:brightness(.5) saturate(.6) contrast(1.05) sepia(.12) hue-rotate(-14deg)}
.tw-companion .tw-contact{left:12%;right:12%}
/* the piano sits on a ground blob the size of its footprint (front foot at the
   box bottom, rear feet up to a third of the way up), not a cast silhouette */
.tw-companion.tw-blob .tw-contact{left:2%;right:2%;bottom:-12%;height:42%;background:radial-gradient(ellipse at center,rgba(20,14,8,.55) 0 40%,rgba(20,14,8,.32) 64%,rgba(20,14,8,0) 92%)}
/* The menu on another player: a vertical list above the head (Lionel,
   2026-09-12), counter-scaled like the hotspot pills so it reads at any zoom.
   .tw-below flips it under the feet when the top of the screen would cut it
   off, --tw-mx nudges it sideways to stay on the plate (town.js fitMenu). */
.tw-pills{position:absolute;left:50%;bottom:calc(100% + 24px);transform:translateX(calc(-50% + var(--tw-mx,0px))) scale(var(--tw-inv,1));transform-origin:50% 100%;display:flex;flex-direction:column;align-items:stretch;gap:1px;min-width:168px;padding:5px;border-radius:12px;border:1px solid rgba(255,215,94,.55);background:rgba(20,16,10,.94);box-shadow:0 8px 22px rgba(0,0,0,.55),inset 0 0 0 1px rgba(255,215,94,.12);pointer-events:auto;z-index:6000}
.tw-pills.tw-below{bottom:auto;top:calc(100% + 8px);transform-origin:50% 0}
/* while the menu is open its sprite rises above the hotspot pills (z 5000) and
   the sprites further down the plate: a sprite's own z-index is its depth
   (10 + y, inline, town.js), which the menu inherits as a stacking context */
.tw-player.sel{z-index:5500!important}
.tw-pills button{display:flex;align-items:center;gap:9px;width:100%;margin:0;text-align:left;font:700 12.5px/1.2 "Avenir Next","Segoe UI",system-ui,sans-serif;padding:7px 10px;border:0;border-radius:8px;background:none;color:#ffd75e;cursor:pointer;white-space:nowrap;text-shadow:none;box-shadow:none}
.tw-pills button:hover,.tw-pills button:focus-visible{background:#ffd75e;color:#221;outline:none}
.tw-pills .tw-mi{flex:none;width:1.25em;text-align:center;font-size:13px;line-height:1}
.tw-ui{position:absolute;inset:0;pointer-events:none}
.tw-ui>*{pointer-events:auto}
/* camera controls, bottom right: zoom in / zoom out / whole city / find me */
.tw-ctl{position:absolute;right:14px;bottom:14px;display:flex;flex-direction:column;gap:8px}
.tw-ctl button{width:40px;height:40px;border-radius:50%;border:1px solid rgba(255,255,255,.25);background:rgba(20,16,10,.8);color:#ffd75e;font:400 20px/1 "Avenir Next","Segoe UI",system-ui,sans-serif;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0;transition:background .15s,opacity .15s}
.tw-ctl button:hover{background:rgba(255,215,94,.22)}
.tw-ctl button:disabled{opacity:.32;cursor:default}
.tw-ctl button:disabled:hover{background:rgba(20,16,10,.8)}
.tw-ctl .tw-overview.on{background:#ffd75e;color:#221}
.tw-zin,.tw-zout{font-size:24px}
/* the zone's music (vienna.js): a ♫ under the camera buttons, set a little
   apart because it is not a camera control. Off = dimmed and struck through,
   so the state reads at a glance without a second glyph. */
.tw-musicbox{position:relative;margin-top:6px;display:flex;justify-content:flex-end}
.tw-musicbox[hidden]{display:none}
.tw-ctl .tw-music{position:relative;font-size:21px;flex:none}
.tw-ctl .tw-music.off{color:rgba(255,215,94,.45)}
.tw-ctl .tw-music.off::after{content:"";position:absolute;left:50%;top:50%;width:2px;height:26px;background:#ff8a6b;transform:translate(-50%,-50%) rotate(45deg);border-radius:1px}
/* the 🎹 under the ♫ (vienna.js): does the GAME voice the keys you play? Off
   is dimmed and struck the ♫'s way. It sits BELOW the ♫ because the ♫'s
   slider comes out above its button (.tw-vol, bottom:48px) and would cover
   a button standing there. .tw-ctl button is display:flex, so [hidden]
   needs saying. */
.tw-ctl .tw-piano{position:relative;margin-top:6px;font-size:19px;flex:none}
.tw-ctl .tw-piano[hidden]{display:none}
.tw-ctl .tw-piano.off{opacity:.55;filter:grayscale(.6)}
.tw-ctl .tw-piano.off::after{content:"";position:absolute;left:50%;top:50%;width:2px;height:26px;background:#ff8a6b;transform:translate(-50%,-50%) rotate(45deg);border-radius:1px}
/* the volume beside it (Lionel, 2026-09-12: muting is not enough, you should
   be able to turn the music down). A 40px column has no room for a slider, so
   it slides out to the LEFT of the ♫ on hover or keyboard focus, and on a
   coarse pointer — which has no hover — the ♫ opens it instead and the panel's
   own ♫ does the muting (town.js). It comes out ABOVE the button, never to its
   left: the "now playing" pill sits exactly there (.tw-np, right:62px), and a
   slider laid over the piece being named would read as a bug. The panel keeps
   pointer events only while it is out, or an invisible strip would eat clicks
   on the plate above the column. z-index 25: neither .tw-root nor #vienna-root
   makes a stacking context, so the panel competes in #vienna-screen's own and
   stands OVER the right-hand rail (24) — a phone's tournament card sat on top
   of it — while the ledger, also 25 but later in the markup, still wins. */
.tw-vol{position:absolute;right:0;bottom:48px;z-index:25;height:38px;display:flex;align-items:center;gap:9px;padding:0 12px 0 6px;border-radius:999px;border:1px solid rgba(255,255,255,.25);background:rgba(16,13,8,.96);box-shadow:0 6px 18px rgba(0,0,0,.5);box-sizing:border-box;opacity:0;transform:translateY(6px);transition:opacity .14s,transform .14s;pointer-events:none}
/* the bridge: the panel stands 8px clear of the ♫, and without this the pointer
   crosses bare plate on its way up and the panel folds away under the finger */
.tw-vol::after{content:"";position:absolute;left:0;right:0;top:100%;height:10px}
.tw-musicbox:focus-within .tw-vol,.tw-musicbox.open .tw-vol{opacity:1;transform:none;pointer-events:auto}
@media (hover:hover){.tw-musicbox:hover .tw-vol{opacity:1;transform:none;pointer-events:auto}}
.tw-ctl .tw-volmute{width:30px;height:30px;font-size:15px;border:0;background:none}
.tw-ctl .tw-volmute:hover{background:rgba(255,215,94,.22)}
.tw-voln{color:#ffd75e;font:700 11.5px/1 "Avenir Next","Segoe UI",system-ui,sans-serif;font-variant-numeric:tabular-nums;min-width:30px;text-align:right;user-select:none}
.tw-volr{-webkit-appearance:none;appearance:none;width:104px;height:4px;border-radius:3px;background:rgba(255,215,94,.3);cursor:pointer;margin:0}
.tw-volr::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#ffd75e;border:0;cursor:pointer}
.tw-volr::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:#ffd75e;border:0;cursor:pointer}
.tw-volr:focus-visible{outline:2px solid #ffd75e;outline-offset:4px}
/* what the zone is playing (town.setNowPlaying): a pill beside the ♫ with
   the piece and its composer, the same glass as the buttons. Its width and
   its place are set by town.js layoutNp: beside the button with the room
   the shell reports (never over the chat at bottom left), or ABOVE the
   column (.above, the bottom set inline) when there is no room beside it
   — a phone, or a desktop with the ledger docked. */
.tw-np{position:absolute;right:62px;bottom:14px;height:40px;max-width:420px;display:flex;align-items:center;gap:7px;padding:0 14px;border-radius:999px;border:1px solid rgba(255,255,255,.25);background:rgba(20,16,10,.8);color:#ffd75e;font:500 12.5px/1.2 "Avenir Next","Segoe UI",system-ui,sans-serif;box-sizing:border-box;white-space:nowrap;overflow:hidden;pointer-events:none;user-select:none}
.tw-np[hidden]{display:none}
.tw-np.above{right:14px;max-width:calc(100% - 28px)}
.tw-np i{font-style:normal;font-size:9.5px;letter-spacing:.09em;text-transform:uppercase;color:rgba(255,215,94,.7);flex:none}
.tw-np b{font-weight:700;overflow:hidden;text-overflow:ellipsis;min-width:0}
.tw-np span{color:rgba(255,255,255,.82);flex:none;overflow:hidden;text-overflow:ellipsis;max-width:45%}
/* "Lucas is playing. Listen in?" — the invitation to hear whoever is at the
   concert grand, in the same glass as the readout it stacks on top of. It is
   the ONE place the offer reaches somebody who has folded the hall's card
   away, so it carries its own answer buttons and takes pointer events (the
   now-playing pill takes none). town.js layoutNp lays both from the bottom up. */
.tw-listen{position:absolute;right:62px;bottom:62px;height:40px;max-width:420px;display:flex;align-items:center;gap:9px;padding:0 6px 0 14px;border-radius:999px;border:1px solid rgba(255,215,94,.45);background:rgba(20,16,10,.92);box-shadow:0 6px 18px rgba(0,0,0,.45);color:#ffd75e;font:500 12.5px/1.2 "Avenir Next","Segoe UI",system-ui,sans-serif;box-sizing:border-box;white-space:nowrap;overflow:hidden;user-select:none;animation:tw-listen-in .22s ease-out}
.tw-listen[hidden]{display:none}
.tw-listen.above{right:14px;max-width:calc(100% - 28px)}
.tw-listen b{font-weight:600;overflow:hidden;text-overflow:ellipsis;min-width:0}
.tw-listen button{flex:none;border-radius:999px;border:0;cursor:pointer;font:700 12px/1 "Avenir Next","Segoe UI",system-ui,sans-serif;padding:0 12px;height:28px;display:flex;align-items:center;justify-content:center}
.tw-listen .tw-listen-go{background:#ffd75e;color:#221}
.tw-listen .tw-listen-go:hover{background:#fff0a8}
.tw-listen .tw-listen-x{background:none;color:rgba(255,215,94,.6);font-size:13px;padding:0 8px;margin-left:-4px}
.tw-listen .tw-listen-x:hover{color:#ffd75e;background:rgba(255,215,94,.18)}
.tw-listen button:focus-visible{outline:2px solid #ffd75e;outline-offset:3px}
@keyframes tw-listen-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.tw-listen{animation:none}}
/* the zoom readout above the + button (town.js showZoomLevel): appears when the
   zoom moves, fades 3 s after the last change. It keeps its place in the
   column while hidden so the buttons under it never jump. */
.tw-ctl .tw-zoomlvl{height:24px;min-width:40px;padding:0 5px;border-radius:999px;border:1px solid rgba(255,255,255,.25);background:rgba(20,16,10,.8);color:#ffd75e;font:700 12px/1 "Avenir Next","Segoe UI",system-ui,sans-serif;font-variant-numeric:tabular-nums;display:flex;align-items:center;justify-content:center;box-sizing:border-box;opacity:0;transition:opacity .35s;pointer-events:none;user-select:none}
.tw-ctl .tw-zoomlvl.on{opacity:1;transition:opacity .12s}
/* zoomed right out the town reads as a map, so every building names itself.
   The labels live inside the scaled world, so --tw-inv (set in town.js) takes
   the camera's scale back off them and they stay the same size on screen. */
.tw-far .tw-hslabel{--tw-pill-dim:.8}
.tw-far .tw-hs{fill:rgba(255,215,94,.06);stroke:rgba(255,215,94,.35)}
.tw-toast{position:absolute;left:50%;top:14px;transform:translateX(-50%) translateY(-8px);background:#ffd75e;color:#221;font:700 13px "Avenir Next",system-ui,sans-serif;padding:8px 14px;border-radius:9px;opacity:0;transition:.2s;pointer-events:none}
.tw-toast.on{opacity:1;transform:translateX(-50%) translateY(0)}
/* Talking to somebody. Their portrait sits in the top-left corner and what they
   say runs across the top-centre, big enough to read at a glance while the room
   itself stays visible underneath. The strip covers the viewport but passes
   clicks through everywhere the two panels are not. */
/* the voice of 1830: a Garamond, self-hosted like Barlow (variable, one file
   per style), for everything a townsperson SAYS. The chrome around it -- the
   buttons, the name plate -- stays on the UI sans, so the speech is the only
   thing wearing the old face. */
@font-face{font-family:"EB Garamond";font-style:normal;font-weight:400 800;font-display:swap;src:url(fonts/EBGaramond.woff2) format("woff2")}
@font-face{font-family:"EB Garamond";font-style:italic;font-weight:400 800;font-display:swap;src:url(fonts/EBGaramond-Italic.woff2) format("woff2")}
.tw-dialog{position:absolute;inset:0;pointer-events:none;color:#eee;font-family:"Avenir Next","Segoe UI",system-ui,sans-serif}
.tw-dialog.hidden{display:none}
.tw-dlg-face{position:absolute;left:16px;top:16px;width:132px;height:170px;object-fit:contain;object-position:bottom center;
  background:radial-gradient(120% 100% at 50% 0%,rgba(58,44,28,.94),rgba(24,17,10,.94) 70%);
  border:1px solid rgba(122,86,40,.85);border-radius:14px;box-shadow:0 12px 34px rgba(0,0,0,.55);pointer-events:auto}
/* a painted bust is framed edge to edge; a walking sprite stands on the floor of the frame */
.tw-dlg-face.bust{object-fit:cover;object-position:top center}
/* Everyone in the town speaks through a SHEET OF PARCHMENT (Lionel,
   2026-08-31: the quest interface should read like World of Warcraft's quest
   book, in our own style, without copying it). Design Gallery
   ui/quest-box · quest-parchment-r1, candidate B "road-worn", picked out of
   four Codex panels.
   It is a nine-slice border-image, so the sheet stretches to whatever the
   speech needs and the layout, the measured pager and the word-by-word fade
   are all untouched. `border-image-slice` is in pixels of the 900px-wide
   built panel and is printed by tools/tour_build_questui.py; the border is
   DRAWN wider than the 4px the layout reserves and outset past it, so the
   torn edge hangs over the box's own corners the way a real sheet would,
   and the padding is what actually keeps the text off it. */
.tw-dlg-body{position:absolute;left:50%;top:18px;transform:translateX(-50%);width:min(760px,calc(100% - 340px));
  background:#efdfbe;color:#332414;   /* the paper's own colour, so a panel that fails to load still reads */
  border:4px solid transparent;border-image:url(ui/quest-parchment-worn.webp) 46 fill stretch;
  border-image-width:34px;border-image-outset:14px;padding:16px 44px;
  filter:drop-shadow(0 14px 30px rgba(0,0,0,.55));text-align:left;pointer-events:auto;box-sizing:border-box}
.tw-dlg-name{display:block;color:#6b2f1c;font:900 12.5px/1 inherit;letter-spacing:1.7px;text-transform:uppercase;margin-bottom:7px}
/* Garamond has a small x-height, so it is set a shade bigger and looser than
   the sans it replaced; on parchment it is INK, and 500 keeps it from going
   spidery over the paper's texture */
.tw-dlg-text{font-family:"EB Garamond","Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
  font-size:22.5px;font-weight:500;line-height:1.46;letter-spacing:.2px;margin:0 0 12px;color:#332414}
/* the engraved rule between the story and the task, which is the beat WoW's
   page has and ours did not. Sized by its HEIGHT: the ornament is 4.5:1, so
   `contain` at a text-sized height draws a hairline, not a rule */
.tw-dlg-text::after{content:"";display:block;height:30px;margin:14px auto 2px;
  background:url(ui/quest-divider.webp) center / contain no-repeat;opacity:.9}
/* nothing to separate: a speech with no objective ends at its last word */
.tw-dlg-text:has(+.tw-dlg-obj.hidden)::after{display:none}
/* the whole page of speech at once, one paragraph per authored line */
.tw-dlg-p{display:block}
.tw-dlg-p+.tw-dlg-p{margin-top:9px}
/* the WoW fade: each word carries its own delay (set in town.js), and dropping
   .typing cancels every animation at once, which is what a click to skip does */
.tw-dlg-text.typing .tw-w{opacity:0;animation:tw-word .22s ease-out forwards}
@keyframes tw-word{from{opacity:0;transform:translateY(2px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.tw-dlg-text.typing .tw-w{opacity:1;animation:none}}
/* the task, under the story and legible from the first page */
.tw-dlg-obj{position:relative;margin:0 0 12px;padding:9px 12px;text-align:left;
  border-left:3px solid rgba(138,90,40,.75);background:rgba(120,74,32,.10);
  color:#3d2a15;font-family:"EB Garamond","Iowan Old Style",Palatino,Georgia,serif;font-size:16px;font-weight:500;line-height:1.38}
.tw-dlg-obj.hidden{display:none}
.tw-dlg-obj b{display:block;color:#6b2f1c;font:900 10.5px/1 inherit;letter-spacing:1.6px;text-transform:uppercase;margin-bottom:4px}
/* a quest waiting to be handed in is STAMPED, so the state is a thing on the
   page rather than a change of wording (quests.js objective() sets the kind) */
.tw-dlg-obj[data-kind="ready"]::after{content:"";position:absolute;right:-20px;top:-24px;
  width:64px;height:64px;transform:rotate(9deg);pointer-events:none;
  background:url(ui/quest-seal.webp) center / contain no-repeat;filter:drop-shadow(0 3px 6px rgba(0,0,0,.45))}
.tw-dlg-actions{display:flex;gap:8px;flex-wrap:wrap}
.tw-dlg-actions button{font:700 13.5px inherit;font-family:inherit;padding:8px 14px;border-radius:7px;
  border:1.5px solid rgba(107,74,36,.85);background:rgba(255,251,238,.32);color:#4a3115;cursor:pointer}
.tw-dlg-actions button:hover{background:#6b4a24;color:#f6ecd8}
.tw-dlg-close{position:absolute;right:10px;top:8px;background:transparent;border:0;color:#7a5a34;font-size:15px;line-height:1;cursor:pointer}
.tw-dlg-close:hover{color:#33241a}
/* a short window (a laptop, a docked ledger) sets the speech a size down, so a
   quest still arrives in one page rather than two */
@media (max-height:800px){
  .tw-dlg-text{font-size:20.5px;line-height:1.44}
  .tw-dlg-text::after{height:22px;margin-top:10px}
  .tw-dlg-obj{font-size:15px;padding:8px 11px}
}
/* narrow viewports: the portrait shrinks and the text takes the rest of the
   row. The sheet's torn edge is painted, so the padding cannot fall as far as
   it did on the old flat panel or the words sit on the tear */
@media (max-width:840px){
  .tw-dlg-face{left:10px;top:10px;width:84px;height:112px}
  .tw-dlg-body{left:104px;right:10px;top:12px;transform:none;width:auto;padding:13px 32px}
  .tw-dlg-text{font-size:19px;line-height:1.45}
  .tw-dlg-text::after{height:20px;margin-top:10px}
  .tw-dlg-obj{font-size:15px;padding:7px 10px;margin-bottom:10px}
  .tw-dlg-obj[data-kind="ready"]::after{width:48px;height:48px;right:-12px;top:-18px}
}
/* author overlay */
.tw-maskov{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;image-rendering:pixelated;opacity:.42}
.tw-author .tw-npc{cursor:move}
.tw-author .tw-root{cursor:crosshair}

/* pacing enemies (town.js PATROL): the "!" when one notices you, and the
   Wandering Virtuoso's shine (vienna.js rolls it per luck-day) */
.tw-npc.alert::after{content:"!";position:absolute;left:50%;top:-34px;transform:translateX(-50%);font:900 22px/1 "Avenir Next","Segoe UI",system-ui,sans-serif;color:#fff;background:#e0342d;border:2px solid #fff;border-radius:50%;width:26px;height:26px;text-align:center;line-height:22px;box-shadow:0 2px 6px rgba(0,0,0,.5);animation:tw-alert .5s ease-out}
@keyframes tw-alert{0%{transform:translateX(-50%) scale(.2) translateY(10px);opacity:0}60%{transform:translateX(-50%) scale(1.3)}100%{transform:translateX(-50%) scale(1);opacity:1}}
.tw-npc.shiny .tw-body{animation:tw-shine 1.6s ease-in-out infinite alternate}
@keyframes tw-shine{from{filter:hue-rotate(290deg) saturate(1.5) brightness(1.1) drop-shadow(0 0 4px rgba(255,215,94,.7))}to{filter:hue-rotate(310deg) saturate(1.8) brightness(1.25) drop-shadow(0 0 12px rgba(255,215,94,1))}}
.tw-npc.shiny .tw-name{color:#fff07a;opacity:1;text-shadow:0 0 6px #ffd75e,0 1px 2px #000}
/* the ghosts of the Old Town (vienna.js GHOSTS, at night only): a pale,
   translucent, cold-lit figure that throws no shadow. Until their own
   sprite sets are painted (tools/tour/ghosts-r1.json) they wear a period
   set through this filter, which is the placeholder look Lionel is judging
   in the Design Gallery */
.tw-npc.ghost .tw-body{filter:grayscale(.85) brightness(1.35) contrast(.9) sepia(.3) hue-rotate(170deg) saturate(1.6) drop-shadow(0 0 6px rgba(150,210,255,.85)) drop-shadow(0 0 16px rgba(120,180,255,.5));opacity:.72;animation:tw-haunt 3.2s ease-in-out infinite alternate}
@keyframes tw-haunt{from{opacity:.6}to{opacity:.82}}
/* the painted sets (sprites/ghost-*, round ghosts-r1) are already pale and
   blue: only the glow and the breathing, no re-tint */
.tw-npc.ghost.painted .tw-body{filter:drop-shadow(0 0 5px rgba(150,210,255,.8)) drop-shadow(0 0 14px rgba(120,180,255,.45));opacity:.88;animation:tw-haunt-p 3.2s ease-in-out infinite alternate}
@keyframes tw-haunt-p{from{opacity:.78}to{opacity:.95}}
.tw-npc.ghost .tw-shadow,.tw-npc.ghost .tw-contact{display:none}
.tw-npc.ghost .tw-name{color:#cfe8ff;opacity:1;text-shadow:0 0 8px #7fc4ff,0 1px 2px #000;letter-spacing:.5px}
.tw-npc.ghost .tw-ring{border-color:rgba(150,210,255,.7)}

/* the quest mark over a townsperson (town.js nameTag, vienna.js quests):
   a gold ! for something to offer, a ? for something to hand in */
.tw-npc .tw-q{position:absolute;left:50%;top:-34px;transform:translateX(-50%);font:900 26px/1 Georgia,serif;color:#ffd75e;text-shadow:0 0 6px rgba(0,0,0,.9),0 2px 0 #7a4b00;animation:tw-qbob 1.4s ease-in-out infinite alternate;pointer-events:none}
@keyframes tw-qbob{from{transform:translate(-50%,0)}to{transform:translate(-50%,-6px)}}
