:root{--ink:#302c22;--faint:#746c58;--paper:#f0e8d8;--gold:#a78a55;--red:#813c2c}*{box-sizing:border-box}html,body{margin:0;width:100%;height:100%;overflow:hidden;background:#c7c5b4;color:var(--ink);font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif}button,a,select,input{font:inherit}button{cursor:pointer}button:focus-visible,a:focus-visible{outline:2px solid #8c6140;outline-offset:4px}#world{display:block;width:100%;height:100%;touch-action:none}header{position:absolute;top:28px;left:32px;right:28px;display:flex;justify-content:space-between;align-items:flex-start;pointer-events:none}.brand{display:flex;gap:15px;text-shadow:0 1px 16px #f5ecda90}.seal{font-family:"Noto Serif CJK SC","Songti SC","STSong",serif;display:block;padding:7px 6px 6px;background:#8a3c2ce6;color:#f3e4c9;border:1px solid #e2c5a0;outline:1px solid #8a3c2ca0;font-size:17px;line-height:22px;align-self:flex-start;box-shadow:0 2px 10px #58321c20}.brand h1{font-family:"Songti SC","Noto Serif CJK SC","STSong",serif;letter-spacing:6px;font-weight:500;font-size:36px;margin:7px 0 5px}.overline{font-size:10px;letter-spacing:2.6px;margin:0;font-weight:600}.subtitle{font-size:11px;letter-spacing:4px;margin:0;color:#5e5646}.top-actions{display:flex;align-items:center;gap:10px;pointer-events:auto}.tag{font-size:9px;letter-spacing:1.7px;padding:9px 13px;border:1px solid #766b503d;background:#f0ead753;backdrop-filter:blur(10px)}.icon-btn{background:#eee6d969;border:1px solid #73644842;width:35px;height:35px;font-size:23px;color:#473e2f;backdrop-filter:blur(10px)}.icon-btn:hover{background:#f5ecd9}.panel{position:absolute;right:28px;top:81px;width:250px;padding:21px;background:#efe8dcd9;border:1px solid #fff5;box-shadow:0 10px 40px #211e1720;backdrop-filter:blur(18px);display:none}.panel.open{display:block}.panel h2{font-family:"Songti SC",serif;font-weight:500;font-size:23px;letter-spacing:2px;margin:9px 0 22px}.panel label{font-size:12px;display:flex;align-items:center;justify-content:space-between}.panel select{font-size:11px;width:130px;padding:6px;border:1px solid #aba18b;background:#f6f0e4;color:var(--ink)}.range-label{margin-top:20px;margin-bottom:8px}.range-label output{color:#8a6a3e;font-size:11px}input[type=range]{width:100%;accent-color:#a28755;height:3px;cursor:pointer}.toggles{display:grid;grid-template-columns:1fr 1fr;gap:12px 8px;margin:22px 0}.toggles label{font-size:11px;justify-content:flex-start;gap:4px}input[type=checkbox]{accent-color:#a2854f}.panel-bottom{display:flex;flex-direction:column;gap:12px;border-top:1px solid #b1a89566;padding-top:15px}.panel-bottom button{background:#4a4837;color:#f0e5d0;border:none;padding:8px;font-size:11px;letter-spacing:1px}.panel-bottom a{font-size:11px;color:#695539;text-decoration:none;align-self:center}.muted{font-size:9px;letter-spacing:.3px;color:#7a7264;margin-bottom:0}.scene-caption{position:absolute;left:33px;bottom:147px;pointer-events:none;text-shadow:0 1px 14px #f5ebd9a0}.chapter{display:flex;align-items:center;gap:10px;color:#504632;font-size:10px;letter-spacing:2px}.chapter span:first-child{font-size:16px}.chapter i{height:1px;width:29px;background:#86714d}.scene-caption h2{font-size:26px;letter-spacing:4px;margin:14px 0 10px;font-family:"Noto Serif CJK SC","Songti SC","STSong",serif;font-weight:500}.scene-caption p{font-size:11px;letter-spacing:2px;color:#514a3d;margin:0}.view-nav{position:absolute;bottom:58px;left:50%;transform:translateX(-50%);background:#efeadcdb;backdrop-filter:blur(18px);display:flex;align-items:center;gap:8px;padding:10px 13px;border:1px solid #f9f3e3a0;box-shadow:0 5px 28px #2d291724}.views{display:flex;gap:4px}.views button,.mode{background:transparent;border:1px solid transparent;color:#6f6756;padding:10px 14px;white-space:nowrap;transition:background .2s}.views button{font-size:12px;letter-spacing:1px}.views button small{font-size:9px;color:#999078;margin-right:6px;letter-spacing:0}.views button.active{background:#4d4e3a;color:#f4ead2}.views button.active small{color:#c5b797}.views button:hover:not(.active),.mode:hover{background:#c2b6974d}.mode{font-size:11px;padding:9px 10px}.mode.selected{border-bottom:1px solid #a1834c;color:#5e4525}.nav-separator{height:24px;width:1px;background:#b3a68970;margin:0 6px}footer{position:absolute;bottom:23px;left:33px;right:30px;display:flex;justify-content:space-between;font-size:9px;letter-spacing:1.1px;color:#47493e;text-shadow:0 1px 8px #f0e4cbaa;pointer-events:none}#loading{position:absolute;inset:0;z-index:10;background:#e9e2d3;display:flex;flex-direction:column;align-items:center;justify-content:center;transition:opacity .7s}.load-seal{border:1px solid #9a6553;color:#874735;font-family:"Songti SC",serif;padding:10px;font-size:42px;margin-bottom:24px;box-shadow:inset 0 0 0 3px #e9e2d3,inset 0 0 0 4px #aa8572}#loading h1{font-family:"Songti SC","STSong",serif;letter-spacing:5px;font-weight:500;font-size:27px;margin:20px 0 30px}#load-text{font-size:11px;color:#77715f;letter-spacing:1px}.track{width:270px;height:2px;background:#c7beaa;margin:16px 0}.track i{display:block;width:0;height:100%;background:#88663f;transition:width .25s}#load-value{font-size:10px;letter-spacing:2px;color:#9d8b71}#error{position:absolute;z-index:11;inset:30%;background:#f4ebdb;padding:25px;box-shadow:0 5px 50px #0003}#error p{font-size:13px;line-height:1.8;overflow-wrap:anywhere}#error button{padding:8px 20px}.crosshair{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);color:#fff9;font-size:18px;pointer-events:none}#toast{position:absolute;left:50%;top:22px;transform:translateX(-50%);font-size:12px;padding:9px 18px;background:#3f4439dd;color:#f5eacf;opacity:0;transition:opacity .3s;pointer-events:none}.hide-ui .ui{display:none}
@media(max-width:900px){header{top:19px;left:20px;right:18px}.brand h1{font-size:28px;letter-spacing:4px}.overline{font-size:8px}.tag{display:none}.view-nav{bottom:45px;max-width:calc(100% - 24px);gap:3px;padding:7px;overflow-x:auto}.views button{padding:9px 10px;font-size:11px}.views button small{display:none}.mode{padding:8px 9px}.nav-separator{margin:0 2px}.scene-caption{left:21px;bottom:128px}.scene-caption h2{font-size:22px}.scene-caption p{font-size:10px}footer{bottom:15px;left:20px;right:18px}.instructions{max-width:65%;font-size:8px}#scene-stats{font-size:8px}.panel{right:18px;top:65px}.subtitle{font-size:9px}.chapter{font-size:8px}.seal{font-size:14px;line-height:19px;padding:5px}}@media(max-width:560px){.brand h1{font-size:25px}.brand .overline{letter-spacing:1.2px}.view-nav{width:calc(100% - 24px)}.views button{padding:9px 8px}.mode{font-size:10px;padding:8px}.scene-caption{bottom:115px}.scene-caption h2{font-size:19px}.scene-caption p{max-width:200px;line-height:1.7}.chapter{letter-spacing:1px}#scene-stats{display:none}.instructions{max-width:100%}#loading h1{font-size:21px;letter-spacing:3px}}

/* 1.1 walking affordances */
#walk-status{position:fixed;left:50%;top:105px;transform:translateX(-50%);background:rgba(34,32,26,.73);border:1px solid #bda77b66;color:#eee1c6;padding:9px 18px;border-radius:3px;backdrop-filter:blur(6px);font-size:12px;pointer-events:none;display:flex;gap:22px;}#walk-status[hidden]{display:none;}#door-hint{color:#ecd396}.walk-enter{width:100%;margin:5px 0 3px;padding:10px;background:#7c3327;color:#f5e9d3;border:0;cursor:pointer}#walk-location{max-width:170px}.hide-ui #walk-status{display:none}#tools{max-height:80vh;overflow-y:auto;}

/* Living river inspection panel */
#ecosystem-panel{right:28px;top:116px;width:330px;max-height:calc(100vh - 230px);overflow:auto;padding:18px;z-index:20;background:rgba(244,238,223,.94);border:1px solid rgba(83,71,44,.18);box-shadow:0 15px 45px #2825181a;backdrop-filter:blur(16px)}
#tools{z-index:25;max-height:calc(100vh - 220px);overflow:auto}.eco-heading{display:flex;justify-content:space-between;align-items:center}.eco-heading h2{margin:4px 0 12px}.eco-heading .icon-btn{width:28px;height:28px}.eco-time{display:flex;justify-content:space-between;font-size:12px;color:#736650;border-top:1px solid #b8aa8a80;padding-top:10px}.eco-time strong{font-family:monospace;letter-spacing:.12em;color:#355d59}#life-summary,#follow-status,#life-ledger{font-size:11px;line-height:1.7;color:#655c48;margin:9px 0}#traffic-map{display:block;width:100%;height:auto;border:1px solid #baac8d66;border-radius:3px}.map-legend{font-size:10px;text-align:center;color:#79745e;margin:7px 0 12px}
/* The map key and the ledger, both rebuilt from ecology.json rather than authored in the markup.
   Two columns because the names are short (南岸西市集 is five characters) and the panel is 250px;
   one per line made the key taller than the map it explains. */
#route-legend{margin:0 0 12px}
.legend-group{border-top:1px solid #b9ab8c55;padding:7px 0 3px}
.legend-group:first-child{border-top:0;padding-top:0}
.legend-head{display:flex;align-items:center;gap:6px;margin:0 0 4px;font-size:11px;color:var(--ink)}
.legend-head i{flex:0 0 auto;width:10px;height:2px;display:block}
.legend-head em{margin-left:auto;font-style:normal;font-size:9px;color:var(--faint)}
/* Two columns, because the key measured 374px on one column and pushed 跟随此船 / 自由视角 - the
   panel's primary controls - below the fold. The longest name is 14 characters, about 140px, so
   two 150px columns hold every one of them. */
.legend-names{columns:2;column-gap:10px;padding-left:16px}
.legend-name{margin:0 0 2px;font-size:10px;line-height:1.5;color:#6c6350;break-inside:avoid}
#life-stats{border-top:1px solid #b9ab8c55;margin-top:9px;padding-top:8px}
.stat-group{margin:0 0 8px}
.stat-head{margin:0 0 3px;font-size:9px;letter-spacing:1.7px;color:#8a7f66}
.stat-row{display:flex;justify-content:space-between;gap:10px;font-size:10px;line-height:1.7;color:#6c6350}
.stat-row b{font-weight:500;color:var(--ink)}.eco-actions{display:flex;gap:7px;margin-top:9px}.eco-actions button{flex:1;border:1px solid #aa9b7d55;padding:8px 3px;background:#fff9;color:#63533e;border-radius:3px;cursor:pointer}.eco-actions button:first-child{background:#335f58;color:#f5efda}#dock-status{border-top:1px solid #b9ab8c55;padding-top:8px;margin-top:10px}#dock-status>div{display:flex;justify-content:space-between;font-size:11px;line-height:1.9;color:#685f4d}#traffic-events{font-size:10px;line-height:1.8;color:#807259}#traffic-events time{font-family:monospace;color:#38645c}#ecosystem-panel label{font-size:12px}.hide-ui #ecosystem-panel{display:none!important}@media(max-width:1000px){#ecosystem-panel{width:278px;right:16px;top:100px}.scene-caption{max-width:52%}}@media(max-width:700px){#ecosystem-panel{display:none}#ecosystem-panel.open{display:block;width:270px;top:100px;max-height:calc(100vh - 205px)}.top-actions .tag{display:none}}

#capture-dialog{width:min(1100px,90vw);background:#e7dcc4;border:1px solid #897456;color:#403722}#capture-dialog::backdrop{background:#181813b8}#capture-dialog img{display:block;width:100%;margin-top:14px}#capture-dialog a{margin-left:22px}#verification{position:fixed;z-index:100;left:16px;top:150px;max-height:60vh;max-width:600px;overflow:auto;background:#f3ebd9;padding:15px;font:12px monospace}.view-nav{width:min(1260px,calc(100vw - 40px));max-width:calc(100vw - 40px)}.views{min-width:0;flex:1;flex-wrap:nowrap;overflow-x:auto;justify-content:flex-start}.view-nav .mode,.nav-separator{flex-shrink:0}@media(max-width:600px){.view-nav{flex-wrap:wrap}.views{flex-basis:100%}.nav-separator{display:none}.view-nav .mode{flex:1}.scene-caption{bottom:156px}}#tools{max-height:calc(100vh - 120px);overflow:auto}

#capture-dialog img:not([src]){display:none}

/* English labels keep the scroll layout compact and readable. */
html[lang=en] .brand h1{font-family:Georgia,"Times New Roman",serif;font-size:34px;letter-spacing:.5px}
html[lang=en] .subtitle{letter-spacing:1.1px}
html[lang=en] .scene-caption h2{font-family:Georgia,"Times New Roman",serif;letter-spacing:.5px}
html[lang=en] .scene-caption p{letter-spacing:.3px;line-height:1.5;max-width:620px}
html[lang=en] .views button{padding:10px;font-size:11px;letter-spacing:0}
html[lang=en] .panel{width:330px}
html[lang=en] .panel h2{font-family:Georgia,"Times New Roman",serif;letter-spacing:.4px}
html[lang=en] .panel select{width:200px;max-width:70%}
html[lang=en] label:has(#walk-location){align-items:stretch;flex-direction:column;gap:6px}
html[lang=en] #walk-location{width:100%;max-width:none}
html[lang=en] .toggles{grid-template-columns:1fr}
html[lang=en] #loading h1{font-family:Georgia,"Times New Roman",serif;letter-spacing:1px}
@media(max-width:700px){html[lang=en] .brand h1{font-size:25px}html[lang=en] .panel{width:min(330px,calc(100vw - 36px))}}
/* Point-and-look inspection card. Anchored bottom-left so it clears the header, the scene
   caption and the centred view bar; the overline + h2 pattern is shared with the traffic panel. */
#inspect-panel{left:28px;right:auto;top:auto;bottom:250px;width:290px;max-height:min(46vh,410px);overflow:auto;padding:16px 18px;z-index:21;background:rgba(244,238,223,.94);border:1px solid rgba(83,71,44,.18);box-shadow:0 15px 45px #2825181a;backdrop-filter:blur(16px)}
#inspect-panel .eco-heading h2{margin:3px 0 10px;font-size:19px;letter-spacing:.3px}
#inspect-body{margin-top:2px}
.inspect-row{display:flex;justify-content:space-between;gap:14px;padding:5px 0;border-top:1px solid #b1a89555;font-size:11px;line-height:1.45}
.inspect-row:first-child{border-top:0}
.inspect-row span{color:var(--faint);white-space:nowrap}
.inspect-row b{font-weight:500;text-align:right;color:var(--ink);overflow-wrap:anywhere}
@media(max-width:900px){#inspect-panel{left:20px;bottom:244px;width:min(290px,calc(100vw - 40px))}}

/* Language toggle. Shares .icon-btn's geometry but sets its own font: the CJK face is much wider
   than a glyph icon, and a serif face at icon size renders 中 as a full-bleed block. */
#lang-btn{font-family:Georgia,"Times New Roman",serif;font-size:14px;letter-spacing:.5px;padding:0;line-height:1}
html[lang=zh] #lang-btn{font-family:"Songti SC","Noto Serif CJK SC",serif;font-size:16px}
/* The voice switch is the first .icon-btn that holds state rather than opening a panel, so there
   was no existing on-style to copy: it takes the same palette the active view button uses. Hidden
   until the OS has actually delivered its voice list - an empty list and a list with nothing for
   the current language are different facts, and only the second one means the button is useless. */
.icon-btn.active{background:#4d4e3a;color:#f4ead2}
#voice-btn{font-size:17px;line-height:1;padding:0}
#voice-btn[hidden],.voice-row[hidden],#voice-audition[hidden]{display:none}
.audition{background:none;border:1px solid #a89b7c;color:#6a5c3e;font-size:10px;letter-spacing:1px;padding:5px 9px;margin-top:8px}
.amb-row{font-size:11px;justify-content:flex-start;gap:4px;margin:18px 0 2px}
html[lang=zh] .amb-row{font-size:12px}
/* Filled from JS, never by applyStatic - see app.js. No data-i18n, because a language toggle would
   overwrite the district name with the chrome label and only a hook could put it back. */
.amb-scene{margin:9px 0 0;font-size:10px;letter-spacing:1px;color:#8a6a3e;min-height:13px}
.amb-scene:empty{display:none}
/* CJK needs far less tracking than the Latin display type above, or the letters stop forming words. */
html[lang=zh] .overline,html[lang=zh] .chapter span,html[lang=zh] .tag{letter-spacing:.8px}
html[lang=zh] .brand h1{font-size:33px;letter-spacing:5px}
html[lang=zh] .subtitle{letter-spacing:3px}
html[lang=zh] .scene-caption h2{font-size:24px;letter-spacing:3px}
html[lang=zh] .scene-caption p{letter-spacing:1.2px;line-height:1.7;max-width:520px}
html[lang=zh] .views button{font-size:13px;letter-spacing:1.5px;padding:10px 12px}
html[lang=zh] .mode{letter-spacing:1.5px}
html[lang=zh] .panel h2{letter-spacing:3px}
html[lang=zh] #instructions,html[lang=zh] #scene-stats{letter-spacing:.4px}
html[lang=zh] .inspect-row{font-size:12px}
html[lang=zh] .inspect-row span{letter-spacing:1px}
/* The 1.1px/low-tracking tuning above assumes Latin; Chinese panels are denser so give them back
   the same room the English-only build had. */
html[lang=zh] .panel label{font-size:13px}
/* A four-character caption plus a 150px select does not fit the English-width panel, and the
   caption used to break mid-word. Pin the caption to one line and let the select take the rest. */
html[lang=zh] .panel label>span{white-space:nowrap;flex:1 1 auto}
html[lang=zh] .panel select{font-size:12px;width:148px;min-width:0;flex:0 0 auto}
html[lang=zh] .toggles{grid-template-columns:1fr 1fr}
html[lang=zh] .toggles label{font-size:12px}
html[lang=zh] .eco-time,html[lang=zh] #life-summary,html[lang=zh] #follow-status,html[lang=zh] #life-ledger{font-size:12px}
html[lang=zh] #dock-status>div{font-size:12px;line-height:2}
html[lang=zh] #traffic-events{font-size:11px}
html[lang=zh] .map-legend{font-size:11px}
@media(max-width:700px){html[lang=zh] .brand h1{font-size:26px}html[lang=zh] .views button{font-size:12px;padding:9px 10px}html[lang=zh] .scene-caption h2{font-size:21px;letter-spacing:2px}html[lang=zh] .subtitle{letter-spacing:1.5px}}
/* Generated character portrait. A sibling of #inspect-body, never a child: show() rebuilds that
   node wholesale every 350ms, so anything nested inside it would be destroyed three times a
   second. The [hidden] override is required for the same reason as #walk-status - the element's
   own display would otherwise beat the user-agent [hidden] rule. */
#inspect-ai[hidden]{display:none}
#inspect-ai{margin-top:16px;padding-top:14px;border-top:1px solid #b1a89555}
#inspect-ai .overline{display:flex;align-items:center;gap:9px;margin:0 0 10px;color:var(--faint);font-size:9px;letter-spacing:2.6px;font-weight:600;white-space:nowrap}
#inspect-ai .overline::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,#b1a89566,transparent)}
/* The prose is the scroll's own voice, so it takes the inscription face rather than the UI sans;
   Latin gets Georgia, the same pairing the brand and scene caption use. */
#ai-desc{margin:0 0 11px;font-family:Georgia,"Times New Roman",serif;font-size:13px;line-height:1.8;color:#2b271e}
#ai-line{margin:0 0 12px;padding:9px 12px;border-left:3px double var(--gold);background:#a78a5512;
 font-family:Georgia,"Times New Roman",serif;font-size:13px;line-height:1.75;color:#3d3423}
#ai-line[hidden]{display:none}
#ai-make{width:100%;margin:2px 0 10px;padding:9px;background:#4a4837;color:#f0e5d0;border:none;font-size:12px;
 letter-spacing:2px;cursor:pointer;transition:background .18s}
#ai-make:hover{background:#5b5844}
#ai-make[hidden]{display:none}
#ai-foot{display:flex;align-items:center;justify-content:space-between;gap:12px}
#ai-note{margin:0;font-size:9px;line-height:1.5;flex:1 1 auto}
#ai-again{flex:0 0 auto;padding:6px 13px;font-size:11px;background:none;border:1px solid #b1a89580;
 color:var(--ink);cursor:pointer;transition:background .18s,border-color .18s}
#ai-again:hover{background:#a78a5518;border-color:var(--gold)}
#ai-again[hidden]{display:none}
#ai-cancel{flex:0 0 auto;padding:6px 2px;background:none;border:none;color:var(--faint);font-size:10px;cursor:pointer}
#ai-cancel:hover{color:var(--red)}
#ai-cancel[hidden]{display:none}
html[lang=zh] #ai-desc,html[lang=zh] #ai-line{font-family:"Songti SC","Noto Serif CJK SC","STSong",serif;font-size:14px;line-height:2.05}
html[lang=zh] #ai-desc{text-align:justify;text-justify:inter-ideograph}
/* The cursor readout. z-index 26 because #tools is 25 and this is repositioned under the cursor,
   which can cross any of the panels. The [hidden] override is required for the same reason as
   #walk-status and #inspect-ai: the element's own display would otherwise beat the user-agent
   [hidden] rule and it would never go away. Warm paper rather than #toast's dark or
   #walk-status's HUD grey - it carries the same values as the card and should read as part of
   it, not as a system message. */
#hover-readout{position:absolute;z-index:26;max-width:280px;padding:7px 11px;pointer-events:none;
 background:rgba(244,238,223,.94);border:1px solid rgba(83,71,44,.18);box-shadow:0 6px 20px #2825181a;
 backdrop-filter:blur(10px);font-size:11px;line-height:1.5;color:var(--ink);white-space:nowrap}
#hover-readout[hidden]{display:none}
html[lang=zh] #hover-readout{font-size:12px;letter-spacing:.6px}
/* Sibling of #inspect-body, not a child: show() rebuilds that node wholesale every 350ms, so a
   button inside it would be destroyed three times a second. The [hidden] override is the same one
   #walk-status and #inspect-ai need - the element's own display beats the user-agent rule. */
#inspect-follow{margin-top:12px}
#inspect-follow[hidden]{display:none}
#follow-person{width:100%;padding:9px;border:1px solid #aa9b7d55;background:#fff9;color:#63533e;border-radius:3px;cursor:pointer;font-size:12px}
#follow-person:hover{background:#f0e8d8}
#inspect-follow.on #follow-person{background:#335f58;color:#f5efda;border-color:#335f88}
