:root {
  --bg: #12161f;
  --ink: #f4ead2;
  --ink-dim: rgba(244, 234, 210, 0.55);
  --bar: #e8b93a;
  --bar-failed: #c4473a;
  --bar-track: rgba(244, 234, 210, 0.16);
  --knob: rgba(244, 234, 210, 0.85);
  --pad: rgba(18, 22, 31, 0.28);
  /* Stacking order (owner tg 7180 「설정·모달은 항상 맨 위」), lowest first. Use these names for anything new; files that keep a
     plain number say which step it is.
       1  letter (.letter)
       2  HUD: logo, © Float, Steam badge, gear, camera / eye buttons, joystick, 「앉기」 row (.near), tap ring
       3  name tags (.vgm-tags), server line, privacy line (.vg-policy)
       4  Vincent's bubble, talk panel (.chat), start choice
       5  room chat (.vgm-chat), action row, jump; 6 the 「더보기」 action sheet
      12  house panels (houses.js: 13 「내 집 · 남은 시간 · 방명록」, 12 rock-paper-scissors)
      14  top notice cards (.vgm-float 14, wait card 15)
      20  painting at the easel (multi/paint-panel.js 20-22)
      30  house prop move bar (map/houseprops-move.js)
      50  short toast (main.js)
      80  scene fade (#fade)
      90  loading screen (#loading 90, its tip 91)
     100  settings (.vgs) and every modal (.vgm-modal, .sheet): always on top; while one is open the floating panels hide
          (rule at the end of this file) */
  --z-fade: 80;
  --z-loading: 90;
  --z-modal: 100;
}
* { box-sizing: border-box; }
html, body {
  margin: 0; height: 100%; overflow: hidden; background: var(--bg);
  overscroll-behavior: none; -webkit-user-select: none; user-select: none; touch-action: none;
}
#view { display: block; width: 100%; height: 100%; outline: none; cursor: grab; }
#view.locked { cursor: none; }

#loading {
  position: fixed; inset: 0; display: grid; place-items: center; background: var(--bg);
  transition: opacity 400ms ease; z-index: 3;
}
#loading.done { opacity: 0; pointer-events: none; }
#loading .track { width: min(320px, calc(100% - 64px)); height: 4px; border-radius: 2px; background: var(--bar-track); overflow: hidden; }
#loading .fill { width: 100%; height: 100%; background: var(--bar); transform-origin: left center; transform: scaleX(0); transition: transform 120ms linear; }
#loading.failed .fill { background: var(--bar-failed); transform: scaleX(1); }

#hint {
  position: fixed; right: max(16px, env(safe-area-inset-right)); bottom: max(16px, env(safe-area-inset-bottom));
  color: var(--ink-dim); z-index: 2; pointer-events: none; line-height: 0;
  filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.45));
}
/* touch devices: the joystick itself is the control; no corner icon */
body.touch #hint { display: none; }
@media (pointer: coarse) { #hint { display: none; } }

#joystick {
  position: fixed; left: max(24px, env(safe-area-inset-left)); bottom: max(28px, env(safe-area-inset-bottom));
  width: 120px; height: 120px; border-radius: 50%; background: var(--pad);
  border: 2px solid var(--ink-dim); display: none; z-index: 2; touch-action: none;
}
body.touch #joystick { display: block; }
@media (pointer: coarse) { #joystick { display: block; } }
#knob {
  position: absolute; left: 50%; top: 50%; width: 48px; height: 48px; margin: -24px 0 0 -24px; border-radius: 50%;
  background: var(--knob); transform: translate(0, 0); will-change: transform;
}

/* ---------- web walk overlays and Vincent talk (vincent-ui.js) */
[hidden] { display: none !important; }  /* panels set display; `hidden` must still win */
:root {
  --night: #0f1a33;
  --night-2: #17264a;
  --night-glass: rgba(15, 26, 51, 0.86);
  --wheat: #e7b54a;
  --cream: #f4ecd8;
  --muted: #b9c3d9;
  --line: rgba(244, 236, 216, 0.16);
  --support: #2f4d3a;
  --display: "Cormorant Garamond", "Noto Serif KR", serif;
  --body: "Noto Serif KR", serif;
  --gutter: max(16px, env(safe-area-inset-left));
  --ease: cubic-bezier(0.16, 1, 0.3, 1);
}
body { color: var(--cream); font-family: var(--body); -webkit-text-size-adjust: 100%; word-break: keep-all; overflow-wrap: anywhere; }
button { font: inherit; color: inherit; cursor: pointer; }
a { color: var(--wheat); }

