:root {
  --bg:#0e1411; --panel:#151c18; --line:#2b362e; --text:#edf0e7;
  --muted:#a0ada2; --accent:#d0dcab; --accent-bg:#293525; --radius:10px;
  font:14px/1.5 var(--fsb-font);
}
*{box-sizing:border-box}
html,body{margin:0;width:100%;height:100%;overflow:hidden}
body{height:100dvh;display:grid;grid-template-rows:minmax(0,1fr);background:var(--bg);color:var(--text)}
button,input,select{font:inherit}
button,a,input,select,summary{-webkit-tap-highlight-color:transparent}
/* Allow menu scrolling, but never turn ordinary player touches into zoom. */
html,body{touch-action:pan-x pan-y}
button,summary,a{touch-action:pan-x pan-y}
#stage,#stage *,#touch,#touch *{touch-action:none;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none;-webkit-user-drag:none}
.touch-input body,.touch-input body *{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}
button{cursor:pointer;color:inherit;border:1px solid var(--line);border-radius:var(--radius);background:transparent;min-height:44px}
button:disabled{opacity:.4;cursor:default}
button:not(:disabled):hover{background:var(--fsb-hover)}
button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,summary:focus-visible,[tabindex="0"]:focus-visible{outline:2px solid var(--accent);outline-offset:3px}
button.primary{background:var(--accent);color:var(--fsb-accent-text);border:0;font-weight:700;min-height:58px;font-size:19px}
button.primary:not(:disabled):hover{background:#dee8c0}
[hidden]{display:none!important}
.sr-only{position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}
.workspace{--panel-width:360px;display:grid;grid-template-columns:minmax(0,1fr) var(--panel-width);min-height:0;overflow:hidden;transition:grid-template-columns 260ms cubic-bezier(.2,0,0,1)}
#stage{position:relative;display:grid;grid-template-rows:minmax(0,1fr);min-width:0;min-height:0;overflow:hidden;background:#000}
/* The slot cards are built by script. A paint that lands before them puts the
   import box and the hints at the top of the pane, and building the list then
   shoves them down the screen. Hidden content is not laid out against, so the
   pane appears once, already complete. */
#saves-pane:not([data-ready]) .saves-scroll{visibility:hidden}
/* player-settings sizes this to the exact pixel, but the shape belongs here:
   until the script ran the box had no size, the stage collapsed, and the
   whole panel jumped down the moment it was measured. */
#viewport{min-width:0;min-height:0;display:flex;align-items:center;justify-content:center;
  width:100%;height:auto;aspect-ratio:var(--screen-aspect,4/3);max-width:100%;max-height:100%;place-self:center}
