/* Haven's entry shell stays visible while Unity quietly prepares the world. It wears the game's Gump UI (AgentScripts/UI/Gump/
   BRIEF.md) over the village green rendered in Play (AgentScripts/LoginArt): the brass HAVEN wordmark between brass rails, the login
   in one of the game's windows (dark-wood frame, brass brackets and studs, title plank and rail, slate field), a brass button, a
   sunken name field and the amber XP tube for progress. The art is the game's own sprite kit, exported (with a few composites drawn
   by the kit's code) by AgentScripts/UI/Gump/entry/export.py at 2 px per art px. The page draws at 1 CSS px per game unit, so every
   piece is sliced and tiled unscaled and image-rendering: pixelated keeps it exact on 1x, 1.5x, 2x and 3x screens. Type follows the
   game's (font option D, AgentScripts/UI/Gump/BRIEF.md): pixel titles (GumpName) thresholded and given their hard 1 art px drop by
   the SVG filter in index.html (#gump-type), and everything else in Atkinson Hyperlegible Next, smooth, with the game's soft
   two-step shadow (--shade).
   Keep lengths even (whole art px). Documentation/UI/LoginScreen/README.md describes the page. */
@font-face { font-family: GumpName; src: url(splash/fonts/GumpName.woff2) format('woff2'); font-display: swap; }
@font-face { font-family: GumpText; src: url(splash/fonts/GumpText.woff2) format('woff2'); font-display: swap; }
@font-face { font-family: GumpTextCaps; src: url(splash/fonts/GumpTextCaps.woff2) format('woff2'); font-display: swap; }
/* The kit's palette (AgentScripts/UI/Gump/mockup/px.py) and the game's stone inks (HudSkin: Gold, Cream, Sage, Coral; Page.OnBrass). */
:root { color-scheme: dark; --out: #100c09; --gold: #eed27c; --cream: #e2cea0; --sage: #b59767; --coral: #ea603e; --on-brass: #2e1e12;
  --name: GumpName, monospace; --body: GumpText, system-ui, sans-serif; --label: GumpTextCaps, system-ui, sans-serif;
  --shade: 1px 1px 0 var(--out), 2px 2px 0 rgba(16,12,9,.55); }
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; background: var(--out); color: var(--cream); font: 16px/20px var(--body);
  font-variant-ligatures: none; }
button, input { font: inherit; color: inherit; }
[hidden] { display: none !important; }
.gump-filters { position: absolute; width: 0; height: 0; overflow: hidden; }
#game { position: relative; width: 100%; height: 100%; background: var(--out); }
#unity-canvas { display: block; width: 100%; height: 100%; outline: none; touch-action: none; }
#loading { position: absolute; inset: 0; isolation: isolate; overflow: auto; background: var(--out); }
.splash-art, .splash-shade { position: fixed; inset: 0; pointer-events: none; }
.splash-art { z-index: -2; }
.splash-art > * { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; object-position: center; }
.splash-art video { opacity: 0; transition: opacity 1.2s ease-in-out; }
.splash-art video.playing { opacity: 1; }
/* A pool of shade behind the centred title and window, a darker foot and a light vignette at the edges. */
.splash-shade { z-index: -1; background: radial-gradient(ellipse 30% 44% at 50% 50%,#100c09c4,#100c0670 55%,transparent),linear-gradient(0deg,#100c09a6,transparent 24%),radial-gradient(ellipse at 50% 45%,transparent 55%,#100c0666); }
.splash-shell { min-height: 100%; padding: 32px 16px; display: flex; flex-direction: column; justify-content: center; }
/* The chrome is pixel art: nearest-neighbour wherever the browser scales it. The backdrop keeps smooth scaling. */
.entry-content, #update, #error, #world-status { image-rendering: pixelated; }
/* Arrives in four 2 px steps, so it never sits between pixels. */
.entry-content { width: min(512px,100%); margin: 0 auto; animation: arrive 480ms steps(4,end); }

/* The wordmark: HAVEN cast in brass (splash/wordmark.webp), a brass rail with diamond ends either side. The h1 keeps its text for
   assistive technology; the anonymous text item has no size, so the gaps either side of it leave the wordmark's room. */
#loading h1 { display: flex; align-items: center; gap: 140px; height: 80px; margin: 0 -40px 20px; background: url(splash/wordmark.webp) 50% 0 no-repeat;
  color: transparent; font-size: 0; line-height: 0; }
#loading h1::before, #loading h1::after { content: ''; flex: 1; height: 12px; border-style: solid; border-width: 4px 10px 8px;
  border-image: url(splash/rail.webp) 4 10 8 fill / 4px 10px 8px repeat; }