/* ① logo: the game logo image, 30% of the viewport width (60% below 768px; owner 10-31, tg 5516) */
.ov-logo {
  /* owner 10-31: top and left margin 2.5% of the width (at least 12px on phones) */
  /* owner tg 8364: below the top row, which now starts with the people count at the top left (was max(2.5vw, 12px, safe area)) */
  position: fixed; top: max(2.5vw, calc(var(--vg-top-row, 10px) + 42px)); left: max(2.5vw, 12px, env(safe-area-inset-left)); z-index: 2;
  pointer-events: none; line-height: 0;
}
.ov-logo img { display: block; width: 30vw; height: auto; filter: drop-shadow(0 1px 6px rgba(0, 0, 0, 0.35)); }
@media (max-width: 767.98px) { .ov-logo img { width: 60vw; } }  /* owner tg 5516 */
/* owner tg 6481: 「반고흐 속으로」 as the logo's subtitle on the Korean page, small, under the logo's left edge */
.ov-logo-sub { display: block; margin-top: 6px; padding-left: 2px; font-family: var(--display); font-weight: 600; line-height: 1.2;
  font-size: clamp(14px, 1.3vw, 22px); color: var(--cream, #f4ecd8); letter-spacing: 0.04em; text-shadow: 0 1px 6px rgba(0, 0, 0, 0.6); }
/* ② Steam notice */
/* letter panel + Steam badge: one block anchored to the bottom right (owner 10-31 「편지는 아래정렬」) */
:root {
  --block-bottom: max(16px, env(safe-area-inset-bottom));
  --badge-h: 64px;
  --letter-h: 60dvh;                                                     /* desktop: fixed 60dvh, scrolls inside */
  --letter-bottom: calc(var(--block-bottom) + var(--badge-h) + 8px);
}
.ov-steam-wrap { position: fixed; left: max(16px, env(safe-area-inset-left)); bottom: var(--block-bottom); z-index: 2; } /* owner tg 8391: bottom left (was right) */
/* touch (owner tg 8391 「컨트롤러보다 밑에」): on the joystick's corner but under it (joystick z 2), and never taking a finger;
   the link stays clickable with a mouse */
body.touch .ov-steam-wrap { z-index: 1; pointer-events: none; }
@media (pointer: coarse) { .ov-steam-wrap { z-index: 1; pointer-events: none; } }
.ov-steam {
  /* an app-store style badge (owner ref tg 5497): black, thin grey border, the official Steam symbol on the left, a thin
     "COMING SOON ON" line over the official STEAM® wordmark on the right (both split unchanged from Valve's logo SVG) */
  box-sizing: border-box; height: var(--badge-h); display: inline-flex; align-items: center; gap: 12px; padding: 8px 16px 8px 10px;
  background: #000; color: #fff; border: 2px solid #a6a6a6; border-radius: 10px; text-decoration: none;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); max-width: 100%;
}
.steam-symbol { display: block; height: 44px; width: auto; flex: none; }
.steam-lines { display: flex; flex-direction: column; justify-content: center; gap: 5px; }
.steam-soon {
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  font-size: 0.72rem; font-weight: 400; letter-spacing: 0.08em; text-transform: uppercase; white-space: nowrap; line-height: 1;
}
.steam-wordmark { display: block; height: 22px; width: auto; }
.steam-text { display: none; padding: 10px 18px; font-size: 0.9rem; }
a.ov-steam:hover, a.ov-steam:focus-visible { outline: 2px solid var(--wheat); outline-offset: 2px; }
body[data-steam-badge="text"] .ov-steam { height: auto; padding: 0; background: var(--night-glass); border: 1px solid var(--line); border-radius: 999px; }
body[data-steam-badge="text"] .steam-symbol, body[data-steam-badge="text"] .steam-lines { display: none; }
body[data-steam-badge="text"] .steam-text { display: inline; }
@media (max-width: 767.98px) {
  /* phones: the badge sits just above © Float, the letter (40dvh) right above it; with a joystick the whole block goes up
     above the joystick */
  :root { --block-bottom: max(34px, calc(env(safe-area-inset-bottom) + 28px)); --badge-h: 56px;
    --letter-bottom: calc(var(--block-bottom) + var(--badge-h) + 8px);
    --letter-h: min(27dvh, calc(100dvh - var(--letter-bottom) - var(--logo-bottom))); }
  /* touch (owner tg 5515): the badge sits right of the joystick on its baseline (8px gap); the letter goes above the joystick
     (joystick top + 8px) so its lower-left never covers the stick */
  body.touch { --block-bottom: max(28px, env(safe-area-inset-bottom));
    --letter-bottom: calc(max(28px, env(safe-area-inset-bottom)) + 120px + 8px);
    --letter-h: min(27dvh, calc(100dvh - var(--letter-bottom) - var(--logo-bottom))); }
  body.touch .ov-steam-wrap { left: max(24px, env(safe-area-inset-left)); max-width: 60vw; } /* tg 8391: in line with the joystick's left edge, under it */
  .ov-steam-wrap { max-width: 60vw; }
  .steam-symbol { height: 38px; }
  .steam-wordmark { height: 19px; }
  .steam-soon { font-size: 0.62rem; }
  .ov-steam { gap: 9px; padding: 6px 12px 6px 8px; }
}

/* ③ © Float */
.ov-copy {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: max(18px, env(safe-area-inset-bottom)); z-index: 2;
  background: none; border: 0; padding: 4px 8px; font-size: 0.8rem; color: var(--cream); text-shadow: 0 0 6px rgba(0, 0, 0, 0.85), 0 1px 2px rgba(0, 0, 0, 0.9);
}
.ov-copy:hover, .ov-copy:focus-visible { color: var(--cream); text-decoration: underline; }
/* © Float is a link to float.do (owner 10-31): white, never underlined; a soft ring for keyboard focus only */
a.ov-copy, a.ov-copy:hover, a.ov-copy:focus, a.ov-copy:focus-visible, a.ov-copy:visited { color: #fff; text-decoration: none; }
a.ov-copy { white-space: nowrap; border-radius: 6px; }
a.ov-copy:focus { outline: none; }
a.ov-copy:focus-visible { outline: 1px solid rgba(255, 255, 255, 0.6); outline-offset: 2px; }
/* phones: © Float stays bottom-centre on the last line; the Steam notice sits just above it on the right */
@media (max-width: 520px) {
  .ov-copy { bottom: max(6px, env(safe-area-inset-bottom)); }
}
/* © Float a size smaller (owner 10-31), still white and never underlined */
a.ov-copy { font-size: 0.7rem; }
/* © Float at the very bottom of the loading screen too (owner tg 7173): the in-game one's place, font, size, colour and glow */
#loading .loading-copy {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: max(18px, env(safe-area-inset-bottom)); padding: 4px 8px;
  font-size: 0.7rem; color: #fff; white-space: nowrap; pointer-events: none;
  text-shadow: 0 0 6px rgba(0, 0, 0, 0.85), 0 1px 2px rgba(0, 0, 0, 0.9);
}
@media (max-width: 520px) { #loading .loading-copy { bottom: max(6px, env(safe-area-inset-bottom)); } }
/* phones (owner 10-31 「COMING SOON을 스팀버튼에 넣고 버튼 자체 사이즈를 줄이라구」): the same badge with its COMING SOON ON
   line, scaled down as a whole; 44 px tall stays a full touch target */
@media (max-width: 640px) {
  :root { --badge-h: 44px; }
  .ov-steam { gap: 7px; padding: 4px 10px 4px 6px; border-width: 1px; border-radius: 8px; }
  .steam-lines { gap: 3px; }
  .steam-soon { font-size: 0.5rem; letter-spacing: 0.06em; }
  .steam-symbol { height: 30px; }
  .steam-wordmark { height: 14px; }
}
#hint { display: none; }

/* near Vincent only */
.near {
  position: fixed; left: 50%; bottom: max(84px, calc(env(safe-area-inset-bottom) + 72px)); transform: translateX(-50%); z-index: 2;
  display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; align-items: center;
}
/* the talk row (vincent-ui): the talk button (+ mic) on top, other buttons (「앉기」) in a row under it (owner tg 6381) */
.near:has(> .near-main) { flex-direction: column; flex-wrap: nowrap; align-items: center; }
.near-main, .near-more { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 8px; align-items: center; }
.near-more:not(:has(> :not([hidden]))) { display: none; }
.near-talk {
  display: inline-flex; gap: 10px; align-items: center; border: 1px solid var(--wheat); background: var(--night-glass);
  border-radius: 999px; padding: 12px 20px; font-weight: 600; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
  transition: transform 150ms var(--ease), background-color 150ms ease;
}
.near-talk:hover, .near-talk:focus-visible { transform: translateY(-2px); background: var(--night-2); }
.near-talk kbd { border: 1px solid var(--line); border-radius: 4px; padding: 0 6px; font-size: 0.8rem; color: var(--muted); }
body.touch .near-talk kbd { display: none; }
/* the 「앉기」/「일어나기」 button (main.js seatTick): same look as the talk button (live check 10-31: unstyled, text unreadable) */
.sit-btn {
  border: 1px solid var(--wheat); background: var(--night-glass); color: inherit; border-radius: 999px; padding: 12px 20px;
  min-height: 44px; font: inherit; font-weight: 600; box-shadow: 0 6px 24px rgba(0, 0, 0, 0.35);
  transition: transform 150ms var(--ease), background-color 150ms ease;
}
.sit-btn:hover, .sit-btn:focus-visible { transform: translateY(-2px); background: var(--night-2); }
.near-mic {
  width: 46px; height: 46px; border-radius: 50%; border: 1px solid var(--line); background: var(--night-glass); display: grid;
  place-items: center;
}
.near-mic.on { border-color: var(--wheat); color: var(--wheat); }
/* above the room's name tags and question bubbles (.vgm-tags, z 3): Vincent's answer is never covered (owner 6064 retake) */
.bubble {
  position: fixed; left: 0; top: 0; z-index: 4; max-width: min(280px, calc(100vw - 32px)); padding: 10px 14px; border-radius: 14px;
  background: var(--cream); color: var(--night); font-size: 0.92rem; line-height: 1.5;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3); will-change: transform;
}
/* the whole answer: a long one scrolls inside (owner 10-31); the tail (::after) stays outside the scroller */
.bubble-text {
  max-height: min(40vh, 260px); overflow-y: auto; overscroll-behavior: contain; touch-action: pan-y; white-space: pre-wrap;
  overflow-wrap: anywhere;
}
/* the tail points at Vincent's head even when the bubble is pushed inside the screen (--tail-dx, set in vincent-ui.js) */
.bubble::after {
  content: ""; position: absolute; left: calc(50% + var(--tail-dx, 0px)); bottom: -7px; margin-left: -7px; border: 7px solid transparent; border-bottom: 0;
  border-top-color: var(--cream);
}
body.ui-open #joystick { display: none; }

/* chat panel: right side on wide screens, bottom sheet on phones */
.chat {
  position: fixed; z-index: 4; right: max(16px, env(safe-area-inset-right)); top: 72px; bottom: 72px; width: min(400px, calc(100% - 32px));
  display: flex; flex-direction: column; background: var(--night-glass); border: 1px solid var(--line); border-radius: 16px;
  backdrop-filter: blur(10px); box-shadow: 0 18px 48px rgba(0, 0, 0, 0.45); overflow: hidden;
  animation: rise 220ms var(--ease);
}
@keyframes rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .chat { animation: none; } }
@media (max-width: 640px) {
  .chat { left: 0; right: 0; bottom: 0; top: auto; width: 100%; height: min(72vh, 620px); border-radius: 16px 16px 0 0; border-bottom: 0; }
  /* keyboard up (owner tg 5769 「가로 줄어들지 마」): the sheet keeps its full width; vincent-ui.js fitChat() sets --kb (the part of
     the layout viewport under the keyboard) and --vv-avail (the visible height less a gap), so only the height follows */
  .chat { bottom: var(--kb, 0px); height: min(72vh, 620px, var(--vv-avail, 100dvh)); }
  /* talking (owner tg 5774 「세로가 3줄 정도만 나오게 하고 크기를 키울 수 있게 해」): about three message lines above the turns
     line and the input (head 49 + log 113 + meta 32 + input row 66); pulling the head up makes it taller (--chat-h, fitChat()) */
  .chat { --chat-3l: 262px; }
  .chat.is-talk { height: min(var(--chat-h, var(--chat-3l)), var(--vv-avail, 100dvh)); }
  .chat.is-talk .chat-head { position: relative; touch-action: none; cursor: ns-resize; }
  .chat.is-talk .chat-head::before {
    content: ""; position: absolute; top: 5px; left: 50%; width: 36px; height: 4px; margin-left: -18px; border-radius: 2px;
    background: rgba(244, 236, 216, 0.35); pointer-events: none;
  }
  .chat.is-talk .chat-head button { cursor: pointer; }
  .chat.is-resizing { animation: none; user-select: none; }
}
.chat-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; padding: 12px 14px; border-bottom: 1px solid var(--line); }
.chat-head strong { font-family: var(--display); font-size: 1.3rem; margin-inline-end: 8px; white-space: nowrap; }  /* 「Vincent van Gogh」, one line (owner tg 5775) */
.chat-band { font-size: 0.75rem; color: var(--muted); overflow-wrap: normal; }
.chat-head-actions { display: flex; gap: 4px; }
.ghost { white-space: nowrap; flex-shrink: 0; background: none; border: 0; padding: 6px 8px; border-radius: 8px; color: var(--muted); font-size: 0.85rem; }
.ghost:hover, .ghost:focus-visible { color: var(--cream); background: rgba(244, 236, 216, 0.08); }
/* the account row (owner 10-31): logout, delete, channel, character as icons; words only in tooltips */
.chat-menu { display: flex; gap: 6px; padding: 6px 14px; border-bottom: 1px solid var(--line); }
.chat .chat-menu { display: none !important; }  /* owner tg 5774: the icon row is gone (its actions are in settings) */
.icon-btn {
  display: grid; place-items: center; width: 40px; height: 36px; flex: none; padding: 0; background: none;
  border: 1px solid var(--line); border-radius: 8px; color: var(--muted);
}
.icon-btn:hover, .icon-btn:focus-visible { color: var(--cream); background: rgba(244, 236, 216, 0.08); }
.chat-stage { flex: 1; min-height: 0; display: flex; flex-direction: column; }
.chat-body { flex: 1; min-height: 0; display: flex; flex-direction: column; }
/* the log and its 「새 답 ↓」 pill (vincent-ui grow(): shown when an answer arrives while the log is scrolled up) */
.chat-log-wrap { position: relative; flex: 1; min-height: 0; display: flex; flex-direction: column; }
.chat-new {
  position: absolute; left: 50%; bottom: 10px; transform: translateX(-50%); z-index: 1; padding: 6px 14px; min-height: 32px;
  border: 1px solid var(--wheat); border-radius: 999px; background: var(--night-2); color: var(--cream); font: inherit;
  font-size: 0.85rem; font-weight: 600; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4); cursor: pointer; white-space: nowrap;
}
.chat-new:hover, .chat-new:focus-visible { border-color: var(--cream); }
.chat-new[hidden] { display: none; }
/* who hears the next answer (owner tg 6388): 「전체 | 1:1」 left of ✕, in a room only */
.hear-seg { display: inline-flex; align-items: center; border: 1px solid var(--line); border-radius: 999px; padding: 2px; flex-shrink: 0; }
.hear-seg[hidden] { display: none; }
.hear-opt {
  background: none; border: 0; border-radius: 999px; padding: 4px 10px; min-height: 28px; color: var(--muted); font: inherit;
  font-size: 0.8rem; font-weight: 600; white-space: nowrap; cursor: pointer;
}
.hear-opt[aria-pressed="true"] { background: var(--cream); color: var(--night); }
.hear-opt:not([aria-pressed="true"]):hover, .hear-opt:focus-visible { color: var(--cream); }
.chat-head-actions { align-items: center; }
.chat-log { flex: 1; min-height: 0; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px;
  overscroll-behavior: contain; touch-action: pan-y; -webkit-overflow-scrolling: touch; }