canvas{display:block;width:100%;height:100%;object-fit:contain;background:#000;outline:none}
/* The title card. It is the first thing anyone sees, and until a session
   exists there is nothing behind it but black. */
#stage-title{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);display:flex;flex-direction:column;align-items:center;gap:0;max-width:min(420px,86%);padding:0 16px;text-align:center}
#stage-title img{image-rendering:pixelated;width:72px;height:72px;margin-bottom:18px;filter:drop-shadow(0 8px 24px #0009)}
#stage-title h2{margin:0;font-size:27px;font-weight:750;letter-spacing:-.04em;color:#eef2e6;text-shadow:0 2px 18px #000b}
#stage-title p{margin:7px 0 26px;font-size:10px;letter-spacing:.19em;color:#9fae98}
#stage-start{width:230px;min-height:64px;font-size:21px;box-shadow:0 10px 34px #0008}
#stage-title:has(#stage-start[hidden]){display:none}
#stage-status{position:absolute;left:50%;bottom:7%;transform:translateX(-50%);display:flex;align-items:center;gap:8px;max-width:86%;padding:7px 14px;border-radius:20px;background:#0b1110cc;color:#ebeee5;font-size:12px;text-align:center;pointer-events:none;backdrop-filter:blur(3px)}
#stage-status:has(#status:empty){display:none}
#status{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
#speed{color:var(--accent);font-size:11px;flex:none}
#speed:empty{display:none}

/* The panel has one content area; secondary tools never squeeze the save list. */
#panel{position:relative;display:flex;flex-direction:column;min-width:0;min-height:0;overflow:hidden;border-left:1px solid var(--line);background:var(--panel);color:var(--text)}
#panel svg{width:18px;height:18px;flex:none;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round;pointer-events:none}
.panel-heading{position:relative;flex:none;padding:24px 20px 16px}
#collapse-panel[data-tip]::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);right:0;z-index:4;padding:5px 10px;border-radius:7px;background:#0b1110f7;border:1px solid var(--line);color:var(--text);font-size:13px;line-height:1.45;white-space:nowrap;pointer-events:none;opacity:0}
@media(hover:hover){#collapse-panel[data-tip]:hover::after{opacity:1}}
#collapse-panel[data-tip]:focus-visible::after{opacity:1}
.brand{display:flex;align-items:center;gap:12px;min-width:0;margin-bottom:20px}
.brand img{display:block;image-rendering:pixelated;flex:none;width:32px;height:32px}
.brand h1{margin:0;font-size:17px;line-height:1.4;font-weight:700;letter-spacing:-.035em}
.brand p{margin:3px 0 0;color:var(--muted);font-size:9px;letter-spacing:.13em;line-height:1.3}
.session-actions{display:flex;gap:8px}
#panel-fullscreen{display:flex;align-items:center;justify-content:center;gap:7px;min-width:0;flex:1;min-height:44px;padding:0 10px;font-size:12px;color:var(--muted)}
#panel-fullscreen[aria-pressed=true]{color:var(--accent);border-color:#607347}
#panel-fullscreen svg{width:16px;height:16px}
#start{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;width:auto;min-height:44px;font-size:12px;font-weight:600;background:#1d2720;color:#c0cfb9;border-color:#354431}
#start:not(:disabled):hover{background:#2b3828;border-color:#82956a}
#start svg{width:15px;height:15px}
/* One height, one radius, one icon size across the row: the four controls were
   46, 32, 64 and 153 wide at three different heights, which read as four
   unrelated things rather than one bar. */
.session-controls{display:flex;align-items:center;gap:6px;margin-top:10px}
.session-controls button{min-height:38px;border-radius:9px}
#panel .session-controls button svg{width:17px;height:17px}
/* This row is icons and a slider, so each says what it is on hover. Opening
   upward keeps them clear of the rate menu, which opens down. */
/* content:attr() would replace a real element's children, so only the pseudo
   tooltip takes it; the markup one shares the look and nothing else. */
.session-controls [data-tip]::after{content:attr(data-tip)}
.session-controls [data-tip]::after,.session-controls .row-tip{position:absolute;bottom:calc(100% + 6px);left:50%;transform:translateX(-50%);z-index:4;padding:5px 10px;border-radius:7px;background:#0b1110f7;border:1px solid var(--line);color:var(--text);font-size:13px;line-height:1.45;white-space:nowrap;pointer-events:none;opacity:0}
.session-controls>[data-tip]{position:relative}
#rewind[data-tip]::after{left:0;transform:none}
.session-controls [data-tip]:focus-visible::after{opacity:1}
@media(hover:hover){
  .session-controls>[data-tip]:not(:disabled):hover::after{opacity:1}
  /* The wrapper carries the slider's label, so only the slider raises it. */
  /* Not a direct child of the row: the trigger sits inside its menu. */
  .rate-menu:has(.rate-trigger:not(:disabled):hover) .row-tip{opacity:1}
  .volume-control[data-tip]:has(#volume:hover)::after{opacity:1}
  #toggle-mute[data-tip]:hover::after{opacity:1}
  .volume-control:has(#toggle-mute:hover)::after{opacity:0}
}
#toggle-pause,#rewind{display:grid;place-items:center;width:38px;flex:none;padding:0;color:var(--muted)}
/* Playback rate. One control that says the rate it is at, opening its choices
   on hover, so the row keeps its width and the feature stops being invisible. */
.rate-menu{position:relative;flex:none}
.rate-menu:has(.rate-trigger:focus-visible) .row-tip{opacity:1}
/* The widest label in the row, and the panel clips: anchor it to the menu's
   left edge rather than centring it off the side. */
.session-controls .row-tip{left:0;transform:none}
/* 4x is a character shorter than 16x; without a floor the button narrows and
   the volume beside it slides over. Tabular figures make the floor exact. */
#speed-label{min-width:3ch;text-align:center}
.session-controls .row-tip kbd{font-size:12px;padding:1px 5px}
.session-controls .row-tip b{color:var(--accent);font-weight:600}
.rate-trigger{display:flex;align-items:center;justify-content:center;gap:5px;padding:0 10px 0 8px;color:var(--muted);font-size:12px;font-variant-numeric:tabular-nums}
#panel .rate-trigger svg{width:14px;height:14px}
/* Lit while the hold is actually running, so pressing ~ shows up here and the
   key and the control point at each other. */
.session-controls .rate-trigger.held{color:var(--accent);border-color:#607347;background:#273322}
.rate-list{position:absolute;top:calc(100% + 4px);left:0;z-index:3;display:none;flex-direction:column;gap:2px;min-width:100%;padding:4px;border:1px solid var(--line);border-radius:10px;background:#0e1511;box-shadow:0 10px 24px #0008}
/* The gap between the button and the list is outside the menu's own box, so a
   pointer crossing it would leave :hover and shut the list on the way down.
   This spans it without filling it in. */
.rate-list::before{content:'';position:absolute;left:0;right:0;bottom:100%;height:6px}
.rate-menu:hover .rate-list,.rate-menu:focus-within .rate-list{display:flex}
.rate-menu:has(.rate-trigger:disabled) .rate-list,.rate-menu[data-dismissed] .rate-list{display:none}
.rate{min-height:36px;padding:0 12px;border:0;border-radius:7px;color:var(--muted);font-size:12px;white-space:nowrap;text-align:left}
.rate[aria-checked=true]{color:var(--accent);background:#293427}
#toggle-pause[aria-pressed=true]{color:var(--accent);border-color:#607347}
#toggle-pause #resume-icon{margin-left:2px} /* The triangle reads off-centre in a square. */
.volume-control{position:relative;display:flex;align-items:center;gap:2px;flex:1;min-width:0;height:38px;padding-right:10px;border:1px solid var(--line);border-radius:9px}
#toggle-mute{position:relative;display:grid;place-items:center;width:36px;min-height:36px;flex:none;padding:0;border:0;color:var(--muted)}
#toggle-mute[aria-pressed=true]{color:var(--accent)}
#volume{flex:1;min-width:0;height:36px;margin:0;accent-color:var(--accent);background:transparent}
.volume-slider{display:flex;flex:1;min-width:0}
.volume-heading{display:none}
.touch .session-controls{position:relative;gap:4px}
.touch button[data-speed="8"]{display:none}
.touch .volume-control{position:static;flex:none;width:40px;height:40px;margin-left:auto;padding:0}
.touch #toggle-mute{width:38px;min-height:38px}
.touch #toggle-mute[aria-expanded=true]{color:var(--accent);background:#293427}
.touch .volume-slider{display:none;position:absolute;left:0;top:calc(100% + 8px);z-index:10;width:min(320px,calc(100vw - 40px));padding:14px 16px;border:1px solid var(--line);border-radius:12px;background:#18211a;box-shadow:0 12px 28px #0008}
.touch .volume-control.is-open .volume-slider{display:flex;flex-direction:column;gap:8px}
.touch .volume-heading{display:flex;justify-content:space-between;align-items:center;font-size:12px;color:var(--text)}
.touch .volume-heading output{font-variant-numeric:tabular-nums;color:var(--accent)}
.touch #volume{flex:none;width:100%;height:44px}

#load-progress{position:absolute;bottom:0;left:20px;width:calc(100% - 40px);height:2px;border:0;background:transparent;appearance:none}
#load-progress::-webkit-progress-bar{background:transparent}
#load-progress::-webkit-progress-value{background:var(--accent)}
#load-progress::-moz-progress-bar{background:var(--accent)}
.tabs{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:4px;flex:none;margin:0 20px 18px;padding:4px;border:1px solid var(--line);border-radius:12px;background:#0e1511}
.tab{display:flex;align-items:center;justify-content:center;gap:6px;padding:0 5px;min-height:40px;font-size:12px;font-weight:600;color:var(--muted);border:0;border-radius:8px;white-space:nowrap}
#panel .tab svg{display:none;width:16px;height:16px}
.tab[aria-selected=true]{color:var(--accent);background:#293427}
.panel-body{position:relative;flex:1;min-height:0;min-width:0}
.tab-panel{position:absolute;inset:0;display:flex;flex-direction:column;min-width:0;min-height:0}
.scroll-list{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable;scrollbar-width:thin;scrollbar-color:#445044 transparent;padding:0 12px 16px 16px}
.scroll-list h2{position:sticky;top:0;z-index:1;margin:0;padding:18px 8px 9px;background:var(--panel);color:var(--accent);font-size:13px;font-weight:650;letter-spacing:.015em}
/* A scene is the same card as a save slot: one surface, pressed as a whole,
   with its own buttons riding on top of the press. */
#checkpoint-list .save-row{position:relative;display:flex;align-items:center;gap:8px;margin:0 0 8px;padding:8px;border:1px solid var(--line);border-radius:12px;background:var(--fsb-surface);transition:background 120ms ease,border-color 120ms ease}
#checkpoint-list .save-row:has(.checkpoint[aria-pressed=true]){background:#273423;border-color:#607347}
.save-row.dragover{background:var(--accent-bg);border-color:var(--accent)}
.checkpoint,.slot-load{width:100%;min-width:0;min-height:66px;height:100%;padding:10px 8px;text-align:left;border:0;border-radius:8px}
.checkpoint{display:grid;grid-template-columns:26px minmax(0,1fr);align-items:center;gap:9px}
#checkpoint-list .checkpoint{position:static;flex:1;width:auto;min-height:34px;height:auto;padding:2px;background:none}
#checkpoint-list .checkpoint::before{content:'';position:absolute;inset:0}
#checkpoint-list .checkpoint:focus-visible{outline:none}
#checkpoint-list .save-row:has(.checkpoint:focus-visible){outline:2px solid var(--accent);outline-offset:3px}
@media(hover:hover){#checkpoint-list .save-row:has(.checkpoint:not([aria-pressed=true]):hover){background:var(--accent-bg);border-color:#44553a}}
#checkpoint-list .save-row:has(.checkpoint:active){background:var(--fsb-hover)}
.checkpoint .number{display:grid;place-items:center;min-height:26px;border-radius:7px;background:#202a22;color:#b5c49e;font-size:11px;font-variant-numeric:tabular-nums}
.checkpoint[aria-pressed=true] .number{background:var(--accent);color:#273320}
.scene-copy{min-width:0}
.row-title{display:block;color:var(--text);font-size:12.5px;line-height:1.5;font-weight:550;word-break:keep-all;overflow-wrap:anywhere}
#checkpoint-list .row-title{font-size:13px;line-height:18px;font-weight:650}
.save-row small{display:block;margin-top:3px;font-size:10.5px;color:var(--muted);line-height:1.4;word-break:keep-all}
#checkpoint-list .save-row small{margin-top:0;font-size:12px;line-height:16px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.checkpoint[aria-pressed=true] small::after{content:' · 현재 장면';color:var(--accent)}
.download-button{position:relative;display:grid;place-items:center;width:44px;min-height:44px;border:0;color:#94a18f}
/* These rows carry icons only, so each says what it does on hover. The list
   scrolls, so the label opens to the left, inside the row, where the scroll
   box cannot clip it. Kept out of the way of a disabled button, and of touch,
   where a tap would leave the label stuck under the finger. */
.download-button[data-tip]::after{content:attr(data-tip);position:absolute;right:calc(100% + 2px);top:50%;transform:translateY(-50%);z-index:2;padding:5px 10px;border-radius:7px;background:#0b1110f2;border:1px solid var(--line);color:var(--text);font-size:13px;line-height:1.45;white-space:nowrap;pointer-events:none;opacity:0}
.download-button[data-tip]:focus-visible::after{opacity:1}
@media(hover:hover){.download-button[data-tip]:not(:disabled):hover::after{opacity:1}}
#panel .download-button svg{width:16px;height:16px}
.empty-slot{opacity:.65}
.empty-slot .slot-load{opacity:1}
.list-placeholder{padding:40px 24px;text-align:center;color:var(--text);font-size:13px}
.list-footnote,.panel-note{flex:none;margin:0;padding:12px 20px;border-top:1px solid var(--line);color:var(--muted);font-size:10px;line-height:1.6}
.pane-intro{padding:0 20px 18px;flex:none}
/* A second heading in the same pane needs the air the first one gets from the
   pane's own top padding. */
.resource-links+.pane-intro{padding-top:26px}
.pane-intro h2{margin:0;font-size:18px;font-weight:650;letter-spacing:-.03em}
.pane-intro p{margin:6px 0 0;font-size:12px;color:var(--muted)}
.import-box{margin:0 20px 12px;padding:14px 12px;border:1px dashed #43503d;border-radius:10px;background:#1a231c;flex:none}
.import-box.dragover{background:var(--accent-bg);border-color:var(--accent)}
button.secondary{display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:8px 12px;background:#2b3827;border-color:#44553a;color:var(--accent);font-size:13px;font-weight:600}
.import-box p{margin:9px 0 0;font-size:11px;text-align:center;color:var(--muted)}
/* Which slot goes where, one row each, with the labels on a shared column so
   the two menus line up and read as a pair. */
.cloud-import-field{display:grid;grid-template-columns:44px minmax(0,1fr);align-items:center;gap:8px;margin-bottom:8px}
.cloud-import-field label{color:var(--muted);font-size:11px}
.cloud-import-field select{min-width:0;min-height:36px;padding:6px 8px;border:1px solid var(--line);border-radius:8px;background:var(--bg);color:var(--text);font-size:12px;text-overflow:ellipsis}
.panel-hint{display:flex;align-items:center;justify-content:center;gap:6px;flex:none;padding:0 20px 10px;color:var(--muted);font-size:11px}
kbd{font:inherit;font-size:11px;background:#263028;border:1px solid #3d493d;border-radius:4px;padding:1px 4px;color:#cbd5c5}
.saves-scroll{flex:1;min-height:0;overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable;scrollbar-width:thin;scrollbar-color:#445044 transparent}
.saves-scroll .scroll-list{overflow:visible;padding-right:16px}
.settings-scroll{overflow:auto;min-height:0;overscroll-behavior:contain;scrollbar-gutter:stable;scrollbar-width:thin;scrollbar-color:#445044 transparent;padding-bottom:16px}
.panel-actions{margin:0 20px 22px;border-top:1px solid var(--line)}
.setting-row{position:relative;display:flex;align-items:center;justify-content:space-between;gap:10px;min-height:84px;padding:16px 0;border-bottom:1px solid var(--line);font-size:13px}
.setting-row small{display:block;margin-top:5px;color:var(--muted);font-size:10px;line-height:1.5}
.choice{display:flex;flex:none;gap:4px;padding:4px;border:1px solid var(--line);border-radius:10px;background:#0e1511}
.choice button{min-height:34px;padding:0 10px;border:0;border-radius:7px;color:var(--muted);font-size:12px;font-weight:600;white-space:nowrap}
.choice button[aria-checked=true]{color:var(--accent);background:#293427}
#resolution-label{flex:none;align-self:flex-start;white-space:nowrap}
#resolution{display:grid;grid-template-columns:minmax(0,1fr);min-width:0;width:auto;margin-left:auto}
#resolution button{display:flex;align-items:center;justify-content:center;gap:8px;min-width:0;min-height:42px;padding:8px 12px}
#resolution button small{margin:0}
/* Locked out mid-battle or mid-event: the row says so on hover rather than
   letting the press happen and reporting a refusal afterwards. Carried by the
   group, because a disabled button is not a reliable hover target. */
.choice[data-locked=true]{position:relative;cursor:default}
.choice[data-locked=true]::after{content:attr(data-tip);position:absolute;top:calc(100% + 6px);right:0;z-index:4;padding:5px 10px;border-radius:7px;background:#0b1110f7;border:1px solid var(--line);color:var(--text);font-size:13px;line-height:1.5;pointer-events:none;opacity:0;width:max-content;max-width:min(280px,calc(100vw - 60px));text-align:left;white-space:normal}
@media(hover:hover){.choice[data-locked=true]:hover::after{opacity:1}}
.switch-label{cursor:pointer}
.switch-label input{position:absolute;inset:0;width:100%;height:100%;opacity:0;margin:0;cursor:pointer;z-index:1}
.switch-label:has(input:disabled){opacity:.4;cursor:default}
.switch-label input:disabled{cursor:default}
.switch-track{position:relative;flex:none;width:38px;height:23px;border-radius:20px;background:#3a463d;border:1px solid #4d5b51}
.switch-track:after{content:'';position:absolute;width:15px;height:15px;left:3px;top:3px;border-radius:50%;background:#d7dfd5;transition:transform 140ms cubic-bezier(.2,0,0,1)}
.switch-label input:checked~.switch-track{background:var(--accent);border-color:var(--accent)}
.switch-label input:checked~.switch-track:after{transform:translateX(15px);background:#25301e}
.switch-label input:focus-visible~.switch-track{outline:2px solid var(--accent);outline-offset:3px}
.help-section{margin:0 20px 24px;border:1px solid var(--line);border-radius:10px}
.help-section summary{display:flex;align-items:center;justify-content:space-between;min-height:48px;padding:12px 14px;font-size:13px;font-weight:600;cursor:pointer;list-style:none}
.help-section summary::-webkit-details-marker{display:none}
.help-section[open] summary svg{transform:rotate(180deg)}
.key-list{margin:0;padding:0 14px 12px}
.key-list div{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:8px 0;font-size:11px}
.key-list dt{color:var(--muted)}
.key-list dd{margin:0;text-align:right}
.touch-tip{display:none;margin:0;padding:0 14px 14px;font-size:11px;color:var(--muted)}
.resource-links{padding:0 20px}
.resource-links a,.resource-links button{display:flex;align-items:center;justify-content:space-between;width:100%;min-height:54px;gap:12px;padding:10px 0;border:0;border-bottom:1px solid var(--line);border-radius:0;background:transparent;font-size:12px;font-weight:400;text-align:left;color:var(--text);text-decoration:none}
.resource-links a:hover,.resource-links button:not(:disabled):hover{color:var(--accent);background:transparent}
.resource-links a>span:last-child,.resource-links button>span:last-child{color:var(--muted);font-size:17px}
.resource-links small{display:block;margin-top:3px;font-size:10px;color:var(--muted)}
.panel-footer{display:none}

/* 원작 치트 dialog. Sized to the viewport rather than the panel: it is the one
   thing the page shows over the game, and the table wants the width. */
/* Only [open] carries the display, or it would override the closed dialog's
   display:none and sit on the page from the first paint. */
#cheats{width:min(720px,calc(100vw - 32px));max-height:min(680px,calc(100dvh - 32px));padding:0;overflow:hidden;flex-direction:column;border:1px solid var(--line);border-radius:14px;background:var(--panel);color:var(--text)}
#cheats[open]{display:flex;position:fixed;inset:0;margin:auto;z-index:100;box-shadow:0 0 0 100vmax #050807bf}
#cheats header{display:flex;align-items:center;justify-content:space-between;gap:12px;flex:none;padding:18px 12px 14px 24px;border-bottom:1px solid var(--line)}
#cheats h2{margin:0;font-size:20px;font-weight:650;letter-spacing:-.03em}
#close-cheats{display:grid;place-items:center;width:44px;min-height:44px;border:0;color:var(--muted)}
#close-cheats svg{width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round}
.cheats-body{overflow:auto;overscroll-behavior:contain;scrollbar-gutter:stable;scrollbar-width:thin;scrollbar-color:#445044 transparent;padding:18px 24px 24px}
.cheats-body p{margin:0 0 10px;font-size:14px;line-height:1.7;color:#b6c1b4;word-break:keep-all}
.cheats-body table{width:100%;border-collapse:collapse;margin:18px 0}
.cheats-body th,.cheats-body td{text-align:left;padding:10px 12px 10px 0;border-bottom:1px solid var(--line);vertical-align:top;font-size:14px;word-break:keep-all}
.cheats-body th{color:#b1bda7;font-size:11px;font-weight:600}
.cheats-body code{font-size:13px;color:var(--accent);white-space:nowrap}
#cheat-status{flex:none;margin:0;padding:10px 24px;font-size:13px;line-height:1.5;color:var(--accent)}
#cheat-status:empty{display:none}
.cheat-apply{min-height:44px;width:100%;padding:0;border:0;text-align:left;background:transparent}
.cheats-body tbody tr{cursor:pointer}
.cheats-body tbody tr:hover{background:var(--accent-bg)}
.cheats-body tbody tr:focus-within{outline:1px solid var(--accent);outline-offset:-1px}

@media(min-width:801px) and (max-width:1100px){.workspace{--panel-width:320px}}
@media(max-width:800px){
  html,body{height:auto;min-height:100%;overflow:auto}
  /* Narrow is the only layout where the page itself scrolls, so it is the only
     one that can jog sideways as content grows past the viewport. */
  html{scrollbar-gutter:stable}
  body{min-height:100dvh;display:block}
  .workspace{grid-template-columns:minmax(0,1fr);grid-template-rows:auto min(760px,90dvh);overflow:visible}
  #stage{grid-row:1;grid-column:1;height:min(60dvh,75vw);min-height:180px}
  #panel{grid-row:2;border-left:0;border-top:1px solid var(--line)}
}

/* Touch game controls retain the full 4:3 viewport. */
#touch{display:none}
.touch,.touch body{height:100%;min-height:0;overflow:hidden;overscroll-behavior:none}
/* Native form controls can pan; no ancestor grants pinch/double-tap zoom. */
.touch body{display:block;touch-action:pan-x pan-y}
.touch .workspace{display:grid;height:100dvh;overflow:hidden;grid-template-columns:minmax(0,1fr);grid-template-rows:auto minmax(0,1fr);padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}
.touch #stage{grid-area:auto;width:100%;height:auto;min-height:0;aspect-ratio:var(--screen-aspect,4/3);max-height:min(70dvh,max(96px,calc(100dvh - var(--touch-controls-height,0px) - env(safe-area-inset-top) - env(safe-area-inset-bottom))))}
.touch #touch{display:grid;min-height:0;gap:10px;padding:12px;background:var(--bg);grid-template-columns:minmax(0,1fr) minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto;touch-action:none;user-select:none;-webkit-user-select:none}
.touch .pad-move{position:relative;place-self:end start;margin-bottom:0;border-radius:50%;background:var(--fsb-surface);border:1px solid var(--fsb-line);width:var(--stick);height:var(--stick);--stick:clamp(148px,46vw,224px)}
/* touch-action has to sit on the controls themselves. #touch is display:contents
   in landscape, where it generates no box and its own value would never apply,
   and ordinary buttons allow native panning, which lets the
   browser claim a drag and cancel the pointer mid-gesture. */
/* Selection and the context menu are separate. touch-guard.js also cancels
   touchstart, before WebKit can begin a selection-loupe gesture. */
.touch .pad-move,.touch .pad-act,.touch .pad-util,.touch .pad-key,.touch .util,
.touch #stage,.touch canvas{touch-action:none;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none}
.touch .pad-move[data-disabled=true]{opacity:.35}
.touch .stick-knob{position:absolute;left:50%;top:50%;width:38%;height:38%;border-radius:50%;
  background:var(--fsb-raised);border:1px solid var(--fsb-line);
  transform:translate(-50%,-50%) translate(var(--knob-x,0),var(--knob-y,0));
  transition:background .08s,border-color .08s}
.touch .pad-move[data-direction] .stick-knob{background:var(--accent);border-color:var(--accent)}
.touch .stick-mark{position:absolute;border:9px solid transparent;opacity:.55}
.touch .stick-mark.up{left:50%;top:7%;margin-left:-9px;border-bottom-color:var(--text)}
.touch .stick-mark.down{left:50%;bottom:7%;margin-left:-9px;border-top-color:var(--text)}
.touch .stick-mark.left{top:50%;left:7%;margin-top:-9px;border-right-color:var(--text)}
.touch .stick-mark.right{top:50%;right:7%;margin-top:-9px;border-left-color:var(--text)}
.touch .pad-move[data-direction=ArrowUp] .up,.touch .pad-move[data-direction=ArrowDown] .down,
.touch .pad-move[data-direction=ArrowLeft] .left,.touch .pad-move[data-direction=ArrowRight] .right{opacity:1}
.touch .pad-act{display:grid;place-self:end end;margin-bottom:0;gap:8px;grid-auto-flow:row;justify-items:center;min-height:0}
.touch .pad-util{grid-column:1/-1;display:flex;justify-content:space-between;align-items:stretch;gap:6px}
/* Two columns rather than one long row: portrait has height to spare below
   the stick, and seven keys across would close the cleared middle. */
.touch .util-group{display:grid;grid-template-columns:repeat(2,minmax(58px,1fr));gap:6px;flex:0 1 auto;min-width:0}
.touch .pad-key,.touch .util{display:grid;place-items:center;background:var(--fsb-surface);border-color:var(--fsb-line);color:var(--text);border-radius:12px;min-height:0;font-size:15px;font-weight:600;line-height:1}
.touch .pad-key.held,.touch .pad-key:active,.touch .util.held,.touch .util:active{background:var(--accent);border-color:var(--accent);color:var(--fsb-accent-text)}
.touch .pad-key:disabled{opacity:.3}
.touch .act{width:clamp(66px,19vw,100px);height:clamp(66px,19vw,100px);border-radius:50%;font-size:15px}
.touch .act-a{background:var(--accent-bg);border-color:var(--fsb-accent)}
.touch .act-shift{width:clamp(66px,19vw,100px);height:auto;min-height:44px;border-radius:14px;font-size:13px}
.touch .util{gap:3px;padding:7px 2px;font-size:14px;min-height:48px}
.touch .util small{font-size:10px;font-weight:400;color:var(--muted);white-space:nowrap}
.touch .util:active small,.touch .util.held small{color:var(--fsb-accent-text)}
/* Landscape: the leftover is the two side bars, so everything goes there and
   the picture keeps the full height between them. The utility keys used to run
   as one band across the bottom, which cost the game that much height and put
   buttons under the middle of the picture; each group is now the foot of its
   own side bar instead, and the stage spans both rows. */
@media(orientation:landscape){
  .touch .workspace{--side:clamp(112px,calc((100vw - 100dvh*4/3)/2),210px);grid-template-columns:var(--side) minmax(0,1fr) var(--side);grid-template-rows:minmax(0,1fr) auto}
  .touch #stage{grid-area:1/2/3/3;aspect-ratio:auto;height:100%;max-height:none}
  .touch #touch{display:contents}
  .touch .pad-move{grid-area:1/1;place-self:end center;margin-bottom:clamp(6px,3dvh,24px);--stick:min(clamp(132px,34dvh,220px),calc(var(--side) - 4px))}
  .touch .pad-act{grid-area:1/3;place-self:end center;margin-bottom:clamp(6px,3dvh,24px);gap:8px}
  .touch .act{width:clamp(62px,11dvh,96px);height:clamp(62px,11dvh,96px);font-size:15px}
  /* After .act, which also matches this button and would otherwise put its
     square height back — Shift is a modifier and reads as a shorter pill. */
  .touch .act-shift{width:clamp(62px,11dvh,96px);height:44px;min-height:44px;font-size:13px}
  .touch .pad-util{display:contents}
  /* auto-fit rather than a fixed three, so a narrow bar stacks the keys
     instead of squeezing them past reading. */
  .touch .util-group{display:grid;grid-auto-flow:row;grid-template-columns:repeat(auto-fit,minmax(46px,1fr));gap:6px;padding:0 6px 8px;min-width:0}
  .touch .util-group:first-child{grid-area:2/1}
  .touch .util-group:last-child{grid-area:2/3}
  .touch .util small{font-size:10px}
}

/* A full-height menu on phones; landscape uses a compact navigation rail. */
.touch #panel{position:fixed;inset:0;z-index:20;height:100dvh;grid-area:auto;border:0;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);visibility:hidden;opacity:0;transform:translateY(24px);transition:transform 180ms cubic-bezier(.3,0,1,1),opacity 180ms ease,visibility 180ms;box-shadow:0 -12px 40px #0005}
.touch.menu-open #panel{visibility:visible;opacity:1;transform:none;transition-duration:260ms;transition-timing-function:cubic-bezier(.2,0,0,1)}
.touch #panel-fullscreen{display:none}
.touch .panel-heading{padding:20px 20px 16px}
.touch .brand{margin-bottom:16px}
.touch .tab{min-height:44px;font-size:13px}
.touch .panel-footer{display:block;flex:none;padding:12px 20px;border-top:1px solid var(--line);background:#18211a}
#close-menu{display:flex;align-items:center;justify-content:center;gap:10px;width:100%;min-height:48px;border:1px solid #586745;background:var(--accent);color:#26311f;font-size:13px;font-weight:650}
#close-menu:hover{background:#e0e9c4}
.touch .touch-tip{display:block}
.touch .list-footnote{display:none}
@media(orientation:landscape){
  .touch #panel{display:grid;grid-template-columns:228px minmax(0,1fr);grid-template-rows:auto minmax(0,1fr) auto;transform:translateX(24px)}
  .touch.menu-open #panel{transform:none}
  .touch #panel .panel-heading{grid-area:1/1;padding:20px 16px 16px}
  .touch #panel .brand{gap:9px;margin-bottom:16px}
  .touch #panel .brand img{width:28px;height:28px}
  .touch #panel .brand h1{font-size:14px}
  .touch #panel .brand p{font-size:8px;letter-spacing:.08em}
  .touch #panel .tabs{grid-area:2/1;display:flex;flex-direction:column;align-self:stretch;min-height:0;overflow-y:auto;margin:0 16px 12px;padding:0;border:0;background:transparent}
  .touch #panel .tab{flex-shrink:0;justify-content:flex-start;padding:0 12px;gap:10px;min-height:44px}
  .touch #panel .tab svg{display:block}
  .touch #panel .panel-body{grid-area:1/2/4/3;border-left:1px solid var(--line);padding-top:0}
  .touch #panel .tab-panel{padding-top:20px}
  .touch #panel .panel-footer{grid-area:3/1;padding:12px 16px;border-top:0;background:transparent}
  .touch #panel #close-menu{font-size:12px;min-height:44px}
  .touch #panel .save-row{min-height:64px}
  .touch #panel .checkpoint,.touch #panel .slot-load{min-height:62px;padding-top:8px;padding-bottom:8px}
  .touch #panel .scroll-list h2{padding-top:12px}
}
@media(orientation:landscape) and (max-height:380px){
  .touch #panel .panel-heading{padding-top:12px;padding-bottom:10px}
  .touch #panel .brand{margin-bottom:10px}
  .touch #panel .brand p{display:none}
  .touch #panel .tabs{gap:0;margin-bottom:4px}
  .touch #panel .panel-footer{padding-top:6px;padding-bottom:8px}
  .touch #panel .tab-panel{padding-top:12px}
}
@media(orientation:portrait) and (max-height:700px){
  .touch .panel-heading{padding:12px 20px}
  .touch .brand{margin-bottom:12px}
  .touch .brand p{display:none}
  .touch .tabs{margin-bottom:12px}
  .touch #saves-pane .pane-intro h2{display:none}
  .touch #saves-pane .pane-intro{padding-bottom:12px}
  .touch #saves-pane .pane-intro p{margin:0}
  .touch .import-box{padding:10px}
}
@media(prefers-reduced-motion:reduce){.touch #panel,.touch.menu-open #panel,.switch-track:after,.workspace{transition:none}
  .panel-collapsed #panel{transition:none}}

/* Desktop fullscreen gives the entire viewport to the game. */
/* Collapsing the panel. The workspace loses its second column and the square
   that brings it back takes the corner the panel used to start at. */
.panel-collapsed .workspace{grid-template-columns:minmax(0,1fr) 0}
/* The column closes while the panel keeps its own width, so it slides out past
   the right edge and the workspace clips it — nothing inside rewraps on the way
   out. Hidden once it has finished leaving, which is also what takes it out of
   the tab order. */
/* Only where the panel is a column. On touch it is a full-width sheet and a
   floor of 360px would push it off a narrow phone. */
:root:not(.touch) #panel{min-width:var(--panel-width)}
.panel-collapsed #panel{visibility:hidden;transition:visibility 0s 260ms}
#collapse-panel{position:absolute;top:16px;right:16px;display:grid;place-items:center;width:38px;min-height:38px;padding:0;color:var(--muted)}
#collapse-panel:not(:disabled):hover{color:var(--accent);border-color:#607347}
/* Touch already opens and shuts the panel as a sheet; a second way to hide it
   would only sit in the way, including in the sheet's focus order. */
.touch #collapse-panel{display:none}
/* Waits for the panel to finish leaving rather than appearing over it on the
   way out; on the way back it goes at once, before the panel arrives. */
#expand-panel{display:flex;position:absolute;top:16px;right:16px;z-index:2;align-items:center;gap:0;padding:0 11px;min-height:44px;background:#152019e8;border-color:#40503c;color:var(--text);font-size:13px;opacity:0;visibility:hidden;transition:opacity 120ms ease,visibility 0s 120ms}
.panel-collapsed:not(.touch) #expand-panel{opacity:1;visibility:visible;transition:opacity 160ms ease 260ms,visibility 0s 260ms}
.touch #expand-panel{display:none}
#expand-panel span{max-width:0;margin-left:0;overflow:hidden;white-space:nowrap;opacity:0;transition:max-width 200ms cubic-bezier(.2,0,0,1),margin-left 200ms cubic-bezier(.2,0,0,1),opacity 140ms ease}
#expand-panel:is(:hover,:focus-visible) span{max-width:120px;margin-left:9px;opacity:1}
#expand-panel svg{flex:none;width:18px;height:18px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
:root:fullscreen #expand-panel{display:none}
@media(prefers-reduced-motion:reduce){#expand-panel,.panel-collapsed:not(.touch) #expand-panel{transition:none}}
#exit-fullscreen{display:none;position:absolute;top:16px;right:16px;z-index:2;align-items:center;gap:0;padding:0 16px;min-height:52px;background:#152019e8;border-color:#40503c;color:var(--text);font-size:15px}
#exit-fullscreen span{max-width:0;margin-left:0;overflow:hidden;white-space:nowrap;opacity:0;transition:max-width 200ms cubic-bezier(.2,0,0,1),margin-left 200ms cubic-bezier(.2,0,0,1),opacity 140ms ease}
#exit-fullscreen:is(:hover,:focus-visible) span{max-width:140px;margin-left:10px;opacity:1}
#exit-fullscreen svg{flex:none;width:20px;height:20px;fill:none;stroke:currentColor;stroke-width:1.6;stroke-linecap:round;stroke-linejoin:round}
:root:fullscreen:not(.touch),:root:fullscreen:not(.touch) body{height:100%;min-height:0;overflow:hidden}
:root:fullscreen:not(.touch) .workspace{height:100dvh;grid-template-columns:minmax(0,1fr);grid-template-rows:minmax(0,1fr);overflow:hidden}
:root:fullscreen:not(.touch) #panel{display:none}
:root:fullscreen:not(.touch) #stage{grid-area:1/1;height:100%;min-height:0}
:root:fullscreen:not(.touch) #exit-fullscreen{display:flex}

@media(prefers-reduced-motion:reduce){#exit-fullscreen span{transition:none}}

/* Sharing operates on a saved .dat snapshot, independently of the live game.
   Above the row-wide press, so taking a copy of a scene is not entering it. */
.row-actions{position:relative;display:flex;flex:none;align-items:center;gap:6px}
#checkpoint-list .download-button{width:32px;min-height:32px;border:1px solid var(--line);border-radius:8px}
.share-file-button{width:100%;margin-top:10px;border:0;border-top:1px solid var(--line);border-radius:0;padding-top:8px;color:var(--accent);font-size:12px}
.save-sharing{margin:0 20px 16px}
#share-status{margin:0;color:var(--muted);font-size:11px;line-height:1.6}
#shared-save-link{display:flex;align-items:center;gap:6px;margin-top:10px}
#share-url{min-width:0;width:100%;min-height:40px;border:1px solid var(--line);border-radius:8px;background:#111813;color:var(--text);padding:0 9px;font-size:12px}
#copy-save-link{flex:none;padding:0 12px;min-height:40px;color:var(--accent);font-size:12px}

/* Copy feedback stays inside the panel, including the mobile menu. */
.share-button[data-copying] svg{animation:copy-loading 800ms linear infinite}
.share-button.copied{color:var(--accent);background:var(--accent-bg)}
.share-button.copied svg{animation:copy-confirm 180ms cubic-bezier(.2,0,0,1)}
@keyframes copy-loading{to{transform:rotate(360deg)}}
@keyframes copy-confirm{from{transform:scale(.75)}to{transform:scale(1)}}
#panel-toast{position:absolute;z-index:4;left:50%;bottom:20px;display:flex;align-items:center;gap:9px;width:max-content;max-width:calc(100% - 32px);padding:11px 14px;border:1px solid #d0dcab;border-radius:12px;background:#d0dcab;color:#26311f;font-size:12px;font-weight:600;line-height:1.5;box-shadow:0 6px 24px #0005;pointer-events:none;opacity:0;transform:translate(-50%,8px);transition:opacity 160ms ease,transform 200ms cubic-bezier(.2,0,0,1)}
#panel-toast.visible{opacity:1;transform:translate(-50%,0)}
#panel-toast .toast-icon{display:flex;flex:none}
#panel-toast[data-kind=error]{background:#352823;border-color:#9d7565;color:#ffe0d3}
.touch #panel-toast{bottom:calc(84px + env(safe-area-inset-bottom))}
@media(prefers-reduced-motion:reduce){.share-button svg{animation:none!important}#panel-toast{transition:none}}

/* Two shortcut collections share the same list and keep their scroll position. */
.scene-groups{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;flex:none;margin:0 20px 4px;border-bottom:1px solid var(--line)}
.scene-groups button{display:flex;align-items:center;justify-content:center;gap:6px;min-width:0;min-height:42px;padding:6px 0;border:0;border-bottom:2px solid transparent;border-radius:0;color:var(--muted);font-size:12px;font-weight:600;white-space:nowrap}
.scene-groups button[aria-pressed=true]{border-bottom-color:var(--accent);color:var(--accent)}
.scene-groups button:hover{background:transparent;color:var(--text)}
.scene-groups span{font-size:10px;font-variant-numeric:tabular-nums;opacity:.8}

/* Presentation stays outside the engine framebuffer and covers both renderers. */
#stage{place-items:center}
#viewport{position:relative;flex:none;overflow:hidden}
#viewport canvas{width:100%;height:100%}
/* Brightness multiplies; gamma bends. Both are identity at their defaults, so
   a player who never touches them gets exactly the original's output. */
#viewport canvas{filter:url(#fsb-gamma) brightness(var(--screen-brightness,1))}
:root[data-screen-filter=pixel] #viewport canvas,:root[data-screen-filter=crt] #viewport canvas{image-rendering:pixelated}
:root[data-screen-filter=smooth] #viewport canvas{image-rendering:auto}
:root[data-screen-filter=crt] #viewport::after{content:'';position:absolute;inset:0;pointer-events:none;background:repeating-linear-gradient(to bottom,transparent 0,transparent 2px,#0003 2px,#0003 3px);box-shadow:inset 0 0 50px #0005}

.settings-group>h2{margin:0 20px 10px;font-size:13px;font-weight:650;color:var(--accent)}
.settings-group .panel-actions{margin-bottom:20px}
.settings-content{padding:0 14px 14px;min-width:0}
.settings-group .setting-row{min-height:64px;padding:12px 0;font-size:12px}
.settings-group select{min-width:0;max-width:60%;min-height:40px;padding:7px 9px;border:1px solid var(--line);border-radius:8px;background:var(--bg);color:var(--text);font-size:12px}
.settings-group input[type=range]{width:48%;min-width:0;min-height:44px;accent-color:var(--accent)}
.settings-group output{display:block;margin-top:3px;font-size:11px;color:var(--muted);font-variant-numeric:tabular-nums}
.setting-note{margin:10px 0;color:var(--muted);font-size:11px;line-height:1.7;word-break:keep-all;overflow-wrap:anywhere}
.setting-presets{display:flex;gap:8px}.setting-presets button{flex:1;font-size:12px}
/* Label, then the two slots an action can answer to. */
.key-binding{display:grid;grid-template-columns:minmax(0,1fr) 84px 84px;align-items:center;gap:6px;padding:5px 0;font-size:11px}
.key-binding button{min-width:0;padding:5px 4px;min-height:36px;color:var(--accent);font-size:12px}
.key-binding button.key-binding-empty{color:var(--muted);border-style:dashed}
.key-binding-head{padding:0 0 4px;color:var(--muted);font-size:10px}
.key-binding-head span+span{text-align:center}
.key-binding button[aria-pressed=true]{border-color:var(--accent);background:var(--accent-bg)}
.binding-feedback{position:sticky;bottom:0;background:var(--panel)}
.binding-feedback:has(p:not(:empty)){padding:4px 0;border-top:1px solid var(--line)}
#cancel-binding,.setting-reset{min-height:36px;padding:4px 12px;color:var(--muted);font-size:11px}
#gamepad-status.connected{color:var(--accent)}
.gamepad-guide{padding:0}.gamepad-guide div{align-items:start}.gamepad-guide dd{word-break:keep-all}
.pad-preview{display:flex;align-items:center;justify-content:space-between;margin-top:14px;padding:14px 20px;border:1px solid var(--line);border-radius:10px;background:var(--bg);height:112px}
.preview-stick,.preview-actions{display:grid;place-items:center;color:var(--accent);font-size:22px;opacity:var(--pad-opacity,1);transform:scale(var(--pad-scale,1))}
.preview-stick{width:46px;height:46px;border:1px solid var(--line);border-radius:50%}
/* The preview stands in for a strip several times its size, so it shows the
   move at a quarter — enough to read which way the pad went. */
.preview-stick{translate:calc(var(--pad-offset-x,0px)*.25) calc(var(--pad-offset-y,0px)*-.25)}
.preview-actions{translate:calc(var(--pad-offset-x,0px)*-.25) calc(var(--pad-offset-y,0px)*-.25)}
/* Each control moves towards the middle of the screen and upwards, away from
   the edge its strip sits against; player-settings keeps the distance inside
   the room the control gave up, so nothing reaches the picture. */
.touch .pad-move{width:var(--touch-stick-size,var(--stick));height:var(--touch-stick-size,var(--stick));grid-row:1;grid-column:1;opacity:var(--pad-opacity,1);translate:var(--pad-offset-x,0px) calc(var(--pad-offset-y,0px)*-1)}
.touch .pad-act{grid-row:1;grid-column:2;opacity:var(--pad-opacity,1);translate:calc(var(--pad-offset-x,0px)*-1) calc(var(--pad-offset-y,0px)*-1)}
.touch .act{width:var(--touch-action-size,66px);height:var(--touch-action-size,66px)}
.touch .act-shift{height:44px;min-height:44px}
.touch .util-group{opacity:var(--pad-opacity,1)}
.touch #toggle-fullscreen[hidden]+#open-menu{grid-column:-2/-1}
.touch[data-touch-swap-rows=true] #touch{grid-template-rows:auto minmax(0,1fr)}
.touch[data-touch-swap-rows=true] .pad-move,.touch[data-touch-swap-rows=true] .pad-act{grid-row:2}
.touch[data-touch-swap-rows=true] .pad-util{grid-row:1}
@media(orientation:portrait){
  .touch[data-touch-center-menus=true] .pad-util{justify-content:center}
}
@media(orientation:landscape){
  .touch .pad-act{grid-column:3}
  .touch[data-touch-swap-rows=true] .workspace{grid-template-rows:auto minmax(0,1fr)}
  .touch[data-touch-swap-rows=true] .util-group{grid-row:1;padding:8px 6px 0}
}

/* Native focus zoom is separate from pinch/double-tap zoom in iOS Safari. */
.touch-input input:not([type=range]):not([type=checkbox]):not([type=radio]),
.touch-input textarea,.touch-input select{font-size:16px!important}
/* An already zoomed/restored tab must retain a way to pinch back to 1x. */
.browser-zoomed,.browser-zoomed body,.browser-zoomed body *{touch-action:pan-x pan-y pinch-zoom!important}

.player-auth{display:flex;align-items:center;gap:8px;min-width:0}
#player-profile{display:flex;align-items:center;gap:6px;min-width:0;flex:1;color:var(--text);font-size:12px;text-decoration:none}
#player-avatar{width:24px;height:24px;flex:none;object-fit:contain;image-rendering:pixelated}
#player-nickname{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}
.player-auth button,#player-sign-in{min-height:36px;padding:6px 10px;font-size:11px;white-space:nowrap}
#player-sign-in{display:flex;align-items:center;justify-content:center;gap:6px;width:100%}
#player-sign-in img{flex:none}
.player-auth:has(#player-profile[hidden]):has(#player-sign-out[hidden]){display:none}
.save-login{flex:none;padding:12px 20px}
#player-auth-status{margin:8px 0 0;color:var(--muted);font-size:11px;line-height:1.5}

.save-storage-tabs{display:flex;gap:6px;padding:0 20px 16px}
.save-storage-tabs button{flex:1;min-height:40px;font-size:13px}
.save-storage-tabs button[aria-pressed=true]{background:var(--accent-bg);color:var(--accent);border-color:var(--accent)}
#cloud-save-list{padding:0 16px 16px}

#save-status:empty{display:none}

.current-save-wrap{position:relative;display:inline-flex;max-width:100%}
.current-save{min-height:36px;padding:6px 10px;font-size:11px;color:var(--accent);border-color:#44553a;background:var(--accent-bg)}
/* Drawn against the viewport by app.js: inside the row it was clipped by the
   scrolling list, which hid it completely on the top slot. */
.slot-tip{position:fixed;z-index:30;width:max-content;max-width:min(260px,calc(100vw - 32px));padding:7px 10px;border:1px solid var(--line);border-radius:7px;background:#0b1110f7;color:var(--text);font-size:12px;line-height:1.5;white-space:normal;pointer-events:none;box-shadow:0 6px 20px #0007}

/* Browser and cloud slots share this compact card and action bar. */
#local-save-list,#cloud-save-list{padding:0 16px 16px}
/* One slot is one card, and the card is the button that loads it: the load
   area reaches under the whole card, and the save and share buttons sit on top
   of it as targets of their own. */
.save-card{position:relative;display:block;min-height:0;margin:0 0 8px;padding:8px;border:1px solid var(--line);border-radius:12px;background:var(--fsb-surface);transition:background 120ms ease,border-color 120ms ease}
.save-card:last-child{margin-bottom:0}
.save-card-main{display:flex;align-items:center;gap:8px;min-width:0}
/* Two rows, and the same two rows whether or not the slot holds a game: the
   slot and its playtime, then where the party is and what level it is. Saving
   into an empty slot fills boxes that were already there, so no card in the
   list ever resizes. */
.save-card .slot-load,.touch #panel .save-card .slot-load{position:static;display:grid;grid-template-columns:minmax(0,auto) 1fr;align-items:baseline;column-gap:10px;row-gap:1px;flex:1;width:auto;min-width:0;min-height:34px;height:auto;padding:2px;border:0;background:none;text-align:left}
/* The press covers the card it is drawn in, under the buttons that sit on top
   of it, so the whole card is the one target it looks like. */
.save-card .slot-load::before{content:'';position:absolute;inset:0}
.save-card .slot-load:not(:disabled):hover{background:none}
.save-card .slot-load:focus-visible{outline:none}
.save-card:has(.slot-load:focus-visible){outline:2px solid var(--accent);outline-offset:3px}
@media(hover:hover){
  .save-card:has(.slot-load:not(:disabled):hover){background:var(--accent-bg);border-color:#44553a}
}
.save-card:has(.slot-load:not(:disabled):active){background:var(--fsb-hover)}
.save-card .slot-load:disabled{opacity:1;color:var(--muted)}
.save-card .row-title{grid-area:1/1;justify-self:start;font-size:13px;line-height:18px;font-weight:650}
.save-card .slot-load small{grid-area:2/1/auto/-1;display:block;max-width:100%;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:12px;line-height:16px;color:var(--muted)}
/* Above the card-wide load target, so pressing one of these is not a load. */
.save-card-actions{position:relative;display:flex;flex:none;align-items:center;gap:6px}
.save-card-actions .current-save{min-height:32px;padding:5px 10px;font-size:11px}
.save-card-actions .download-button{width:32px;min-height:32px;padding:0;border:1px solid var(--line);border-radius:8px}
/* Playtime closes the title line, leaving the whole second line to the map
   name, which is the one part of a slot long enough to need the ellipsis. */
.save-card-meta{grid-area:1/2;justify-self:end;margin:0;font-size:11px;font-weight:400;line-height:16px;color:var(--muted);opacity:.85;white-space:nowrap;font-variant-numeric:tabular-nums}
.save-card-notice button{min-height:32px;padding:5px 9px;font-size:10px}
.save-card-notice{position:relative;display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:5px;color:var(--accent)}
.save-card-notice small{margin:0;font-size:10px}
.save-card.dragover{background:var(--accent-bg);border-color:var(--accent)}
/* An empty slot is drawn as the outline of a card waiting to be filled: no
   surface, a dashed edge, and a title no louder than the slots that hold a
   game. Keyed to the bytes rather than to the load button, which is also
   disabled while the engine is busy. */
.save-card[data-empty]{background:transparent;border-style:dashed;border-color:#2f3b32}
.save-card[data-empty] .row-title{color:var(--muted);font-weight:550}

.saves-scroll>#shared-save-link{margin:12px 20px}

#storage-cloud{position:relative}
#storage-cloud[aria-disabled=true]{cursor:not-allowed}
#storage-cloud[aria-disabled=true]::after{content:'Google 로그인 후 클라우드 슬롯을 사용할 수 있습니다.';position:absolute;right:0;bottom:calc(100% + 6px);z-index:8;width:220px;max-width:calc(100vw - 64px);padding:8px 10px;border:1px solid var(--line);border-radius:8px;background:#0b1110f7;color:var(--text);font-size:12px;font-weight:400;line-height:1.5;white-space:normal;pointer-events:none;opacity:0}
@media(hover:hover){#storage-cloud[aria-disabled=true]:hover::after,#storage-cloud[aria-disabled=true]:focus-visible::after{opacity:1}}
.save-storage-tabs[data-login-tip] #storage-cloud[aria-disabled=true]::after{opacity:1}

/* A save slot that is fetching and starting a session, one whose bytes are on
   their way to the account, and one that has just been written. Each rides
   beside the title, out of the flow, so no wait ever resizes a card; the
   playtime stands down for the length of the wait. */
@keyframes fsb-slot-spin{to{transform:rotate(1turn)}}
@keyframes fsb-slot-saved{0%{background:var(--accent-bg)}100%{background:transparent}}
.save-card[data-loading] .row-title,.save-card[data-syncing] .row-title,.save-card[data-saved] .row-title{position:relative}
.save-card[data-loading] .save-card-meta,.save-card[data-syncing] .save-card-meta,.save-card[data-saved] .save-card-meta{opacity:0}
.save-card[data-loading] .row-title::after{content:'';position:absolute;left:calc(100% + 8px);top:50%;width:11px;height:11px;margin-top:-5.5px;
  border:2px solid var(--accent);border-right-color:transparent;border-radius:50%;
  animation:fsb-slot-spin .7s linear infinite}
.save-card[data-loading] .save-card-actions{opacity:.35}
.save-card[data-loading] .slot-load small{color:var(--accent)}
.save-card[data-syncing] .row-title::after{content:'동기화 중…';position:absolute;left:calc(100% + 8px);top:50%;
  margin-top:-8px;font-size:11px;font-weight:400;line-height:16px;color:var(--accent);white-space:nowrap}
.save-card[data-saved]{animation:fsb-slot-saved 2.2s ease-out}
.save-card[data-saved] .row-title::after{content:'저장됨';position:absolute;left:calc(100% + 8px);top:50%;margin-top:-8px;
  padding:0 6px;border-radius:999px;background:var(--accent);color:var(--fsb-accent-text);
  font-size:9px;font-weight:700;line-height:16px;white-space:nowrap}
/* The cloud list before its slots arrive. The wait rides the tab that was
   pressed, where it costs the list no space; the slots themselves already say
   they are on the way. */
#storage-cloud[data-loading=true]::before{content:'';position:absolute;right:10px;top:50%;width:11px;height:11px;margin-top:-5.5px;
  border:2px solid var(--accent);border-right-color:transparent;border-radius:50%;
  animation:fsb-slot-spin .7s linear infinite}
@media(prefers-reduced-motion:reduce){
  .save-card[data-loading] .row-title::after,#storage-cloud[data-loading=true]::before{animation-duration:2s}
  .save-card[data-saved]{animation:none;background:var(--accent-bg)}
}

/* The overwrite question. Same surface as the cheat dialog; the actions read
   left to right with the safe one first. */
#confirm-dialog{max-width:min(420px,calc(100vw - 32px));padding:0;border:1px solid var(--line);border-radius:16px;background:var(--panel);color:var(--text)}
#confirm-dialog::backdrop{background:#0009;backdrop-filter:blur(2px)}
#confirm-dialog header{padding:20px 22px 0}
#confirm-dialog h2{margin:0;font-size:17px;font-weight:700;letter-spacing:-.02em}
.confirm-body{padding:10px 22px 0}
.confirm-body p{margin:0;color:var(--muted);font-size:13px;line-height:1.7;word-break:keep-all}
.confirm-detail{margin-top:12px;padding:10px 12px;border:1px solid var(--line);border-radius:10px;background:var(--bg);color:var(--accent);font-size:12px;line-height:1.6}
.confirm-actions{display:flex;justify-content:flex-end;gap:8px;padding:18px 22px 20px}
.confirm-actions button{min-height:40px;padding:0 16px;font-size:13px;font-weight:600;border-radius:10px}
.confirm-actions .primary{min-height:40px;font-size:13px}
@media(max-width:520px){.confirm-actions{flex-direction:column-reverse}.confirm-actions button{width:100%}}
