/* ⭐CASCADE LAYERS (#2920, 2026-09-22): this file is the `base` layer. The
   order is declared once, below, and repeated inline by build-web.js so
   the world page fixes it before any stylesheet loads. Inside a layer,
   load order and specificity still decide between THIS file's rules;
   between files, the layer decides, always. */
@layer base, module, skin, nerm, corners;
@layer base {
/* ══════════════════════════════════════════════════════════════════════
   THE WORLD SHELL (GH #1986) — what is on screen in every room.

   #1986 settled the axis: the question is not left vs right, it is whether
   a thing is in the SHELL at all. Four things are, and Wray confirmed each:

     who you are     your cat's handle, house, level
     the way out     the nav that opens the six doors
     your $9LV       the wallet chip
     chat            the bar, and the lines over cats' heads

   Chat already shipped inside the picture (world-presence.js). This file
   carries the other three, and nothing else: a zone title, a foe plate or a
   cast tray is CONTRIBUTED BY THE ROOM and has a different owner.

   ── WHERE IT SITS ───────────────────────────────────────────────────
   In the letterbox, positioned only in calc() against the four variables
   the engine publishes on <body> when it resizes:

     --fx  left edge of the render      --fw  render width
     --fy  top edge of the render       --fh  render height

   The picture is centred horizontally, so --fx doubles as the right offset.
   Vertically it sits flush to the top since GH #2037, and the band below it
   is its own variable, --fb; bottom-anchored rules read
   `var(--fb, var(--fy, 0px))`, falling back to the old symmetric frame.
   Same technique as world-rail.css and the prototype's own HUD, and it is
   the whole trick: nothing here measures a canvas or works out a scale, so
   the shell is correct at every window size with no JavaScript running at
   all.

   ── WHY IT BORROWS RATHER THAN REDEFINES ────────────────────────────
   The buttons are `.wr-plank` from world-rail.css, not a copy of it. That
   class 9-slices `--wc-button` and it writes the `fill` keyword. #1953's
   hardest-won rule is that EXACTLY ONE side writes `fill` — two of them is
   invalid CSS and the whole shorthand is dropped, which leaves a button on
   its fallback gradient inside a rail painting drawn leather. That is the
   half-and-half look Wray ruled out, reached from the inside, and it looks
   like bad art rather than bad CSS. So this file writes no border-image at
   all and borrows the one that exists.

   Plates are retired on world surfaces (#1953 points 3 and 4), so nothing
   here touches `.hg-plank` or any `--hg-plate-*` token.

   ── WHAT #2020 CHANGED, AND WHY IT IS NOT A RESTYLE ─────────────────
   The rail used to be a handle and a house on bare leather, filling 8% of a
   320x960 strip while the doors sat in the RIGHT rail. It is now mix "E",
   picked by Wray off drawn variants: a parchment plate carrying the portrait,
   the card, the lives and the GP meter, then the $9LV chip, then the six doors
   as a 2x3 pad of tiles.

   IT FILLS THE RAIL BECAUSE IT IS COMPOSED TO, NOT BECAUSE ANYTHING WAS
   SCALED. There is no multiplier that works: frame D's rail is 2.0x the
   width of Jack's mock and only 1.33x its height, which is why the earlier
   4/3 attempt fixed one rail and left the other short.

   THE HEIGHT BUDGET IS TIGHT AND THE DRAWN BUTTON IS WHY. `--wc-button-width`
   is `16px 24px 16px 24px` at chrome pixel 4, so every full-width plank spends
   32px of vertical on its border before it holds anything. Six of them plus a
   large portrait want ~660px of the ~928px available, which is why the doors
   are TILES: six icons cost about a third of the height six planks do, and
   that difference is exactly what pays for the portrait. The pad is not a
   style preference.
   ══════════════════════════════════════════════════════════════════════ */

/* ── the sample notifications are not this player's ──────────────────
   The engine bakes three of them — "Daily pack ready to claim", "Quests
   refreshed", "The cipher is unsolved" — as a fixed array in src/app.js.
   In the standalone that is scene dressing and fine. Served, they tell
   EVERY player their pack is waiting, including the one who claimed it an
   hour ago, and there is nothing behind any of the three to click.

   This file is loaded only by the served build, so hiding it here leaves
   the standalone exactly as it was. It goes rather than being made real
   because the transient daily-pack chip is its own piece of #1986, with a
   real endpoint behind it — and a placeholder that looks finished is how
   you end up never building the real one.

   The renderer does `box.appendChild` with no guard, so the element must
   stay in the page. Hidden, not removed. */
#notes { display: none; }

/* ── the standalone's own labels come off the served world ───────────
   Wray, first impression 2026-08-25: "the text on the frame the label of the
   area in the top and the info text at the bottom about camera controls is
   clutter."

   Both are the standalone's. `#title` names the room you are in, which a
   player can see; `#legend` is three lines of control help, and the engine
   already says "Tap a tile to walk. Tap a cat to say hello." over your own
   cat a second after you arrive, which is the version that arrives when it
   is useful and leaves when it is not.

   HIDDEN, NOT REMOVED, and for a harder reason than #notes: app.js:893 does
   `document.getElementById('title-name').textContent = def.title` with no
   guard, so deleting the element throws on every room change. The room title
   is room-contributed chrome in #1986 and comes back when it is designed
   rather than inherited. */
#title, #legend { display: none; }

.ws-rail {
  /* Component-scoped, like world-rail.css does it, so a global bitmap-type
     seam can define --wc-head / --wc-body later without fighting this file. */
  --ws-head: var(--wc-head, 'Press Start 2P', 'Courier New', monospace);
  --ws-body: var(--wc-body, 'VT323', 'Courier New', monospace);
  --ws-ink: var(--wc-ink-on-leather, var(--wc-ink, #e6c88a));

  position: fixed;
  z-index: 38;                 /* under the rail (40), over the render */
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 10px;
  box-sizing: border-box;
  color: var(--ws-ink);
  font-family: var(--ws-body);
  /* The letterbox is already dark, so with no drawn chrome the surface stays
     almost nothing and the pieces do the work. Same call as the rail. */
  background: var(--wc-leather, linear-gradient(180deg, rgba(20, 12, 6, .55), rgba(10, 6, 3, .8)));
  background-size: var(--wc-leather-tile, auto);
  /* The drawn sources are baked one canvas pixel per chrome pixel and scaled
     by a whole number; smoothing turns the dither to mush. */
  image-rendering: pixelated;
  overscroll-behavior: contain;
}

.ws-rail[hidden] { display: none; }

/* ── THE RAIL'S WIDTH, AND WHY IT IS NO LONGER JUST --fx (#2507) ──────
   `--fx` is the LETTERBOX GUTTER: the black beside the picture, which is
   exactly where the rail lived. Under full bleed there is no gutter, so this
   computed to 0 and every ribbon in the rail measured 0x0 — present, styled,
   and drawn nowhere.

   So the width is a variable with the gutter as its default, and full bleed
   overrides it with a width of its own. `--sk-rail-cols` is that width in
   CHROME PIXELS rather than CSS pixels, multiplied by the render's own
   upscale (`--fs`), because the chrome is bitmap art baked one canvas pixel
   per chrome pixel — a rail measured in CSS pixels would hold a different
   amount of art on every screen. Written against the old capped sum (x3 at
   1920, x4 at 2560), 96 columns was ~15% of the window at every size (288px
   at 1920, 384 at 2560). Since the desktop draws at x2 (#3876, Wray
   2026-10-09), --fs is 2 on every desktop from 1280 to 4096 wide, so the
   rail is 192px at all of them: 15% of a 1280 window, 10% of 1920, 7.5% of
   2560.

   96 IS A STARTING NUMBER, NOT A RULING. It is the one dial for how wide the
   rail is under full bleed; the letterbox path is untouched by it.

   The 4 in `var(--fs, 4)` is the old x4 default, and the served page never
   reaches it: world-frame-fill.js marks the page only once the engine is up,
   and the engine publishes --fs at boot and again straight after the mark.
   Left as it is: a value that renders is not changed without a look. */
.ws-rail[data-edge="left"],
.ws-rail[data-edge="right"] {
  --ws-rail-w: var(--fx, 240px);
}

body.frame-fill-desktop .ws-rail[data-edge="left"],
body.frame-fill-desktop .ws-rail[data-edge="right"] {
  --ws-rail-w: calc(var(--sk-rail-cols, 96) * var(--fs, 4) * 1px);
}

.ws-rail[data-edge="left"] {
  left: 0;
  top: var(--fy, 0px);
  width: var(--ws-rail-w);
  height: var(--fh, 100vh);
}

.ws-rail[data-edge="right"] {
  right: 0;
  top: var(--fy, 0px);
  width: var(--ws-rail-w);
  height: var(--fh, 100vh);
  justify-content: flex-start;
}

/* ── the notices (#1997) ─────────────────────────────────────────────
   THE RIGHT SIDE PAINTS NO GROUND. The left rail is leather because it is
   always full; everything on the right is transient, so on a quiet day there
   is nothing to say at all and a painted rail under that would recreate the
   dead space #2020 exists to remove, on the other side of the picture. Each
   notice brings its own parchment; the bar between them stays letterbox.

   It also takes no pointer events, so the empty space beside the render is
   still the render's -- a full-height invisible rail swallowing clicks over
   the picture is the kind of bug nobody thinks to look for. */
.ws-notices {
  background: none;
  pointer-events: none;
  gap: 12px;
}

.ws-notices > * { pointer-events: auto; }

.ws-note {
  flex: 0 0 auto;
  padding: 12px;
  background-image: var(--wc-parchment, linear-gradient(180deg, #cbb48f, #b39c76));
  background-size: var(--wc-parchment-tile, auto);
  color: var(--wc-ink-on-parchment, #33240f);
  border: 4px solid #33240f;
  box-shadow: 6px 6px 0 rgba(0, 0, 0, .55), inset 0 0 24px rgba(138, 112, 72, .35);
  image-rendering: pixelated;
}

.ws-note-head {
  font-family: var(--ws-head);
  font-size: 10px;
  line-height: 1.4;
  letter-spacing: .1em;
  text-align: center;
  color: rgba(51, 36, 15, .66);
  margin-bottom: 10px;
}

.ws-note-line {
  font-size: 22px;          /* VT323 is condensed and sits small */
  line-height: 1.1;
  letter-spacing: .02em;
  margin-bottom: 10px;
}

/* Letterpress, like the plate: a light lift under dark ink on pale parchment,
   which is the opposite of what the leather surfaces want. */
.ws-note-head, .ws-note-line, .ws-quest-name {
  text-shadow: 2px 2px 0 rgba(255, 255, 255, .26);
}

.ws-note .ws-chip { margin-top: 2px; }

/* ── a quest row ─────────────────────────────────────────────────── */

.ws-quest {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  margin-top: 8px;
}

.ws-box {
  width: 14px;
  height: 14px;
  flex: none;
  margin-top: 3px;
  border: 4px solid #33240f;
  background: rgba(58, 34, 15, .12);
}

.ws-quest.done .ws-box {
  background: #4e9e5e;
  box-shadow: inset 2px 2px 0 #7cd894;
}

/* name over instruction, one block beside the tickbox */
.ws-quest-text {
  display: flex;
  flex-direction: column;
  gap: 1px;
  min-width: 0;
}

.ws-quest-name {
  font-size: 20px;
  line-height: 1.15;
  letter-spacing: .02em;
}

/* WHAT THE QUEST ACTUALLY ASKS (Wray, 2026-09-05: "theres no quest
   description its kind of vague what the quest even is to do sometimes").
   The name is evocative and the line under it is literal -- "Into the Pit"
   means nothing on its own, "Run the Gauntlet in any zone today" is the
   instruction. Quieter than the name so the list still scans as three
   objectives rather than six lines of equal weight. */
.ws-quest-desc {
  font-size: 16px;
  line-height: 1.2;
  opacity: .62;
}

/* A finished quest stays on the list rather than vanishing: three of three
   struck through is the day's progress, and a list that shortens as you go
   reads as things going missing. The panel as a whole goes when they are all
   done -- see questNotice. */
.ws-quest.done .ws-quest-name {
  opacity: .55;
  text-decoration: line-through;
}

/* a finished quest does not need to keep telling you how to do it */
.ws-quest.done .ws-quest-desc { display: none; }

.ws-meter {
  height: 20px;
  border: 4px solid #33240f;
  background: #3c2c18;
  position: relative;
  image-rendering: pixelated;
}

.ws-meter i {
  position: absolute;
  inset: 0;
  background: repeating-linear-gradient(90deg, #c49a3e 0 12px, #8a6d2f 12px 16px);
  box-shadow: inset 0 4px 0 rgba(255, 255, 255, .2), inset 0 -4px 0 rgba(0, 0, 0, .3);
}

/* ── the foot: chip, then doors ──────────────────────────────────────
   Wray, 2026-08-26: the balance sits above where the menu buttons start, not
   at the bottom edge. They are ONE block so nothing can drift between them
   when the plate above changes height — which it does, the moment a run
   starts and the lives row appears. */

.ws-padwrap {
  display: flex;
  flex-direction: column;
  gap: 12px;
  flex: 1 1 auto;
  min-height: 0;
}

/* Two columns normally, three when the rail is broad and shallow. The count
   arrives from fitRail() as `--ws-cols` for the same reason the portrait size
   does: it depends on the measured box, not on a media query, because the rail
   is a share of the window rather than the window itself. */
.ws-pad {
  display: grid;
  grid-template-columns: repeat(var(--ws-cols, 2), 1fr);
  gap: 10px;
  flex: 1 1 auto;
  min-height: 0;
}

/* THREE ACROSS MEANS LESS ROOM PER TILE, so the icon gives way before the
   label does — a door you cannot read is worse than a door with a small
   glyph. */
.ws-rail[data-plate="compact"] .ws-ico { width: 28px; height: 28px; }
.ws-rail[data-plate="compact"] .ws-tile { gap: 6px; font-size: 9px; padding: 6px 3px; }

/* THE FLAT TILE, for a rail too narrow to spend 48px on a border.
   The drawn button's frame is `16 24 16 24` at chrome pixel 4, so in the 240px
   rail a 1440x900 laptop gets, a two-column tile is ~101px wide and the border
   is HALF of it, which wraps "YOUR RECORD" to three lines and blows the
   height. At that width the button's own detail is unreadable anyway, so the
   real choice is a decorated frame with no room for a label, or a plain one
   with the label intact. The label wins: it is navigation.

   This writes a plain border and NO border-image, so the `fill` keyword still
   has exactly one owner (#1953). Taking `.wr-plank` off the element in script
   is what actually removes the 9-slice. */
.ws-rail[data-tile="flat"] .ws-tile {
  border: 4px solid #6b5334;
  background: linear-gradient(180deg, #4a3220, #2c1c0e);
  box-shadow: inset 0 2px 0 rgba(230, 200, 138, .18), 0 3px 0 rgba(0, 0, 0, .5);
  padding: 6px 4px;
  gap: 6px;
}

.ws-rail[data-tile="flat"] .ws-tile:hover { filter: brightness(1.12); }
.ws-rail[data-tile="flat"] .ws-tile:active { transform: translateY(2px); }

/* THE FLAT TILE HAD NO FOCUS RING AND THE DRAWN ONE DID, which is backwards:
   the flat tile is what a 1440x900 laptop gets, so the narrower the window the
   more likely the six doors were invisible to a keyboard. `.wr-plank` carries
   the ring, applyFit takes `.wr-plank` off to flatten the tile, and the ring
   went with it. Restated here rather than moved, because the two states are
   different elements as far as CSS is concerned. */
.ws-rail[data-tile="flat"] .ws-tile:focus-visible {
  outline: 2px solid var(--wc-ink-on-leather, #e6c88a);
  outline-offset: 2px;
}
.ws-rail[data-tile="flat"] .ws-ico { width: 32px; height: 32px; }

/* A TILE IS A `.wr-plank`, RESHAPED — it is not a second button.
   The first cut of this declared its own `border-image: var(--wc-button) ...
   fill` here and __tests__/worldShell.test.js failed it on the spot, which is
   exactly what that test is for: EXACTLY ONE stylesheet may write the `fill`
   keyword for the shared 9-slice, and two of them is invalid CSS that drops
   the whole shorthand silently, leaving a fallback-gradient button inside a
   rail painting drawn leather. So the markup carries both classes and this
   rule sets layout only — no border, no border-image, no `fill`. */
.ws-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  cursor: pointer;
  background: transparent;
  font-family: var(--ws-head);
  font-size: 10px;
  line-height: 1.4;
  text-align: center;
  color: #f0e7d5;
  text-shadow: 2px 2px 0 #000;
  padding: 8px 4px;
}

/* .wr-plank sizes itself to its content and centres one line of text. A tile
   is a cell in a grid, so it takes the cell. */
.ws-pad .ws-tile { width: 100%; height: 100%; }

.ws-ico {
  width: 40px;
  height: 40px;
  flex: none;
  background-size: 100% 100%;
  background-repeat: no-repeat;
  image-rendering: pixelated;
}

/* PLACEHOLDER GLYPHS. Twelve by twelve, one ink, inlined so the rail needs no
   extra requests and no asset that can 404. They are stand-ins: whoever draws
   the real door icons replaces these six rules and nothing else. */
.ws-ico[data-door="rooms"] { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAANklEQVR42mNgIBEwInM+PL/6H5siAUltRgxBXIrR5ZhIddKw1fDh+dX/uEKN9k5iJCbikGMbABE1Fr+MazBvAAAAAElFTkSuQmCC); }
.ws-ico[data-door="arena"] { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAM0lEQVR42mNgwAI+PL/6/8Pzq/+xyTExkAhGpAZG5KDEp1BAUpsRpyRt4wHZZELOJMpJAJgQIWo32NmWAAAAAElFTkSuQmCC); }
.ws-ico[data-door="board"] { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAPElEQVR42mNgIBEwMjAwMHx4fvU/MYoFJLUZGYjVAFPDgk0Qp8noGpAlcIEBtgGbAbT3AxMhxaRELFYAAE+2I8sUaZiTAAAAAElFTkSuQmCC); }
.ws-ico[data-door="stall"] { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAWElEQVR42mNkQAMfnl/9j8wXkNRmZMAGPjy/+h9dMU5xbApx2ky0qVBxJnx+wKaJCZtiAUltRphn0TUxMZAImLAFIbIf0IMVwwZkBXjjgOhgJTXiGElNGgColFq9YzhEMQAAAABJRU5ErkJggg==); }
.ws-ico[data-door="nine"] { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAUUlEQVR42mNgYGBg+PD86v8Pz6/+Z8ABkOWZ0CWwKUbmsxBSgA4YCSlAB0wMJAK4BgFJbUZcipDlSLaBcj8gW4/NmaTbgM+z2GxnIhRC6M4DAEW5J2w5rS/MAAAAAElFTkSuQmCC); }
.ws-ico[data-door="record"] { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAANElEQVR42mNgIBEwMjAwMHx4fvU/MYoFJLUZGbBp+PD86n9sYgwMDAxMpDppVAPtIo4UAABp9Ra9nyuroQAAAABJRU5ErkJggg==); }
/* ── the wallet chip ─────────────────────────────────────────────────
   Wray, 2026-08-23: "it should be a button on the right side bar, when you
   connect it could show your 9LV balance, lets people know its a web3 app."
   Same behaviour as the plaza's chip; #2020 moved it above the doors. */

.ws-wallet {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  gap: 3px;
  align-items: stretch;
  text-align: left;
}

.ws-wallet[hidden] { display: none; }

.ws-wallet-sub {
  font-size: 14px;
  letter-spacing: .06em;
  opacity: .55;
  padding: 0 3px;
}

.ws-wallet-sub:empty { display: none; }

/* The chip itself. Deliberately NOT `.wr-plank`: beside a 2x3 pad of drawn
   buttons, a seventh drawn button reads as a door you have not noticed. Brass
   on dark says readout. No border-image here, so the `fill` keyword still has
   exactly one owner (world-rail.css). */
.ws-chip {
  appearance: none;
  -webkit-appearance: none;
  display: block;
  width: 100%;
  cursor: pointer;
  font-family: var(--ws-head);
  font-size: 12px;
  line-height: 1.4;
  letter-spacing: .04em;
  text-align: center;
  color: #e8c675;
  background: #1a1008;
  border: 4px solid #8a6d2f;
  padding: 12px 6px;
  box-shadow: 4px 4px 0 #000, inset 0 2px 0 rgba(232, 198, 117, .3), inset 0 -4px 0 rgba(0, 0, 0, .5);
  image-rendering: pixelated;
  transition: filter .08s, transform .08s;
}

.ws-chip:hover { filter: brightness(1.12); }
.ws-chip:active { transform: translateY(2px); box-shadow: 4px 2px 0 #000, inset 0 2px 0 rgba(232, 198, 117, .3); }
.ws-chip:focus-visible { outline: 2px solid var(--ws-ink); outline-offset: 2px; }
.ws-chip:disabled { opacity: .6; pointer-events: none; }

/* ── the wallet connect overlay (#2404) ───────────────────────────────
   The web3-standard connect, in the parchment: tap the chip (or ask from a
   framed door), pick your wallet, one signature. Full screen over the world
   because the wallet extension pops its own window and the player should
   not be hunting a tiny modal behind it. Same drawn-chrome vocabulary as
   the chip: --ws-head type, brass on dark, hard shadows. */

.ws-connect {
  position: fixed;
  inset: 0;
  z-index: 100000;
  background: rgba(6, 4, 10, .82);
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
}

.ws-connect-panel {
  width: min(340px, 94vw);
  background: #1a1008;
  border: 4px solid #8a6d2f;
  box-shadow: 6px 6px 0 #000, inset 0 2px 0 rgba(232, 198, 117, .3), inset 0 -4px 0 rgba(0, 0, 0, .5);
  padding: 18px 16px 14px;
  text-align: center;
  image-rendering: pixelated;
}

.ws-connect-title {
  font-family: var(--ws-head);
  font-size: 13px;
  letter-spacing: .08em;
  color: #e8c675;
  margin-bottom: 10px;
}

.ws-connect-note {
  font-family: var(--ws-body);
  font-size: 16px;
  line-height: 1.35;
  color: var(--ws-ink);
  opacity: .85;
  margin-bottom: 14px;
}

.ws-connect-list { display: flex; flex-direction: column; gap: 8px; }
.ws-connect-wallet { font-size: 13px; }

.ws-connect-status {
  font-family: var(--ws-body);
  font-size: 16px;
  min-height: 20px;
  color: var(--ws-ink);
  margin-top: 10px;
}
.ws-connect-status a { color: #e8c675; }
.ws-connect-status:empty { display: none; }

.ws-connect-alt {
  display: block;
  font-family: var(--ws-body);
  font-size: 16px;
  color: #e8c675;
  margin-top: 12px;
  text-decoration: underline;
}

.ws-connect-close {
  appearance: none;
  -webkit-appearance: none;
  border: none;
  background: none;
  cursor: pointer;
  font-family: var(--ws-body);
  font-size: 15px;
  color: var(--ws-ink);
  opacity: .55;
  margin-top: 10px;
  padding: 6px 10px;
}
.ws-connect-close:hover { opacity: .85; }

/* ── no side room: one plank, inside the picture ──────────────────────
   A phone letterbox has bars above and below and none at the sides, so a
   side rail there is zero pixels wide and the shell would simply not exist.
   The plaza already answers this with a single Menu button, so this is a
   port of a shipped pattern rather than a new idea. #1923 owns the real
   phone pass; this only makes sure the world is not unusable before it. */

.ws-menu {
  position: fixed;
  z-index: 39;
  right: calc(var(--fx, 0px) + 14px);
  bottom: calc(var(--fb, var(--fy, 0px)) + 14px);
}

.ws-menu[hidden] { display: none; }

/* THE PHONE SEAT (#2236). Under full bleed --fx and --fb publish 0 and the
   corner above lands on the chat strip's right end. world-frame.js publishes
   the safe bands and stamps data-wf-frame on body: the menu goes to the TOP
   band, top-right — it is meta, not an action, and the bottom rows belong to
   the thumb (chat strip, then the chest and gather row, which is centred and
   WIDE: the timing bar alone leaves 35px a side on a 390px screen, so any
   bottom corner seat overlaps it). The engine's own room title keeps the
   top-left. The letterbox rule above is untouched, so the desktop cannot
   move. */
body[data-wf-frame="fill"] .ws-menu {
  bottom: auto;
  top: calc(var(--wf-safe-top, 8px) + 8px);
}

/* ── the sheet ───────────────────────────────────────────────────────
   A door opens over the picture, never beside it: a page navigation would
   reload the world and respawn you at the gate, which is the same reason
   the plaza frames its pages instead of linking to them. */

.ws-sheet {
  position: fixed;
  z-index: 60;
  left: var(--fx, 0px);
  top: var(--fy, 0px);
  width: var(--fw, 100vw);
  height: var(--fh, 100vh);
  display: flex;
  flex-direction: column;
  background: var(--wc-parchment, linear-gradient(180deg, rgba(20, 12, 6, .94), rgba(10, 6, 3, .97)));
  background-size: var(--wc-parchment-tile, auto);
  color: var(--wc-ink-on-parchment, #2a1608);
  font-family: var(--wc-body, 'VT323', 'Courier New', monospace);
  image-rendering: pixelated;
}

.ws-sheet[hidden] { display: none; }

/* On a phone the sheet IS the screen (--fy and --fh publish the whole of
   it), so its top bar would sit under the notch and its last row under the
   home bar. The safe bands become padding INSIDE the sheet; its position and
   size do not change. */
body[data-wf-frame="fill"] .ws-sheet {
  box-sizing: border-box;
  padding-top: var(--wf-safe-top, 8px);
  padding-bottom: var(--wf-safe-bottom, 12px);
}

.ws-sheet-top {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 12px;
  border-bottom: 1px solid rgba(42, 22, 8, .28);
}

.ws-sheet-title {
  flex: 1 1 auto;
  font-family: var(--wc-head, 'Press Start 2P', monospace);
  font-size: 10px;
  letter-spacing: .04em;
  color: var(--wc-ink-on-parchment, #2a1608);
}

.ws-tabs {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  padding: 8px 12px 0;
}

.ws-sheet-body {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
}

.ws-sheet-body iframe {
  flex: 1 1 auto;
  width: 100%;
  height: 100%;
  border: 0;
  background: #0c0b12;
  /* The framed pages are ordinary app pages, not pixel art. */
  image-rendering: auto;
}

/* A FRAME THAT HAS NOT ANSWERED IS NOT DRAWN.
   `display:none` rather than a transparent frame, for two reasons that are
   both about it not being half-there: a hidden-but-laid-out iframe still takes
   the flex space the waiting panel needs, and a frame you cannot see is still
   a Tab stop, so the keyboard would land in a page nobody can read. */
.ws-frame[data-ready="no"] { display: none; }

/* The panel in its place. Same parchment, so a door that is opening looks like
   part of the sheet rather than a hole in it -- the state is what the words
   say, not a change of material. */
.ws-frame-state {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  padding: 24px;
  text-align: center;
  color: var(--wc-ink-on-parchment, #2a1608);
}

.ws-frame-head {
  font-family: var(--wc-head, 'Press Start 2P', monospace);
  font-size: 12px;
  letter-spacing: .06em;
}

/* WAITING BREATHES, FAILED DOES NOT. A pulse says the castle is still working;
   holding it still on the failed panel is the difference between the two
   states at a glance, before either has been read. */
.ws-frame-state[data-state="waiting"] .ws-frame-head { animation: ws-breathe 1.4s ease-in-out infinite; }

@keyframes ws-breathe {
  0%, 100% { opacity: 1; }
  50% { opacity: .45; }
}

/* Somebody who has asked not to be moved is not moved. */
@media (prefers-reduced-motion: reduce) {
  .ws-frame-state[data-state="waiting"] .ws-frame-head { animation: none; }
}

.ws-frame-sub {
  font-size: 17px;
  line-height: 1.3;
  opacity: .75;
  max-width: 34ch;
}

.ws-frame-act { margin-top: 4px; }

/* WHICH TAB YOU ARE ON. The plank is a 9-slice and its ends are authored art,
   so the marker cannot be a border -- it would fight the drawn one. It is a
   bar under the label instead, inside the plank's own padding, plus the ink
   going to full strength. */
.ws-tabs .wr-plank[aria-current="page"] {
  filter: brightness(1.14);
  box-shadow: inset 0 -4px 0 var(--wc-ink-on-leather, #e8c675);
}

/* The room list is the one door with no page behind it: the rooms are the
   engine's own, so it is built from __HALL.rooms rather than framed. */
.ws-rooms {
  flex: 1 1 auto;
  overflow: auto;
  display: grid;
  gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  align-content: start;
  padding: 12px;
}

.ws-room-sub {
  font-size: 15px;
  line-height: 1.25;
  opacity: .7;
  padding: 2px 3px 0;
}

/* ── Mug's bar, inside the sheet (PR #2040) ──────────────────────────────
 * The bar is not a surface of its own: it is content the tavern's keeper
 * puts in the shell's one sheet (public/js/world-bar.js). These rules only
 * dress what it builds — the sheet, its header and its CLOSE are the
 * shell's and are untouched.
 *
 * The buttons are `.wr-plank`, the rail's own plank, so they inherit the
 * drawn chrome. Nothing here describes a look; it describes a layout. */
.tb-line {
  font-size: 17px;
  line-height: 1.3;
  opacity: .85;
  margin: 0 0 14px;
}

.tb-row {
  margin: 0 0 16px;
}

.tb-row:last-child { margin-bottom: 0; }

.tb-name {
  font-size: 17px;
  line-height: 1.2;
}

.tb-desc {
  font-size: 15px;
  line-height: 1.25;
  opacity: .7;
  margin: 0 0 8px;
}

/* Two acts, side by side, so the pair reads as one decision. They wrap on a
 * narrow sheet rather than being squeezed: a plank is 9-sliced and its ends
 * are fixed size, so a squeezed one distorts. */
.tb-act {
  margin: 0 8px 0 0;
}

}
/* end of @layer base */