.msg { margin: 0; padding: 10px 12px; border-radius: 12px; max-width: 88%; line-height: 1.6; font-size: 0.95rem; white-space: pre-wrap; }
.msg.me { align-self: flex-end; background: var(--night-2); }
.msg.vincent { align-self: flex-start; background: var(--cream); color: var(--night); }
.msg.vincent:empty::before { content: "…"; color: #6b7080; }
/* owner tg 6518: 「. .. …」 turning after the shown text while the rest of the answer is on its way (vincent-ui waitDots) */
.msg.vincent.waiting:empty::before { content: none; }
.msg.vincent.waiting::after { content: attr(data-dots); font-variant-ligatures: none; display: inline-block; min-width: 1.2em; margin-left: .2em; color: #6b7080; }
/* owner tg 6518: room under my question while the answer comes (vincent-ui pinQuestion) */
.chat-spacer { flex: none; pointer-events: none; }
.msg.support { align-self: stretch; max-width: none; background: var(--support); border: 1px solid rgba(244, 236, 216, 0.25); }
.msg.past { opacity: 0.8; }  /* earlier talk kept on this device (vincent-ui showHistory) */
.msg.system { align-self: center; background: none; color: var(--muted); font-size: 0.85rem; text-align: center; }
.source, .resource { display: block; margin-top: 6px; font-size: 0.78rem; opacity: 0.75; }
.resource { opacity: 1; font-weight: 600; }
.chat-help { margin: 0 14px; padding: 6px 10px; border-radius: 8px; background: var(--support); font-size: 0.82rem; }
.chat-status { margin: 6px 14px 0; font-size: 0.8rem; color: var(--muted); }
.chat-row { display: flex; gap: 8px; padding: 10px 14px; }
/* owner tg 6586: hold to talk, next to the input; red while recording */
.chat-mic {
  flex: none; width: 44px; height: 44px; display: grid; place-items: center; border-radius: 50%; border: 1px solid var(--line);
  background: transparent; color: var(--cream); touch-action: none; -webkit-user-select: none; user-select: none; -webkit-touch-callout: none;
}
.chat-mic svg { width: 20px; height: 20px; }
.chat-mic.on { border-color: #e0574f; color: #e0574f; background: rgba(224, 87, 79, 0.14); }
.chat-mic[hidden] { display: none; }
.chat-input {
  flex: 1; resize: none; font: inherit; font-size: 16px; color: var(--cream); background: rgba(244, 236, 216, 0.06);
  border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; max-height: 120px;
}
.chat-input:focus { outline: 2px solid var(--wheat); outline-offset: 1px; }
.chat-send, .primary {
  background: var(--wheat); color: var(--night); border: 0; border-radius: 10px; padding: 10px 16px; font-weight: 600;
  transition: filter 150ms ease;
}
.chat-send:hover, .primary:hover { filter: brightness(1.08); }
.chat-send:disabled, .primary:disabled { opacity: 0.5; cursor: not-allowed; }
/* owner tg 6637: the send button is a paper plane icon */
.chat-send { display: grid; place-items: center; flex: none; width: 48px; padding: 0; }
.chat-send svg { width: 22px; height: 22px; }
.chat-pass { margin: 0 14px 12px; background: none; border: 1px solid var(--wheat); color: var(--wheat); border-radius: 10px; padding: 8px; }
/* owner tg 6652: turns left + 「AI 대화 이용권」 in the head (turns on the left, the pass button next to 전체 | 1:1) */
.chat-head { flex-wrap: wrap; row-gap: 6px; }
.chat-head-meta { flex: 1; min-width: 0; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.chat-head-meta .chat-status { flex: 1; min-width: 0; margin: 0; }
.chat-head-meta .chat-pass { flex: none; margin: 0; padding: 4px 10px; font-size: 0.8rem; white-space: nowrap; }
@media (max-width: 640px) {
  .chat-head-actions { margin-inline-start: auto; }
  .chat-head-meta { order: 2; flex-basis: 100%; }
}
.note { padding: 20px 18px; display: grid; gap: 14px; align-content: start; overflow-y: auto; }
.note p { margin: 0; line-height: 1.7; }
.note .tag { justify-self: start; font-size: 0.75rem; padding: 2px 8px; border: 1px solid var(--wheat); border-radius: 999px; color: var(--wheat); }
.choices { display: flex; gap: 10px; flex-wrap: wrap; }
.choices button:not(.primary) { background: none; border: 1px solid var(--line); border-radius: 10px; padding: 10px 16px; }
/* login (owner 10-31): Google (its own button), Apple (black, Apple's text and logo), email code */
/* centred (owner tg 5768 「중앙정렬로」): the two lines, the three buttons as one equal-width column, the consent line */
.login > p { text-align: center; }
.login .login-choices { justify-self: center; }
.login .login-title { font-size: 1.05rem; font-weight: 600; }
.login-choices { display: grid; gap: 10px; justify-items: stretch; width: fit-content; min-width: min(100%, 280px); max-width: 100%; }
/* = the Google button width: 280 px, or wider when its text in the page language is longer (pl, vi: Google renders it at
   294-308 px) so Apple and email stretch to the same width */
.login-choices .gsi-slot { min-height: 44px; display: flex; justify-content: center; }
.login .apple, .login .email-login {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; border-radius: 999px;
  padding: 0 18px; font: inherit; font-size: 0.95rem; font-weight: 600; transition: filter 150ms ease;
}
.login .apple { background: #000; color: #fff; border: 1px solid rgba(255, 255, 255, 0.35); font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif; }
.login .apple[hidden], .login .email-login[hidden] { display: none; }
.login .email-login { background: none; color: var(--cream); border: 1px solid var(--line); }
.login .apple:hover, .login .email-login:hover { filter: brightness(1.15); }
.login .apple:focus-visible, .login .email-login:focus-visible { outline: 2px solid var(--wheat); outline-offset: 2px; }
.login-form { display: grid; gap: 10px; }
.login-label { display: grid; gap: 6px; font-size: 0.85rem; color: var(--muted); }
.login-input {
  font: inherit; font-size: 16px; color: var(--cream); background: rgba(244, 236, 216, 0.06); border: 1px solid var(--line);
  border-radius: 10px; padding: 10px 12px; width: 100%; box-sizing: border-box;
}
.login-input:focus { outline: 2px solid var(--wheat); outline-offset: 1px; }
.login-input.code { letter-spacing: 0.4em; font-variant-numeric: tabular-nums; text-align: center; font-size: 1.25rem; }
.login-msg:empty { display: none; }
.login-msg { color: var(--wheat); font-size: 0.85rem; }
.login-sent { overflow-wrap: anywhere; }
.login-row { display: flex; flex-wrap: wrap; gap: 4px 8px; justify-content: space-between; }
.login-back, .login-resend, .login-row .ghost { justify-self: start; padding-left: 0; padding-right: 0; }
.login-resend:disabled { opacity: 0.7; cursor: default; }
.login-consent a { color: var(--wheat); }
/* no last line of one character (ja PC left 「す」 alone, 11b): text-wrap pretty evens the last lines where supported. ja / zh have
   no spaces, so the page-wide keep-all made the whole phrase one unbreakable word that overflow-wrap cut at the edge; here they
   break normally (ja by phrase where word-break: auto-phrase exists). Without pretty / auto-phrase: plain wrapping. */
.login-consent { text-wrap: pretty; }
:root:lang(ja) .login-consent, :root:lang(zh) .login-consent { word-break: normal; line-break: strict; }
@supports (word-break: auto-phrase) { :root:lang(ja) .login-consent { word-break: auto-phrase; } }
.turnstile-slot[hidden] { display: none; }
.muted { color: var(--muted); font-size: 0.85rem; }
/* member numbers and 「계정 연결」 (owner tg 8330-8349): 「비로그인으로 계속하기」 on top, the Google / Apple / email column under
   「계정 연결하기 (구글·이메일)」, 「회원 번호로 이어하기」 as one field + button row; all one centred column like the sign-in buttons */
.login .member-guest, .login .member-sub, .login .member-resume-wrap, .login .member-later, .login .member-warn {
  justify-self: center; width: min(100%, 300px);
}
.login .member-guest { min-height: 44px; border-radius: 999px; font-size: 0.95rem; }
.login .member-sub { margin-top: 4px; font-size: 0.8rem; color: var(--muted); text-align: center; }
.login .member-later, .login .member-link-now { justify-self: center; }
.member-resume-wrap { padding-top: 12px; border-top: 1px solid var(--line); }
.member-resume { display: grid; gap: 8px; }
.member-resume-label { text-align: center; }
.member-row { display: flex; gap: 8px; }
.member-row .member-input { flex: 1; min-width: 0; padding: 10px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  letter-spacing: 0; text-align: center; }
.member-row .member-input::placeholder { font-size: 14px; }
.member-resume .login-msg, .login-form .login-msg { white-space: pre-line; }
.member-row .member-go { flex: none; border-radius: 10px; }
.member-confirm { display: grid; gap: 10px; }
.member-confirm p { margin: 0; line-height: 1.6; }
.member-target { text-align: center; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 1rem; letter-spacing: 0.06em; }
.member-confirm-row { display: flex; gap: 8px; align-items: center; }
.member-card { display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 6px 10px; padding: 12px 14px;
  border: 1px dashed var(--wheat); border-radius: 12px; background: rgba(232, 185, 58, 0.06); }
.member-no { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 1.12rem; font-weight: 600; letter-spacing: 0.08em;
  color: var(--cream); white-space: nowrap; user-select: all; }
.member-copy { background: none; border: 1px solid var(--line); color: var(--cream); border-radius: 999px; padding: 4px 12px; font: inherit;
  font-size: 0.82rem; cursor: pointer; }
.member-copy:hover, .member-copy:focus-visible { border-color: var(--wheat); color: var(--wheat); }
.member-copied { flex-basis: 100%; text-align: center; font-size: 0.78rem; color: var(--wheat); }
.member-copied:empty { display: none; }
.member-warn { margin: 0; padding: 8px 12px; border-radius: 10px; background: rgba(224, 87, 79, 0.12); color: #f2b8b3; font-size: 0.85rem;
  line-height: 1.6; text-align: center; }
.member-copy-area { position: fixed; top: 0; left: 0; opacity: 0; pointer-events: none; }
.vgs-member { display: grid; gap: 10px; }
/* the 「계정 연결」 sheet holds more than the talk view: taller on phones (still under the visible height, vincent-ui.js fitChat) */
@media (max-width: 640px) {
  .chat.is-login { height: min(88vh, 700px, var(--vv-avail, 100dvh)); }
}

/* sheet: business info, AI 대화 이용권 */
/* owner tg 6221: on iPhone 100vh is the toolbar-hidden height, taller than what is visible, so the centred box ran under the
   browser bar and its last lines (사업자 정보) could not be scrolled into view; the box now fits the visible height (dvh) */
.sheet { position: fixed; inset: 0; z-index: var(--z-modal); background: rgba(5, 9, 20, 0.55); display: grid; place-items: center;
  padding: max(16px, env(safe-area-inset-top)) 16px max(16px, env(safe-area-inset-bottom)); }
.sheet-box {
  position: relative; width: min(460px, 100%); max-height: calc(100vh - 32px); overflow-y: auto; background: var(--night);
  max-height: calc(100dvh - max(16px, env(safe-area-inset-top)) - max(16px, env(safe-area-inset-bottom)));
  overscroll-behavior: contain; touch-action: pan-y; -webkit-overflow-scrolling: touch;
  border: 1px solid var(--line); border-radius: 16px; padding: 22px 20px; box-shadow: 0 18px 48px rgba(0, 0, 0, 0.5);
}
.sheet-close { position: absolute; top: 10px; right: 10px; }
.sheet h2 { font-family: var(--display); font-weight: 500; color: var(--wheat); font-size: 1.6rem; margin: 0 0 10px; }
.purchase > p { margin: 0 0 10px; line-height: 1.6; }
.offers { display: grid; gap: 10px; margin: 14px 0; }
.offer { display: grid; grid-template-columns: 1fr auto auto; gap: 12px; align-items: center; padding: 12px; border: 1px solid var(--line); border-radius: 12px; }
@media (max-width: 400px) { .offer { grid-template-columns: 1fr auto; } .offer button { grid-column: 1 / -1; } }
.biz { margin-top: 18px; padding-top: 14px; border-top: 1px solid var(--line); }
.purchase .biz h2 { font-size: 1.15rem; }
.biz dl { display: grid; grid-template-columns: max-content 1fr; gap: 4px 12px; margin: 0 0 12px; font-size: 0.82rem; }
.biz dt { color: var(--muted); }
.biz dd { margin: 0; overflow-wrap: anywhere; }
.biz dd .biz-en { display: block; margin-top: 1px; color: var(--muted); font-size: 0.95em; line-height: 1.45; overflow-wrap: break-word; text-wrap: pretty; }
.refund ul { margin: 0 0 6px; padding-left: 18px; font-size: 0.82rem; line-height: 1.6; }
.legal-links { font-size: 0.85rem; margin: 8px 0 0; }

/* letter panel: a right-side column above the Steam notice (desktop); below the logo down to the Steam notice (phones) */
:root { --quote-font: var(--body); --paper: rgba(246, 239, 222, 0.9); --ink: #2b2417;
  /* phones: just under the logo (top margin + 40vw wide x 403/800 tall + a margin) */
  --logo-bottom: calc(max(2.5vw, 12px, env(safe-area-inset-top)) + 60vw * 403 / 800 + 12px); }
.letter {
  position: fixed; z-index: 1; right: max(16px, env(safe-area-inset-right)); bottom: var(--letter-bottom);
  width: min(400px, 32vw); height: var(--letter-h); max-height: calc(100dvh - var(--letter-bottom) - 16px);
  /* owner tg 5518: no paper, no border; dark text with a white glow so it reads on any wall or floor */
  display: flex; flex-direction: column; padding-top: 38px; background: transparent; color: var(--ink); border: 0;
  box-shadow: none; opacity: 0; transform: translateY(8px); pointer-events: none;
  transition: opacity 800ms ease, transform 800ms ease;
}
.letter.on { opacity: 1; transform: none; pointer-events: auto; }
@media (prefers-reduced-motion: reduce) { .letter { transition: none; } }
.letter-close {
  position: absolute; top: 4px; right: 6px; width: 32px; height: 32px; border: 0; background: none; color: var(--ink);
  font-size: 1.3rem; line-height: 1; border-radius: 50%; opacity: 0.85;
  text-shadow: 0 0 2px #fff, 0 0 6px rgba(255, 255, 255, 0.9), 0 0 12px rgba(255, 255, 255, 0.7);
}
.letter-close:hover, .letter-close:focus-visible { opacity: 1; background: rgba(43, 36, 23, 0.08); }
.letter-body { flex: 1; min-height: 0; overflow-y: auto; padding: 2px 22px 16px;
  --glow: 0 0 2px #fff, 0 0 6px rgba(255, 255, 255, 0.9), 0 0 12px rgba(255, 255, 255, 0.7); text-shadow: var(--glow);
  /* soft fade at the top edge only once scrolled down (.fade-top), at the bottom only while more text is below (.fade-bottom);
     letters-panel.js sets both, so "Dear Theo," is never faded at the top of a letter */
  --fade-top: 0px; --fade-bottom: 0px;
  -webkit-mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent);
  mask-image: linear-gradient(to bottom, transparent, #000 var(--fade-top), #000 calc(100% - var(--fade-bottom)), transparent); font-family: var(--quote-font); font-size: 0.92rem; line-height: 1.7; }
.letter-body.fade-top { --fade-top: 18px; }
.letter-body.fade-bottom { --fade-bottom: 22px; }
.letter-body p { margin: 0 0 10px; }
.letter-sign { text-align: right; font-style: italic; opacity: 0.85; margin-top: 14px !important; }
/* below 768px: the letter panel above the badge, full width, 40dvh (never above the logo) */
@media (max-width: 767.98px) {
  .letter { left: 16px; right: 16px; width: auto; top: auto; bottom: var(--letter-bottom); height: var(--letter-h); max-height: none; }
  .letter-body { flex: 1; min-height: 0; font-size: 0.88rem; padding: 2px 16px 12px; }
}
/* tone (letters-panel.js measures the scene behind the panel): on a dark backdrop (the night village) cream text with a dark
   shadow; on a light one (the studio) the dark ink with the white glow above. 300 ms between the two. */
.letter { isolation: isolate; }
.letter, .letter-close { transition: opacity 800ms ease, transform 800ms ease, color 300ms ease, text-shadow 300ms ease; }
.letter-body { transition: text-shadow 300ms ease; }
.letter.dark { --ink-dark: #f3ead2; color: var(--ink-dark); }
.letter.dark .letter-close { color: var(--ink-dark);
  text-shadow: 0 0 2px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.8), 0 0 12px rgba(0, 0, 0, 0.6); }
.letter.dark .letter-close:hover, .letter.dark .letter-close:focus-visible { background: rgba(243, 234, 210, 0.12); }
.letter.dark .letter-body { --glow: 0 0 2px rgba(0, 0, 0, 0.9), 0 0 6px rgba(0, 0, 0, 0.8), 0 0 12px rgba(0, 0, 0, 0.6); }
@media (prefers-reduced-motion: reduce) { .letter, .letter-close, .letter-body { transition: none; } }
/* phones: over the village and the cypress a faint dark wash behind the panel only, in the dark tone */
@media (max-width: 767.98px) {
  .letter::before { content: ''; position: absolute; inset: 0; z-index: -1; pointer-events: none; border-radius: 6px;
    background: linear-gradient(to bottom, rgba(10, 15, 30, 0), rgba(10, 15, 30, 0.35)); opacity: 0; transition: opacity 300ms ease; }
  .letter.dark::before { opacity: 1; }
}

.offer .vat { font-weight: 400; font-size: 0.75rem; color: var(--muted); }

.purchase .unit { font-weight: 600; }
.trademark { font-size: 0.72rem; margin-top: 10px; }

/* map <-> studio: a short fade to the night colour while the other place is put in place */
#fade { position: fixed; inset: 0; background: #05080f; opacity: 0; pointer-events: none; transition: opacity 0.4s ease; z-index: var(--z-fade); }
#fade.on { opacity: 1; }

/* ---------- batch 10-31 (owner): Float loading screen, chrome gone after the first step, a quieter joystick, tap-to-walk ring,
   phone margins */
/* loading (owner 「로딩 때 플로트 로고 나오게 해 줘」): the Float wordmark (brand file, unchanged; its own transparent margin is the
   clear space) centred on night navy, a thin byte bar under it; fades 0.4 s once a frame is drawn, at once with reduced motion */
/* owner 10-31 tg 5726 「배경엔 반고흐 붓터치 어울리는 색으로 하나 찾아서 깔고 캔버스 그림을 올려」: the Starry Night's swirling
   sky (public-domain scan, blurred and darkened, loading-bg.webp) behind his canvas, which hangs with a soft shadow */
#loading { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; z-index: var(--z-loading);
  background: #0b1424 url("loading-bg.webp") center / cover no-repeat; }
/* owner 10-31 tg 5724 「내가 그린 거… 로딩에 이거 쓰라고 해」: his painted canvas as it is (no filter), small */
#loading .loading-logo { display: block; width: clamp(96px, 20vmin, 150px); height: auto; border-radius: 2px; box-shadow: 4px 10px 22px rgba(0, 0, 0, 0.55); }
#loading .track { width: min(200px, calc(100% - 96px)); height: 2px; border-radius: 1px; background: rgba(244, 234, 210, 0.14); }
#loading .fill { background: rgba(244, 234, 210, 0.8); }
@media (prefers-reduced-motion: reduce) { #loading { transition: none; } }
/* loading tips (owner tg 6891/6892/6894, loading-tips.js): one line under the bar, two lines' height kept so a wrapped tip never
   moves anything; fixed outside #loading (screen readers skip a progressbar's children) at the bar's place: the logo + gap + bar
   block is centred, so its bottom is 50% + (logo + 22px) / 2. Hidden with the loading screen, same 0.4 s fade. */
.loading-tip {
  --tip-logo: clamp(96px, 20vmin, 150px);
  position: fixed; left: 0; right: 0; top: calc(50% + (var(--tip-logo) + 22px) / 2 + 22px); z-index: calc(var(--z-loading) + 1);
  width: min(440px, calc(100% - 48px)); height: 3em; margin: 0 auto; overflow: hidden;
  display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2;
  font: 400 0.875rem/1.5 var(--body); color: rgba(244, 234, 210, 0.78); text-align: center; letter-spacing: 0.01em;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6); pointer-events: none; transition: opacity 300ms ease;
}
.loading-tip.out { opacity: 0; }
/* the house panel's sign-in (vincent-ui loginReason 'house'): no talk-only controls in the head */
.login-house :is(.hear-seg, .chat-head-meta) { display: none !important; }
.loading-tip:empty { visibility: hidden; }
#loading.done ~ .loading-tip { opacity: 0; visibility: hidden; transition: opacity 400ms ease, visibility 0s linear 400ms; }
@media (prefers-reduced-motion: reduce) { .loading-tip, #loading.done ~ .loading-tip { transition: none; } }
/* owner tg 7519: the bar has not moved for 10 s -> a line and a refresh button under the tip (loading-tips.js) */
.loading-stall {
  position: fixed; left: 0; right: 0; top: calc(50% + (clamp(96px, 20vmin, 150px) + 22px) / 2 + 22px + 3em + 18px);
  /* 11/02 (controller): on short or narrow phones the button ran into © Float at the bottom; never lower than this */
  top: min(calc(50% + (clamp(96px, 20vmin, 150px) + 22px) / 2 + 22px + 3em + 18px), calc(100dvh - 128px - env(safe-area-inset-bottom)));
  z-index: calc(var(--z-loading) + 1); width: min(440px, calc(100% - 48px)); margin: 0 auto;
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  font: 400 0.8125rem/1.45 var(--body); color: rgba(244, 234, 210, 0.86); text-align: center; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.6);
}
.loading-stall[hidden], #loading.done ~ .loading-stall { display: none; }
.loading-stall button {
  appearance: none; min-height: 36px; padding: 0 18px; border-radius: 999px; cursor: pointer;
  border: 1px solid rgba(244, 234, 210, 0.42); background: rgba(244, 234, 210, 0.1); color: #f4ead2; font: 600 0.875rem/1 var(--body);
}
.loading-stall button:hover { border-color: #e9c46a; }
.loading-stall button:focus-visible { outline: 2px solid #e9c46a; outline-offset: 2px; }
/* the homepage chrome only once the loading screen has gone */
body:has(#loading:not(.done)) :is(.ov-logo, .ov-steam-wrap, .ov-copy, .letter, #joystick, .vg-gear) { visibility: hidden; }

/* owner 「한 번 움직이면 로고나 편지 같은 건 다시 안 뜸」: the first step (letters-panel.js sets body.is-walking, memory only) fades the
   game logo, the Steam badge and © Float with the letter, for the rest of the visit */
.ov-logo, .ov-steam-wrap, .ov-copy { transition: opacity 600ms ease; }
body.is-walking :is(.ov-logo, .ov-steam-wrap, .ov-copy) { opacity: 0; pointer-events: none; }
@media (prefers-reduced-motion: reduce) { .ov-logo, .ov-steam-wrap, .ov-copy { transition: none; } }

/* joystick (owner 「컨트롤러 투명도를 내려 줘」): 0.2 at rest, 0.45 under a finger */
#joystick { opacity: 0.2; transition: opacity 150ms ease; }
#joystick.active { opacity: 0.45; }
/* owner tg 6504 → tg 6993: 3 s unused (main.js STICK_IDLE_MS) it fades out; a touch on its spot brings it back at once (no fade-in delay) */
#joystick.idle:not(.active) { opacity: 0; transition: opacity 600ms ease; }
@media (prefers-reduced-motion: reduce) { #joystick.idle:not(.active) { transition: none; } }

/* tap-to-walk target (tapmove.js): a thin ring for a moment, screen UI only */
.tap-ring { position: fixed; left: 0; top: 0; z-index: 2; width: 26px; height: 26px; border-radius: 50%; pointer-events: none;
  border: 1.5px solid rgba(244, 234, 210, 0.85); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.25); opacity: 0; }
.tap-ring.on { animation: tap-ring 1400ms ease-out forwards; }
@keyframes tap-ring { 0% { opacity: 0.95; scale: 0.6; } 25% { opacity: 0.9; scale: 1; } 100% { opacity: 0; scale: 1.15; } }
@media (prefers-reduced-motion: reduce) { .tap-ring.on { animation: none; opacity: 0.85; } }

/* phones (owner 「모바일에서 로고 상단이랑 좌 여백 좀 더 주고, 스팀 버튼 조금 위로 올려 줘」): logo margins 20px plus the safe area;
   the Steam badge (and the letter above it) 18px higher (16 left © Float 11px below it); with a joystick the joystick goes up the same 18px so the bottoms stay
   aligned */
@media (max-width: 767.98px) {
  /* owner tg 8324/8325/8327: the logo starts just under the top icon row (which now also holds the seed chip and the bag), so the
     two never overlap; was max(2.5vw, 20px) + the safe area. --vg-top-row is the row's top (settings.css), its discs end 35 px lower */
  :root { --logo-top: calc(var(--vg-top-row, 10px) + 42px); --logo-left: calc(max(2.5vw, 20px) + env(safe-area-inset-left));
    --logo-bottom: calc(var(--logo-top) + 60vw * 403 / 800 + 12px);
    --block-bottom: max(52px, calc(env(safe-area-inset-bottom) + 46px)); }
  .ov-logo { top: var(--logo-top); left: var(--logo-left); }
  body.touch { --block-bottom: calc(max(28px, env(safe-area-inset-bottom)) + 18px);
    --letter-bottom: calc(max(28px, env(safe-area-inset-bottom)) + 18px + 120px + 8px); }
  body.touch #joystick { bottom: calc(max(28px, env(safe-area-inset-bottom)) + 18px); }
  /* the talk button (near Vincent) stays above the raised badge */
  .near { bottom: calc(var(--block-bottom) + var(--badge-h) + 12px); }
}

/* portrait phones, touch (owner 10-31 「겹치거나 잘리는 거 없게」): with a home bar (safe-area bottom > 10px) the badge and joystick
   sit 36px above it so © Float below fits (unchanged without a home bar: 46px, as tg 5515); the talk button near Vincent goes
   right of the joystick instead of across it, wrapping its text if a language is long */
@media (max-width: 767.98px) and (orientation: portrait) {
  body.touch { --block-bottom: max(46px, calc(env(safe-area-inset-bottom) + 36px));
    --letter-bottom: calc(max(46px, calc(env(safe-area-inset-bottom) + 36px)) + 120px + 8px); }
  body.touch #joystick { bottom: max(46px, calc(env(safe-area-inset-bottom) + 36px)); }
  body.touch .near { left: auto; right: max(16px, env(safe-area-inset-right)); transform: none;
    max-width: calc(100vw - max(24px, env(safe-area-inset-left)) - 120px - 12px - max(16px, env(safe-area-inset-right))); }
  body.touch .near:has(> .near-main) { align-items: flex-end; }
}

/* phones with the joystick (owner tg 5767 「컨트롤러의 세로 중심이랑 버튼이 맞도록」): the talk button row (and the mic beside it)
   is centred on the joystick's vertical centre. --joy-bottom repeats the joystick's own bottom for each case above (the joystick
   is not moved); the row's centre = that + 60px (half of the 120px stick), translateY(50%) puts its centre on that line however
   tall it wraps. The Steam badge on the joystick's baseline steps aside while the button is up so the two never overlap. */
@media (max-width: 640px) {
  body.touch { --joy-bottom: calc(max(28px, env(safe-area-inset-bottom)) + 18px); }
  body.touch .near { left: auto; right: max(16px, env(safe-area-inset-right)); bottom: calc(var(--joy-bottom) + 60px);
    transform: translateY(50%);
    max-width: calc(100vw - max(24px, env(safe-area-inset-left)) - 120px - 12px - max(16px, env(safe-area-inset-right))); }
  body.touch .near:has(> .near-main) { align-items: flex-end; }
  /* at once, not with the 600 ms fade: the talk button appears instantly and would sit over a fading badge */
  body.touch:has(.near:not([hidden])) .ov-steam-wrap { opacity: 0; pointer-events: none; transition: none; }
}
@media (max-width: 640px) and (orientation: portrait) {
  body.touch { --joy-bottom: max(46px, calc(env(safe-area-inset-bottom) + 36px)); }
}
/* phones, the 함께 걷기 chat window open (owner tg 6559 「기본 높이를 컨트롤러랑 같게 하고 인터랙션 키는 그 위에 나오게」): the window
   holds the joystick's band on the right (multi/ui.js), so the talk row (talk, mic, 「앉기」) sits right above it, 8px clear, and
   follows it up when it is resized taller (multi/ui.js keeps 120px free for the row under the top band). Closed: as above. */
@media (max-width: 640px) {
  body.touch.vgm-chat-open .near { left: auto; right: max(16px, env(safe-area-inset-right)); transform: none;
    bottom: calc(var(--vgm-panel-bottom) + var(--vgm-chat-hh) + 8px); max-width: calc(100vw - 32px); }
}

/* ---------- phones turned sideways (owner 10-31 「모바일 옆으로 돌릴 수도 있게 해 줘」): height <= 500px in landscape.
   The page follows the device (no orientation lock). Desktop-like: a small logo top left (25% of the height), the letter (55% of
   the height) and the Steam badge in the right column, the joystick bottom left clear of the notch and the home bar, © Float
   bottom centre, the talk button just above it. Every value is fixed per layout, so rotating swaps it in one frame (no
   transition on position or size). Placed last so it wins over the portrait phone rules (max-width: 767.98px) as well.
   Only wider than 640px (owner tg 5769 「왜 굳이 작아지는 거야? 가로 줄어들지 마」): a portrait phone with the keyboard up is
   also short and "landscape", and must keep the full-width phone sheet, never this right-side column. */
@media (orientation: landscape) and (max-height: 500px) and (min-width: 641px) {
  :root, body.touch {
    --edge-l: max(16px, calc(env(safe-area-inset-left) + 8px));
    --edge-r: max(16px, calc(env(safe-area-inset-right) + 8px));
    --block-bottom: max(12px, env(safe-area-inset-bottom));
    --badge-h: 52px;
    --letter-bottom: calc(var(--block-bottom) + var(--badge-h) + 8px);
    --letter-h: min(55vh, calc(100vh - var(--letter-bottom) - 12px));
    --letter-h: min(55dvh, calc(100dvh - var(--letter-bottom) - 12px));
  }
  .ov-logo { top: calc(var(--vg-top-row, 10px) + 42px); left: var(--edge-l); } /* owner tg 8364: under the top row (was max(12px, safe area)) */
  .ov-logo img, body .ov-logo img { width: auto; height: 25vh; height: 25dvh; }
  .ov-steam-wrap, body.touch .ov-steam-wrap { left: var(--edge-l); right: auto; bottom: var(--block-bottom); max-width: calc(50vw - 60px); } /* tg 8391: left */
  .ov-steam { gap: 9px; padding: 6px 12px 6px 8px; }
  .steam-symbol { height: 36px; }
  .steam-wordmark { height: 18px; }
  .steam-soon { font-size: 0.62rem; }
  .letter { left: auto; right: var(--edge-r); top: auto; bottom: var(--letter-bottom); width: min(400px, 34vw); height: var(--letter-h);
    max-height: none; padding-top: 34px; }
  .letter-body { font-size: 0.86rem; padding: 2px 16px 12px; }
  #joystick, body.touch #joystick { left: max(24px, calc(env(safe-area-inset-left) + 12px)); bottom: max(16px, calc(env(safe-area-inset-bottom) + 6px)); }
  .ov-copy, #loading .loading-copy { bottom: max(6px, env(safe-area-inset-bottom)); }
  .near { left: 40%; bottom: calc(max(6px, env(safe-area-inset-bottom)) + 40px); }  /* between the joystick and the letter */
  .chat { top: max(12px, env(safe-area-inset-top)); bottom: max(12px, env(safe-area-inset-bottom)); right: var(--edge-r); left: auto;
    width: min(400px, 50vw); height: auto; border-radius: 16px; border-bottom: 1px solid var(--line); }
  .vg-gear { top: max(12px, env(safe-area-inset-top)); right: var(--edge-r); }
  body:has(.chat:not([hidden])) .vg-gear { visibility: hidden; }  /* the chat column covers the gear's corner; × closes it */
}

/* multiplayer room: 「전체 들리게 / 나만 들리게」 above the chat log (vincent-ui roomTurn) */
.chat-hear { display: flex; gap: 6px; padding: 8px 14px 0; }
.chat-hear button { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; white-space: nowrap; }
.chat-hear button svg { width: 17px; height: 17px; flex: none; }
.chat-hear button[aria-pressed="true"] { background: rgba(232, 185, 58, .2); border-color: #e8b93a; }
.msg.vincent.room { opacity: .85; font-size: .95em; }
/* owner tg 6577: another walker's 「전체」 question, with their name */
.msg.other { align-self: flex-start; background: rgba(245, 236, 215, .14); color: var(--cream); font-size: .95em; }

/* Toss purchase (wip/web-toss): the on-screen receipt notice after a payment */
.receipt-facts { display: grid; grid-template-columns: max-content 1fr; gap: 6px 14px; margin: 4px 0 14px; }
.receipt-facts dt { color: var(--muted); }
.receipt-facts dd { margin: 0; overflow-wrap: anywhere; }
.receipt-facts .receipt-order-no { white-space: nowrap; font-size: 0.9em; font-variant-numeric: tabular-nums; }
.receipt-facts .receipt-tax { display: block; font-size: 0.75rem; color: var(--muted); }

/* 함께 걷기 / 혼자 걷기 (owner 10-31): two choices with their head counts (「함께 걷기 12 | 혼자 걷기 5」), the picked one's
   line underneath, then 「들어가기」. Opens on arriving in the studio. */
/* below the purchase / receipt sheet (.sheet, z 5): coming back from a payment the receipt shows on top (live 11-01 E2E) */
.start-choice {
  position: fixed; z-index: 4; left: 0; right: 0; margin-inline: auto; bottom: calc(var(--block-bottom) + var(--badge-h) + 24px);
  width: min(420px, calc(100% - 32px)); padding: 10px; display: grid; gap: 10px; background: var(--night-glass);
  border: 1px solid var(--line); border-radius: 16px; backdrop-filter: blur(8px); box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4);
  animation: rise 220ms var(--ease);
}
.start-row { display: flex; gap: 8px; }
.start-btn {
  flex: 1 1 0; min-height: 52px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; padding: 8px 10px;
  background: none; border: 1px solid var(--line); border-radius: 12px; color: var(--cream); font-size: 1rem; white-space: nowrap;
}
.start-btn[aria-pressed="true"] { border-color: var(--wheat); box-shadow: inset 0 0 0 1px var(--wheat); background: rgba(231, 181, 74, 0.1); }
.start-btn:hover, .start-btn:focus-visible { background: rgba(244, 236, 216, 0.08); outline: none; }
.start-n { min-width: 1ch; font-variant-numeric: tabular-nums; color: var(--wheat); font-size: 0.9rem; }
.start-about { margin: 0 4px; min-height: 2.9em; font-size: 0.86rem; line-height: 1.45; color: var(--muted); word-break: keep-all; }
.start-enter {
  min-height: 48px; border: 0; border-radius: 12px; background: var(--wheat); color: var(--night); font-size: 1rem; font-weight: 600;
}
.start-enter:hover, .start-enter:focus-visible { filter: brightness(1.06); outline: 2px solid var(--cream); outline-offset: 2px; }
body.pre-start .vg-gear, body.pre-start #joystick { visibility: hidden; }
/* the chat (its login view too, opened from settings while the choice is up) has the same z 4 and came out under the choice
   box: while the chat is open the choice steps out of the way and comes back as it was when the chat closes (11b) */
.chat:not([hidden]) ~ .start-choice, body:has(> .chat:not([hidden])) > .start-choice { visibility: hidden; }

/* phones (owner 10-31 「모바일에선 글자 크기 좀 줄여」 + his receipt shot cut off on the right): chat text ~14px, small ~12px, the
   receipt and business facts stacked label-over-value and wrapping, 16px inside the sheet; buttons keep their 44px targets.
   The chat input stays 16px: iOS zooms the page into any field under 16px. */
.receipt-order { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.receipt-copy { min-height: 32px; padding: 2px 10px; border: 1px solid var(--line); border-radius: 8px; background: none; color: var(--cream); font-size: 0.78rem; }
@media (max-width: 640px) {
  .msg { font-size: 14px; line-height: 1.55; }
  .msg.system, .chat-status, .chat-head-meta .chat-pass, .source, .resource { font-size: 12px; }
  .chat-head strong { font-size: clamp(1rem, 4.4vw, 1.15rem); }
  .chat-send { font-size: 14px; }
  .sheet-box { padding: 16px; }
  .sheet h2 { font-size: 1.35rem; }
  .purchase > p, .offer { font-size: 14px; }
  .receipt-facts, .biz dl { grid-template-columns: 1fr; gap: 2px 0; font-size: 14px; }
  .receipt-facts dd, .biz dd { margin: 0 0 8px; overflow-wrap: anywhere; word-break: break-word; }
  .receipt-facts .receipt-order-no { white-space: normal; overflow-wrap: anywhere;
    font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
  .receipt-facts .receipt-tax, .offer .vat, .refund ul, .legal-links, .muted { font-size: 12px; }
}

/* SEO heading + summary for crawlers and screen readers only: no box, no layout, not focusable */
.sr-only { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* one-time privacy policy notice (privacy-notice.js, controller 11-01): centred just above the talk row band (.near: bottom
   max(84px, safe + 72px), talk button + 「앉기」 row ≈ 100 px), so it clears the logo and the channel badge (top left), the gear
   (top right), the joystick (bottom left) and the room chat button (bottom right); under the chat panel (z 4) and the sheets */
.vg-policy {
  position: fixed; z-index: 3; bottom: calc(max(84px, env(safe-area-inset-bottom) + 72px) + 112px);
  left: 0; right: 0; margin-inline: auto; width: min(460px, calc(100% - 2 * var(--gutter)));
  display: flex; align-items: flex-start; gap: 6px; padding: 12px 6px 12px 14px; border-radius: 14px;
  background: var(--night-glass); border: 1px solid var(--line); color: var(--cream);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.4); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
  -webkit-user-select: text; user-select: text; touch-action: pan-x pan-y; animation: vg-policy-in 360ms var(--ease) both;
}
.vg-policy-text { margin: 0; flex: 1; font-size: 13px; line-height: 1.55; }
.vg-policy-more { white-space: nowrap; font-weight: 600; color: var(--wheat); text-underline-offset: 3px; }
.vg-policy-x {
  flex: none; width: 32px; height: 32px; margin: -6px 0 0; display: grid; place-items: center; border: 0; border-radius: 50%;
  background: none; color: var(--muted); font-size: 20px; line-height: 1;
}
.vg-policy-x:hover { color: var(--wheat); background: rgba(244, 236, 216, 0.08); }
.vg-policy-x:focus-visible, .vg-policy-more:focus-visible { outline: 2px solid var(--wheat); outline-offset: 2px; }
body:has(#loading:not(.done)) .vg-policy { visibility: hidden; }
/* live 11-01: low on the screen it took the taps meant for 「빈센트와 이야기하기」: it steps aside while the talk row or a chat
   window shows and comes back after (still until closed) */
body:has(.near:not([hidden])) .vg-policy, body:has(.chat:not([hidden])) .vg-policy { display: none; }
@keyframes vg-policy-in { from { opacity: 0; transform: translateY(-6px); } to { opacity: 1; transform: none; } }
@media (prefers-reduced-motion: reduce) { .vg-policy { animation: none; } }

/* actions (owner tg 6766/6777, multi/emotes.js): on phones the action row, the jump button and the chat hold the right column
   above the chat button; the talk row (talk, mic, 「앉기」) goes right above them. emotes.js measures their top into --vgm-act-top. */
@media (max-width: 640px) {
  body.touch.vgm-act-on .near { left: auto; right: max(16px, env(safe-area-inset-right)); transform: none;
    bottom: calc(var(--vgm-act-top, 120px) + 8px); }
}

/* 「앉기」·「눕기」·talk row above the action row (bed agent 11-01: on PC 1440 the sit button sat under the row's key 4 and a tap
   hit the action). Desktop: lifted clear of the row; every size: drawn above it so a tap never lands on an action by mistake. */
body:has(.vgm-emotes:not([hidden])) .near { z-index: 6; }
@media (pointer: fine) {
  body:has(.vgm-emotes:not([hidden])) .near { bottom: max(140px, calc(env(safe-area-inset-bottom) + 128px)); }
}

/* Settings and modals on top (owner tg 7180): while the settings sheet, a character / channel dialog or the pass sheet is open,
   the floating panels (house panel 「내 집 · 남은 시간 · 방명록」, room chat, Vincent's talk panel, notices, the prop move bar,
   action row, joystick …) step out of the way instead of showing through the dimmed backdrop. They come back as it closes. */
body:has(> .vgs, .vgm-modal, > .sheet:not([hidden]):not(.vgshop-sheet)) :is(.vgh-label, .vghp-bar, .vgm-chat, .vgm-emotes, .vgm-emote-sheet,
  .vgm-jump, .vgm-float, .vgm-wait-card, .vgm-server, .vgm-paint, .vgm-paint-mine, .near, .bubble, .chat, .letter, .vg-policy,
  .start-choice, .tap-ring, #joystick, .vg-hud) { visibility: hidden !important; }

/* No double-tap zoom on phones (owner tg 7182/7184; index.html has maximum-scale=1, user-scalable=no and the iOS guard). Every
   control: manipulation (taps, scrolling and the browser's own pinch stay, the double-tap zoom goes). The view canvas: none,
   stricter than manipulation, so the browser never takes a one- or two-finger gesture from the game (look drag, tap-to-walk,
   the pinch that switches the view, main.js). */
#view { touch-action: none; }
:where(button, a, input, select, textarea, label, summary, [role="button"], [role="switch"]) { touch-action: manipulation; } /* zero specificity: a control that needs none (hold-to-talk, run) keeps it */

/* owner D0 check: a long input hint is cut with … instead of being chopped mid-word at phone width */
input::placeholder, textarea::placeholder { text-overflow: ellipsis; white-space: nowrap; overflow: hidden; }

/* owner tg 7707: no zoom on phones — text fields at 16px or more so iOS Safari does not zoom in on focus, and the policy notice pans
   but never zooms (it was touch-action:auto) */
@media (pointer: coarse) { input, textarea, select { font-size: max(16px, 1em) !important; } }

/* Top row (owner tg 8324/8325/8327/8364/8365): one row along --vg-top-row (settings.css) holding, left to right, the people count
   (multi/ui.js .vgm-server), the seed chip and the bag (shop/ui.js; tg 8386 order), then 「내 집」 / hide / camera (.vg-hud) and the gear. Every item is
   the gear's 26 px shape and colour: night-glass fill, the faint 1 px line, cream text, 0.6 (0.4 while walking) until touched; the count
   and the chip are pills, the rest discs. The count is on this row from the first frame (it used to sit under the logo until the
   first step) and in front of the logo and the top notices (z 16 > notices 15, chip 13, logo 2); the guide cards (40), sheets and the
   settings (the --z-modal layer, and style.css hides it under any open sheet) still cover it. It never runs under the chip:
   shop/index.js placeBag writes the chip's left edge into --vg-row-left (unset: no cap beyond its own). */
:root body .vgm-server, :root body.is-walking .vgm-server { /* :root: over multi/ui.js's own rules (incl. body.is-walking), injected later */
  top: calc(var(--vg-top-row, 10px) + 9px); left: max(16px, env(safe-area-inset-left)); z-index: 16;
  box-sizing: border-box; height: 26px; min-height: 26px; border-radius: 999px;
  border: 1px solid var(--line, rgba(244, 236, 216, 0.16)); background: var(--night-glass, rgba(15, 26, 51, 0.86));
  color: var(--cream, #f4ecd8); opacity: 0.6;
  max-width: min(calc(100% - 132px), calc(var(--vg-row-left, 100vw) - max(16px, env(safe-area-inset-left)) - 14px)); } /* tg 8422: the same 14 px gap as the rest of the row */
:root .vgm-server svg { color: inherit; }
:root .vgm-server:hover, :root .vgm-server:focus-visible, :root .vgm-server:active { opacity: 1; }
:root body.is-walking .vgm-server:not(:hover):not(:focus-visible) { opacity: 0.4; }

/* owner tg 8706 「가방누르면 왜 다른 UI버튼들이 사라지냐」: the shop sheet (bag, shelf, charge) floats over the scene without a dimmed
   full-screen backdrop; the top row, chat and notices stay visible and pressable around it (excluded from the hide rule above) */
.sheet.vgshop-sheet { background: transparent; pointer-events: none; }
.sheet.vgshop-sheet > * { pointer-events: auto; }
.sheet.vgshop-sheet .vgshop-box { box-shadow: 0 18px 48px rgba(0, 0, 0, 0.5); }
/* owner tg 8707 「상단버튼들 조금만 더 진하게」: the top row one step stronger at rest (0.6 -> 0.78, walking 0.4 -> 0.58); touched = 1 */
:root body :is(.vg-gear, .vgshop-chip, .vgm-server, .vg-hud-btn):not(:hover):not(:focus-visible):not(:active):not([aria-expanded="true"]) { opacity: 0.78; }
:root body.is-walking :is(.vg-gear, .vgshop-chip, .vgm-server, .vg-hud-btn):not(:hover):not(:focus-visible):not(:active):not([aria-expanded="true"]) { opacity: 0.58; }

/* tg 8706: the room chat stays too while the bag is open (multi/ui.js hides it under body.ui-open for the other panels) */
body.ui-open:has(.vgshop-sheet:not([hidden])) .vgm-chat { display: flex; }