/* A window, built as the game builds it (Spellbook.Book): its box is the frame's outer rect. Under it (::before) the dark-wood
   frame with the field's edge and the dithered drop shadow (splash/frame.webp, reaching 3 art px down-right) over the slate field;
   then (::after) the title plank and brass rail with the field's top edge (splash/head.webp); over both, the window's own
   background (brass studs on the bands) and border-image (the brass L-brackets). Both layers sit at z-index -1 and nothing here may
   form a stacking context, so they stay beneath the brackets and the form. The title text sits on the plank; the field starts
   under the rail. */
.window, #update > div { position: relative; display: flex; flex-direction: column; border: 16px solid transparent;
  border-image: url(splash/cover-corners.webp) 36 / 36px / 2px stretch;
  background: url(splash/rivet.webp) left 80px top 4px, url(splash/rivet.webp) 50% 4px, url(splash/rivet.webp) right 80px top 4px,
    url(splash/rivet.webp) left 80px bottom 4px, url(splash/rivet.webp) 50% calc(100% - 4px), url(splash/rivet.webp) right 80px bottom 4px,
    url(splash/rivet.webp) left 4px center, url(splash/rivet.webp) right 4px center;
  background-repeat: no-repeat; background-origin: border-box; }
.window { min-height: 216px; }
/* The window's height follows its view; the entry status keeps the login's 216 while the world loads. */
.window::before, .window::after, #update > div::before, #update > div::after { content: ''; position: absolute; z-index: -1; pointer-events: none; }
.window::before, #update > div::before { inset: -16px; border: 16px solid transparent;
  border-image: url(splash/frame.webp) 22 24 24 22 / 22px 24px 24px 22px / 0 6px 6px 0 repeat;
  background: url(splash/slate.webp) 0 0/64px 64px padding-box; }
.window::after, #update > div::after { top: 0; left: -6px; right: -6px; height: 44px; border-style: solid; border-width: 6px 12px 32px;
  border-image: url(splash/head.webp) 6 12 32 fill / 6px 12px 32px repeat; }
/* The title on the plank: Pixelify Sans 500 at 22 (2x its grid) in brass with its hard drop. */
.plank, #update h2 { display: block; height: 34px; margin: 0 0 22px; padding: 0 20px; overflow: hidden; color: var(--gold);
  font: 500 22px/32px var(--name); white-space: nowrap; text-overflow: ellipsis; filter: url(#gump-type); -webkit-font-smoothing: none; font-kerning: none; }
/* The front door's views (login.js): the plank's title, then one column of rows in the field. */
.view { display: flex; flex-direction: column; flex: 1; }
.stack { display: flex; flex-direction: column; gap: 12px; flex: 1; padding: 0 16px 18px; }
.stack.centred { justify-content: center; gap: 14px; padding-bottom: 26px; }
#view-title .stack, #view-back .stack { min-height: 216px; }
.centred { text-align: center; }
p { margin: 0; }
/* A field: its label in Silkscreen over a well sunk into the slate, brass-rimmed while focused and red-rimmed when refused. The
   well is drawn on the wrapper so the input's text alone is thresholded. A Show button or the Free mark sits at its end. */
.field { display: flex; flex-direction: column; gap: 6px; }
.lbl { color: var(--gold); font: 14px/20px var(--label); text-transform: uppercase; text-shadow: var(--shade); }
.well { display: flex; align-items: center; gap: 8px; height: 44px; padding: 0 4px 0 8px; border: 6px solid transparent;
  border-image: url(splash/input.webp) 6 fill / 6px repeat; }
.well:focus-within { border-image-source: url(splash/input-focus.webp); }
.well:has(input[aria-invalid='true']) { border-image-source: url(splash/input-invalid.webp); }
.well input { flex: 1; min-width: 0; height: 32px; padding: 0 4px; border: 0; border-radius: 0; background: none; color: var(--cream);
  font: 22px/32px var(--body); caret-color: var(--gold); outline: none; -webkit-appearance: none; appearance: none; text-shadow: var(--shade); }
.well input[type=password] { font-size: 30px; letter-spacing: 2px; }
.well input::selection { background: #7e5c20; color: #fff2be; }
.well input::-ms-reveal { display: none; }
/* The live username check: the brass tick and FREE in green, or nothing while it is unknown. */
.free { display: flex; align-items: center; gap: 8px; padding-right: 6px; color: #9fd06a; font: 14px/20px var(--label);
  text-transform: uppercase; text-shadow: var(--shade); }
.free::before { content: ''; width: 20px; height: 16px; background: url(splash/tick.webp); }
.rules { display: flex; flex-direction: column; gap: 4px; }
/* The password rule turns green with the brass tick once it is met. */
.rule { display: flex; align-items: center; gap: 8px; color: #c2ad84; font: 16px/20px var(--body); text-shadow: var(--shade); }
.rule::before { content: ''; width: 20px; height: 16px; background: url(splash/tick.webp); opacity: .35; filter: grayscale(1); }
.rule.met { color: #9fd06a; }
.rule.met::before { opacity: 1; filter: none; }
.note.taken { color: var(--coral); }
.note { color: #c2ad84; font: 16px/20px var(--body); text-shadow: var(--shade); }
.note.bright { color: var(--cream); }
.error { min-height: 20px; color: var(--coral); font: 16px/20px var(--body); text-shadow: var(--shade); }
.error:empty { display: none; }
/* Remember this device: the kit's toggle box, its brass tick over it when checked. */
.check { position: relative; display: flex; align-items: center; gap: 10px; width: max-content; max-width: 100%; color: var(--cream);
  font: 16px/20px var(--body); cursor: pointer; }
.check input { position: absolute; opacity: 0; width: 18px; height: 18px; margin: 0; }
.check::before { content: ''; flex: none; width: 18px; height: 18px; background: url(splash/check.webp); }
.check:hover::before, .check:has(input:focus-visible)::before { background-image: url(splash/check-hover.webp); }
.check::after { content: ''; position: absolute; left: 0; top: calc(50% - 10px); width: 20px; height: 16px; background: url(splash/tick.webp); visibility: hidden; }
.check:has(input:checked)::after { visibility: visible; }
.check span { text-shadow: var(--shade); }
/* A text link: brass, underlined 1 art px. */
.link { display: inline; padding: 0; border: 0; background: none; color: var(--gold); font: inherit; text-decoration: underline 2px;
  text-underline-offset: 4px; cursor: pointer; outline: none; }
.link:hover, .link:focus-visible { color: #fff2be; }
/* Free usernames to pick when one is taken: HUD chips. */
.suggest { display: flex; flex-direction: column; gap: 8px; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chips button { height: 32px; padding: 0 10px; border: 6px solid transparent; border-image: url(splash/chip.webp) 6 fill / 6px repeat; background: none;
  color: var(--cream); font: 16px/20px var(--body); cursor: pointer; outline: none; }
.chips button:hover, .chips button:focus-visible { color: #fff2be; border-image-source: url(splash/button-small-hover.webp); border-image-slice: 4 fill; border-image-width: 4px; border-width: 4px; padding: 0 12px; }
.traveler { display: flex; flex-direction: column; align-items: center; gap: 4px; }
#back-name { color: var(--cream); font: 500 22px/28px var(--name); filter: url(#gump-type); -webkit-font-smoothing: none; font-kerning: none; }
#back-detail { color: #c2ad84; font: 16px/20px var(--body); text-shadow: var(--shade); }
.wait { display: flex; justify-content: center; align-items: baseline; gap: 12px; margin: 6px 0; }
#paused-clock { color: var(--gold); font: 500 22px/28px var(--name); font-variant-numeric: tabular-nums; filter: url(#gump-type); -webkit-font-smoothing: none; font-kerning: none; }
/* Buttons, as the game labels them in Silkscreen at 16 (2x): the primary brass (dark ink), the stone secondary (cream), the small
   stone Show. Hover (and keyboard focus) brightens one; pressed sinks it and drops the label 1 art px. Every swap loads with the
   page. A primary that waits is the kit's disabled brass. */
.btn, #update button { display: block; height: 40px; padding: 0 12px; border: 6px solid transparent;
  border-image: url(splash/button-primary.webp) 6 fill / 6px repeat; border-radius: 0;
  background: url(splash/button-primary-hover.webp) 0 0/0 0 no-repeat, url(splash/button-primary-pressed.webp) 0 0/0 0 no-repeat,
    url(splash/button-secondary-hover.webp) 0 0/0 0 no-repeat, url(splash/button-secondary-pressed.webp) 0 0/0 0 no-repeat;
  color: var(--on-brass); font: 16px/28px var(--label); text-transform: uppercase; cursor: pointer; outline: none; }
#update button { margin: 0 16px 16px; }
.btn span, #update button span { display: block; }
.btn:hover, #update button:hover, .btn:focus-visible, #update button:focus-visible { border-image-source: url(splash/button-primary-hover.webp); }
.btn:active, #update button:active { border-image-source: url(splash/button-primary-pressed.webp); }
.btn:active span, #update button:active span { padding-top: 2px; }
.btn.stone { border-image-source: url(splash/button-secondary.webp); color: var(--cream); }
.btn.stone span { text-shadow: var(--shade); }
.btn.stone:hover, .btn.stone:focus-visible { border-image-source: url(splash/button-secondary-hover.webp); }
.btn.stone:active { border-image-source: url(splash/button-secondary-pressed.webp); }
.btn[disabled], .btn[disabled]:hover, .btn[disabled]:active { border-image-source: url(splash/button-primary-disabled.webp); color: #6e5c3e; cursor: default; }
.btn[disabled] span { padding-top: 0; }
.btn.small { flex: none; height: 28px; padding: 0 8px; border-width: 4px; border-image: url(splash/button-small.webp) 4 fill / 4px repeat;
  color: var(--cream); font-size: 16px; line-height: 20px; }
.btn.small span { text-shadow: var(--shade); }
.btn.small:hover, .btn.small:focus-visible { border-image-source: url(splash/button-small-hover.webp); }
.btn.small:active { border-image-source: url(splash/button-small-pressed.webp); }
.btn.busy { cursor: progress; }
/* Back: the stone button at the screen's bottom left, as the game places it. */
#back { position: fixed; left: 40px; bottom: 40px; min-width: 136px; image-rendering: pixelated; animation: arrive 480ms steps(4,end); }
/* Progress: the label in Silkscreen over the amber XP tube with its segment notches, centred in the field. */
.loading-progress { position: relative; display: flex; flex-direction: column-reverse; margin: auto 16px; }
#progress-label { display: block; margin-bottom: 8px; color: var(--cream); font: 14px/20px var(--label); text-shadow: var(--shade); }
progress { display: block; width: 100%; height: 18px; border: 2px solid transparent; border-image: url(splash/bar.webp) 4 fill / 4px repeat;
  background: none; color: #f0b62e; -webkit-appearance: none; appearance: none; }
progress::-webkit-progress-bar { background: transparent; }
progress::-webkit-progress-value { background: url(splash/tube-cap-xp.webp) 100% 0/2px 14px no-repeat, url(splash/fill-xp.webp) 0 0/8px 14px repeat-x; transition: width 300ms; }
progress::-moz-progress-bar { background: url(splash/tube-cap-xp.webp) 100% 0/2px 14px no-repeat, url(splash/fill-xp.webp) 0 0/8px 14px repeat-x; }
.loading-progress::after { content: ''; position: absolute; left: 2px; right: 2px; bottom: 2px; height: 14px; pointer-events: none;
  background: url(splash/tube-notch.webp) 0 0/26px 14px repeat-x; }
#loading.awaiting-name .loading-progress { display: none; }
@keyframes arrive { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }

/* The update notice: the game dimmed (no blur) under a window like the login's: the title on the plank, one line, Reload. */
#update { position: absolute; inset: 0; z-index: 10; display: grid; place-content: center; padding: 16px; background: radial-gradient(ellipse at 50% 50%,#100c09a0,#100c09d8); }
#update > div { width: min(384px,calc(100vw - 32px)); }
#update p { margin: 0 16px 16px; color: var(--cream); font: 16px/20px var(--body); text-shadow: var(--shade); }
/* The startup failure and the world-status pill are stone gumps with a mark drawn before their text (Unity and index.html replace
   the text): an opaque stone panel with a red diamond, and a HUD chip with a brass one. Centred without transforms, so the art
   stays on whole pixels. */
#error, #world-status { position: absolute; left: 0; right: 0; margin: 0 auto; border: 0 solid transparent; box-shadow: 4px 4px 0 #0000005a; }
#error { z-index: 5; top: 80px; width: min(520px,calc(100% - 32px)); padding: 16px 20px 16px 42px; border-image: url(splash/panel.webp) 10 fill / 10px repeat;
  color: var(--cream); font: 16px/20px var(--body); }
#error::before { content: ''; position: absolute; left: 18px; top: 19px; width: 14px; height: 14px; background: url(splash/diamond-red.webp); }
#world-status { bottom: 176px; width: max-content; min-width: 240px; max-width: calc(100% - 32px); height: 32px; padding: 0 14px 0 34px; overflow: hidden;
  border-image: url(splash/chip.webp) 6 fill / 6px repeat; color: var(--cream); font: 16px/32px var(--label); white-space: nowrap; text-overflow: ellipsis; pointer-events: none; }
#world-status::before { content: ''; position: absolute; left: 12px; top: 9px; width: 14px; height: 14px; background: url(splash/diamond-brass.webp); }

/* Phones: the window fills the width and the rails shorten. */
@media (max-width: 560px) {
  .splash-art > * { object-position: 42% center; }
  .splash-shade { background: radial-gradient(ellipse 70% 40% at 50% 50%,#100c09b3,#100c0959 60%,transparent),linear-gradient(0deg,#100c09b3,transparent 30%); }
  #loading h1 { margin: 0 0 16px; }
  .splash-shell { padding-bottom: 88px; }
  #back { left: 16px; bottom: 16px; }
}
/* Short landscape screens: tighter spacing keeps Enter in view. */
@media (max-height: 560px) and (min-width: 561px) {
  .splash-shell { padding-top: 14px; padding-bottom: 14px; }
  #loading h1 { margin-bottom: 10px; }
}
/* Big screens at 1x: the chrome at 3 and 4 px per art px, still whole pixels. */
@media (min-width: 2200px) and (min-height: 1200px) { .entry-content, #update > div, #error, #world-status, #back { zoom: 1.5; } }
@media (min-width: 3000px) and (min-height: 1650px) { .entry-content, #update > div, #error, #world-status, #back { zoom: 2; } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}
