.foundry-surface-root,
.foundry-surface-root * {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  cursor: default;
}

/* main.css's universal `* { font-family: Arial }` BEATS inheritance, so
   without this rule every JS-built element renders Arial until its final
   classed rule arrives — the visible font switch when entering Moon Fuse.
   Descendants inherit the surface's themed family from frame one; the root
   itself keeps its own declaration below (inherit there would pull Arial
   from body). */
.foundry-surface-root * {
  font-family: inherit;
}

.foundry-surface-root {
  height: 100%;
  overflow: hidden;
  font-family: "Spectral", serif;
  background: #1a1a2e;
  color: #eee;
}

/* A preload fetch is not a decoded FontFace. On a cold first entry the
   template therefore used to paint Times/Arial for a frame and then jump to
   Spectral/Cinzel. The host's opaque background remains immediate, but its
   text can paint only after foundry-surface.js has activated all themed faces. */
.foundry-surface-host.foundry-fonts-pending .foundry-surface-root {
  visibility: hidden;
}

/* A failed or abnormally slow font request gets one stable, truthful fallback
   for this mount. Even if the late request eventually succeeds, it cannot
   introduce the same mid-session font swap we are preventing. */
.foundry-surface-host.foundry-fonts-fallback .foundry-surface-root,
.foundry-surface-host.foundry-fonts-fallback .foundry-surface-root *,
/* Moon Fuse's popups are appended to <body>, outside the surface root, so the
   root-scoped rule left them on the themed faces while the surface itself was
   forced to Georgia — two typefaces on one screen. */
body.foundry-fonts-fallback #card-hover-preview,
body.foundry-fonts-fallback #card-hover-preview *,
body.foundry-fonts-fallback #card-side-hint,
body.foundry-fonts-fallback #foundry-notice-stack,
body.foundry-fonts-fallback #foundry-notice-stack * {
  font-family: Georgia, serif !important;
}

.foundry-surface-root :where(input, textarea, button, select) {
  font-family: inherit;
}

.foundry-surface-root .hidden {
  display: none !important;
}

#foundry-app {
  display: flex;
  flex-direction: column;
  height: 100%;
  -webkit-user-select: none;
  user-select: none;
}

#foundry-app input,
#foundry-app textarea {
  -webkit-user-select: text;
  user-select: text;
}

.foundry-tabs {
  position: relative;
  display: flex;
  align-items: center;
  gap: clamp(6px, 0.85vw, 12px);
  padding: 8px 12px;
  min-height: calc(var(--mf-rail-btn) + 16px);
  /* NOTHING IS FIXED ABOVE THIS BAR ANY MORE. The Foundry does not paint the
     chrome menu — it has its own Lobby control and its own language select —
     so this reserves the edge inset only, not a corner control's footprint.
     It used to reserve `edge + fullscreen-slot + btn`, which after the collapse
     was a button-and-a-half of empty gutter at the end of the tab bar. */
  padding-right: calc(var(--mf-rail-edge) + 8px);
  background: #101020;
  border-bottom: 1px solid #2a2a3a;
}

.foundry-tablist {
  display: flex;
  align-items: center;
  gap: clamp(4px, 0.6vw, 10px);
  min-width: 0;
}

.foundry-context-tools {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  justify-content: flex-start;
}

.foundry-tab-tools {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  min-width: 0;
  width: 100%;
}

.foundry-tab-tools[hidden] {
  display: none !important;
}

.foundry-tab-tools[data-foundry-tools-for="moon-fuse-panel"] {
  justify-content: flex-start;
}

.foundry-tab-tools[data-foundry-tools-for="moon-fuse-panel"] .foundry-balance-pill {
  margin-left: auto;
}

/* Desktop keeps only the balance control in this command bar; Moon Fuse search
   lives above the inventory list. Compact layouts relocate that same search
   field into the toolbar through foundry-surface.js. */
@media (min-width: 769px) and (min-height: 561px) {
  .foundry-tab-tools[data-foundry-tools-for="moon-fuse-panel"] {
    justify-content: flex-end;
  }

  .foundry-tab-tools[data-foundry-tools-for="moon-fuse-panel"] .foundry-balance-pill {
    margin-left: 0;
  }
}

.foundry-language-control {
  display: flex;
  flex: 0 1 clamp(96px, 10vw, 148px);
  min-width: 82px;
}

.foundry-language-select {
  width: 100%;
  max-width: 100%;
  min-height: 34px;
  padding: 5px 28px 5px 10px;
  border: 1px solid rgba(198, 200, 232, 0.42);
  border-radius: 999px;
  background: #17172b;
  color: #f4f4ff;
  font-size: 13px;
  cursor: pointer;
}

.foundry-language-select:hover {
  border-color: rgba(246, 226, 122, 0.72);
  background: #1e1e34;
}

.foundry-language-select:focus-visible {
  outline: 3px solid #f6e27a;
  outline-offset: 2px;
}

/* A FOUNDRY TAB NAME IS NEVER ABBREVIATED.
   `foundry-tab-fit.js` measures the labels against the room the row actually
   has and publishes ONE shared scale, `--foundry-tab-fit`, which every tab
   font-size below multiplies by. The type is the thing that yields; the word
   never is. `max(11px, ...)` is the floor, written into the sheet rather than
   trusted to the script: 11px is the repo's published phone-button legibility
   minimum (mobile-defects.real.spec.js), and an unreadable name is not a
   better outcome than a shortened one. */
.foundry-tab {
  background: #17172b;
  border: 1px solid #2a2a3a;
  color: #c6c8e8;
  font-family: "Cinzel", serif;
  font-size: max(11px, 13px * var(--foundry-tab-fit, 1));
  font-weight: 600;
  letter-spacing: 0.04em;
  min-height: 34px;
  padding: 6px clamp(9px, 1vw, 16px);
  border-radius: 999px;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s, color 0.2s;
}

.foundry-tab:hover {
  background: #1e1e34;
  border-color: #3a3a5a;
}

.foundry-tab.active {
  background: #262645;
  color: #f6e27a;
  border-color: #4c4c76;
}

.foundry-tab:focus-visible {
  outline: 2px solid rgba(150, 136, 255, 0.9);
  outline-offset: 2px;
}

/* The Lobby option is an action button (exit the foundry), not a panel
   toggle. Anchored at the LEFT edge of the tab bar so it stays clear of
   the fixed-position language selector at the right of the viewport. */
.foundry-tab.foundry-tab-lobby {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  color: #f0d8a0;
  border-color: #5a4628;
  background: rgba(40, 28, 14, 0.55);
  margin-right: 4px;
}

/* The measurable name. An inline-block that never wraps reports the width its
   text WANTS even while it overflows its button, which is what makes a real
   fit possible; a bare text node reports nothing of the kind. */
.foundry-tab-label {
  display: inline-block;
  white-space: nowrap;
}

.foundry-tab-lobby-glyph {
  line-height: 1;
}

.foundry-tab.foundry-tab-lobby:hover {
  background: rgba(60, 40, 18, 0.75);
  border-color: rgba(255, 215, 130, 0.7);
  color: #fff5d8;
}

.foundry-tab.foundry-tab-lobby:focus-visible {
  outline: 2px solid rgba(255, 215, 130, 0.85);
  outline-offset: 2px;
}

/* Touch ergonomics: 44px tap height for tabs (incl. Lobby); desktop keeps the compact pills. */
@media (pointer: coarse) {
  .foundry-tab {
    min-height: 44px;
  }
}

#foundry-panels {
  flex: 1;
  min-height: 0;
  display: flex;
}

.foundry-panel {
  flex: 1;
  min-height: 0;
  display: none;
}

.foundry-panel.active {
  display: flex;
}

#moon-fuse-panel {
  position: relative;
}

.foundry-workspace-notice {
  position: absolute;
  z-index: 120;
  top: 12px;
  left: 50%;
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: min(680px, calc(100% - 320px));
  padding: 10px 12px;
  border: 1px solid rgba(246, 226, 122, 0.42);
  border-radius: 10px;
  background: rgba(16, 16, 32, 0.96);
  color: #f2e8bd;
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.38);
  font-size: 13px;
  line-height: 1.35;
  transform: translateX(-50%);
}

.foundry-workspace-notice button {
  flex: 0 0 auto;
  padding: 7px 11px;
  border: 1px solid #6c5a2c;
  border-radius: 8px;
  background: #2a2518;
  color: #f6e27a;
  cursor: pointer;
}

.foundry-workspace-notice button:hover,
.foundry-workspace-notice button:focus-visible {
  border-color: #f6e27a;
  background: #38301c;
}

.element.awaiting-art-selection {
  border-color: rgba(246, 226, 122, 0.72);
  box-shadow: 0 4px 18px rgba(246, 226, 122, 0.18);
}

.element.awaiting-art-selection:focus-visible {
  outline: 2px solid #f6e27a;
  outline-offset: 3px;
}

#moon-dream-panel {
  flex-direction: column;
  overflow: hidden;
  background: radial-gradient(circle at top, rgba(36, 36, 68, 0.8), rgba(14, 14, 26, 0.95));
}

/* Play Area */
#play-area {
  flex: 1;
  position: relative;
  background: linear-gradient(135deg, #16213e 0%, #0f0f1a 100%);
  overflow: hidden;
  touch-action: none;
}

#canvas-elements {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

#hint-text {
  position: absolute;
  bottom: 30px;
  left: 50%;
  transform: translateX(-50%);
  color: rgba(255, 255, 255, 0.3);
  font-size: 14px;
  max-width: min(80%, 620px);
  text-align: center;
  pointer-events: none;
}

.foundry-cost-note {
  font-size: clamp(10px, 0.9vmin, 13px);
  line-height: 1.2;
  color: #9a94c2;
  white-space: nowrap;
}

.foundry-tab-tools {
  display: flex;
  align-items: center;
  gap: 10px;
}

.moon-fuse-cost {
  display: block;
  margin-top: 5px;
  color: rgba(255, 255, 255, 0.72);
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}

/* Draggable Elements */
.element {
  position: absolute;
  background: #2a2a4a;
  border: 1px solid #4a4a6a;
  border-radius: 8px;
  padding: 8px 16px;
  cursor: grab;
  -webkit-user-select: none;
  user-select: none;
  display: flex;
  align-items: center;
  gap: 8px;
  font-family: "Spectral", serif;
  font-weight: bold;
  font-size: 14px;
  letter-spacing: 0.15px;
  text-transform: uppercase;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  transition:
    transform 0.1s,
    box-shadow 0.1s;
  z-index: 10;
  touch-action: none;
}

.element.loading::after {
  content: "";
  width: 12px;
  height: 12px;
  margin-left: 6px;
  border: 2px solid rgba(122, 122, 170, 0.6);
  border-top-color: #7a7aff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

.element.first-discovered {
  /* Per-card deterministic hue/saturation/lightness stamped by
     discovery-palette.js; the variable bands keep light text legible. */
  background: linear-gradient(
    135deg,
    hsl(var(--fd-hue-a, 262) var(--fd-sat-a, 39%) var(--fd-light-a, 31%)),
    hsl(var(--fd-hue-b, 38) var(--fd-sat-b, 49%) var(--fd-light-b, 24%))
  );
}

.element.first-discovered:hover {
  background: linear-gradient(
    135deg,
    hsl(var(--fd-hue-a, 262) var(--fd-sat-a, 39%) calc(var(--fd-light-a, 31%) + 9%)),
    hsl(var(--fd-hue-b, 38) var(--fd-sat-b, 49%) calc(var(--fd-light-b, 24%) + 9%))
  );
}

.element:hover {
  transform: scale(1.05);
  box-shadow: 0 6px 20px rgba(100, 100, 200, 0.3);
  border-color: #7a7aaa;
}

.element:focus-visible {
  outline: 2px solid rgba(185, 177, 255, 0.95);
  outline-offset: 2px;
  border-color: rgba(185, 177, 255, 0.95);
}

.element.dragging {
  cursor: grabbing;
  opacity: 0.8;
  z-index: 100;
}

.element .emoji {
  font-size: 18px;
}

/* Sidebar */
#sidebar {
  width: 280px;
  background: #12121f;
  display: flex;
  flex-direction: column;
  border-left: 1px solid #2a2a3a;
}

/* The phone header is ONE 53px row carrying the Lobby button, both tab
   buttons, the cost note, the Stardust pill, the language picker and the
   inventory search. Measured on a 667x275 page box the Moon Dream tab ran 12px
   into the search field. Shortening the cost note reclaimed most of it; the
   rest comes from the tabs, which had desktop padding and no permission to
   shrink. They keep their 44px tap height — only the horizontal padding and
   the label yield. */
/* Conditions ordered width-first on purpose: a block that opens with the same
   prefix as the shared compact-landscape query is picked up by tests that
   extract that block by its opening string. */
@media (max-width: 760px) and (orientation: landscape) and (max-height: 560px) {
  #foundry-tabs .foundry-tablist .foundry-tab {
    padding-inline: 8px;
    min-width: 0;
    flex: 0 1 auto;
    /* The label is sized to fit, so nothing here may cut it short. */
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
  }

  /* A belt-and-braces clip: whatever the tabs end up measuring, they can never
     PAINT outside the lane the row allotted them and onto the search field. */
  #foundry-tabs .foundry-tablist {
    flex: 0 1 auto;
    min-width: 0;
    overflow: hidden;
  }
}

/* THE SAME CLAMP, KEYED ON WHERE THE SEARCH ACTUALLY IS.
   The media block above protects the tabs only at `max-width: 760px` in
   landscape under 560px tall. `syncMoonFuseSearchPlacement()` moves the search
   into this header on a broader condition — portrait ≤768px wide, or landscape
   ≤560px tall at ANY width — so 932x300, 852x344 and every portrait phone put
   the field beside the tabs with nothing stopping them meeting. This rule fires
   off the placement the JS just performed, so the protection can never again
   cover a smaller set of viewports than the relocation does. */
[data-foundry-search-placement="header"] #foundry-tabs .foundry-tablist .foundry-tab {
  padding-inline: 8px;
  min-width: 0;
  flex: 0 1 auto;
  /* THIS RULE IS WHY THE NAMES WERE SHORTENED. It carries an id, so at (1,3,0)
     it silently outranked the (0,2,0) `overflow: visible; text-overflow: clip`
     written 3700 lines below to protect exactly these labels — on every phone
     in both orientations and on every portrait viewport up to 768px, which is
     precisely where the room is scarce. Measured French at 852x344 with a real
     notch: "Rêve lunaire" lost 8px with this rule, 0px without it.
     The width still has to come from somewhere; it now comes from the type
     (`--foundry-tab-fit`) and from the price note, never from the word. */
  overflow: visible;
  text-overflow: clip;
  white-space: nowrap;
}

[data-foundry-search-placement="header"] #foundry-tabs .foundry-tablist {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
}

/* AND THE LANE ITSELF, for the same reason. The rule above also outranked
   `.foundry-tabs .foundry-tablist { flex: 0 0 auto; min-width: max-content }`
   at (1,2,0) vs (0,2,0), so the lane stayed shrinkable and its `overflow:
   hidden` — kept above as the guarantee that a tab can never PAINT onto the
   search field — became a guillotine instead of a guard rail. Pinned to its
   content the clip has nothing to cut.

   Above 600px only: at or below that the bar is a three-column GRID and the
   lane is a grid item, where `max-content` would overflow its track instead of
   widening it. There the tabs get their room from the fitted type. */
@media (min-width: 601px) {
  [data-foundry-search-placement="header"] #foundry-tabs .foundry-tablist {
    flex: 0 0 auto;
    min-width: max-content;
  }
}

.foundry-inventory-search-slot {
  flex: 0 0 auto;
  padding: 12px 12px 0;
}

.foundry-inventory-search-slot:empty {
  display: none;
}

.foundry-inventory-search-slot > #search-input {
  width: 100%;
  max-width: none;
}

#search-input {
  /* Slightly shorter than the old clamp(112px, 18vw, 248px). The field was the
     widest discretionary thing in the foundry header, so it was the one taking
     the millimetres the Moon Dream tab beside it needed; it loses them first. */
  flex: 0 1 clamp(104px, 15.5vw, 208px);
  width: clamp(104px, 15.5vw, 208px);
  min-width: 0;
  min-height: 34px;
  margin: 0;
  padding: 7px 11px;
  background: #1a1a2e;
  border: 1px solid #3a3a5a;
  border-radius: 6px;
  color: #eee;
  font-size: 14px;
}

#search-input::placeholder {
  color: #666;
}

/* Touch ergonomics: 44px tap height for the search field on touch devices. */
@media (pointer: coarse) {
  #search-input {
    min-height: 44px;
  }
}

.stardust-display {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 10px;
  background: #18182a;
  border: 1px solid #2a2a3a;
  border-radius: 6px;
  font-size: 13px;
  color: #cfc7ff;
  white-space: nowrap;
}

.foundry-balance-pill {
  -webkit-appearance: none;
  appearance: none;
  flex: 0 0 auto;
  min-height: 34px;
  border-radius: 999px;
  margin: 0;
  color: inherit;
  font: inherit;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease, box-shadow 0.2s ease;
}

.foundry-balance-pill:hover:not([disabled]) {
  background-color: #24243a;
  border-color: rgba(246, 226, 122, 0.52);
  box-shadow: 0 0 12px rgba(246, 226, 122, 0.12);
}

.foundry-balance-pill:focus-visible {
  outline: 2px solid #f6e27a;
  outline-offset: 2px;
}

.foundry-tab-tools .foundry-balance-pill {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  line-height: 1;
}

.stardust-display span:last-child {
  font-weight: 600;
  color: #f6e27a;
}

#element-list {
  --foundry-inventory-item-min-width: 180px;
  --foundry-inventory-item-height: 60px;
  flex: 1;
  overflow-x: hidden;
  overflow-y: hidden;
  padding: 12px;
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(min(var(--foundry-inventory-item-min-width), 100%), 1fr)
  );
  grid-auto-rows: var(--foundry-inventory-item-height);
  align-content: flex-start;
  gap: 8px;
  touch-action: manipulation;
}

.sidebar-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 12px;
  border-top: 1px solid #2a2a3a;
  background: #12121f;
}

.sidebar-pagination button {
  background: #1c1c32;
  border: 1px solid #30304a;
  color: #c6c8e8;
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 12px;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}

.sidebar-pagination button:disabled {
  opacity: 0.5;
  cursor: default;
}

.sidebar-pagination button:not(:disabled):hover {
  background: #262645;
  border-color: #4c4c76;
}

/* Touch ergonomics: 44px tap height for Prev/Next pager buttons on touch devices. */
@media (pointer: coarse) {
  .sidebar-pagination button {
    min-height: 44px;
  }
}

.sidebar-page-info {
  font-size: 11px;
  color: rgba(198, 200, 232, 0.75);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.sidebar-element {
  background: #2a2a4a;
  border: 1px solid #3a3a5a;
  border-radius: 6px;
  padding: 8px 12px;
  cursor: grab;
  font-family: "Spectral", serif;
  font-weight: bold;
  font-size: 13px;
  letter-spacing: 0.15px;
  text-transform: uppercase;
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
  width: 100%;
  color: #eee;
  text-align: left;
  -webkit-appearance: none;
  appearance: none;
  transition: background 0.15s;
  min-height: var(--foundry-inventory-item-height);
  height: var(--foundry-inventory-item-height);
  overflow: hidden;
}

.sidebar-element:active {
  cursor: grabbing;
}

.sidebar-element.first-discovered {
  /* Same deterministic per-card gradient as the canvas chip. */
  background: linear-gradient(
    135deg,
    hsl(var(--fd-hue-a, 262) var(--fd-sat-a, 39%) var(--fd-light-a, 31%)),
    hsl(var(--fd-hue-b, 38) var(--fd-sat-b, 49%) var(--fd-light-b, 24%))
  );
}

.sidebar-element.first-discovered:hover {
  background: linear-gradient(
    135deg,
    hsl(var(--fd-hue-a, 262) var(--fd-sat-a, 39%) calc(var(--fd-light-a, 31%) + 9%)),
    hsl(var(--fd-hue-b, 38) var(--fd-sat-b, 49%) calc(var(--fd-light-b, 24%) + 9%))
  );
}

.sidebar-element:hover {
  background: #3a3a5a;
}

.sidebar-element:focus-visible {
  outline: 2px solid rgba(150, 136, 255, 0.95);
  outline-offset: -2px;
  border-color: rgba(185, 177, 255, 0.95);
}

.foundry-workspace-read-only #play-area .element,
.foundry-workspace-read-only #sidebar .sidebar-element {
  cursor: not-allowed;
}

.sidebar-element > span:nth-child(2) {
  min-width: 0;
  overflow: hidden;
  white-space: normal;
  overflow-wrap: anywhere;
  line-height: 1.15;
}

.sidebar-element .emoji {
  font-size: 16px;
}

.foundry-inspect-glyph {
  flex: 0 0 auto;
  margin-left: auto;
  color: rgba(226, 218, 255, 0.82);
  font-family: ui-sans-serif, system-ui, sans-serif;
  font-size: 14px;
  line-height: 1;
  text-shadow: 0 1px 6px rgba(0, 0, 0, 0.72);
  pointer-events: none;
}

.foundry-inspect-glyph + .sidebar-stellar-badge {
  margin-left: 0;
}

.sidebar-stellar-badge {
  margin-left: auto;
  color: rgba(120, 180, 255, 0.85);
  font-size: 14px;
  flex-shrink: 0;
}

.trash-zone {
  position: absolute;
  left: 20px;
  bottom: 20px;
  padding: 10px 14px;
  border-radius: 6px;
  border: 1px dashed #533;
  background: rgba(27, 22, 33, 0.95);
  color: #d8b7b7;
  font-size: 12px;
  text-align: center;
  transition:
    background 0.15s ease,
    border-color 0.15s ease,
    color 0.15s ease;
  z-index: 40;
  cursor: pointer;
}

.trash-zone.active {
  background: rgba(58, 31, 42, 0.95);
  border-color: #ff7a7a;
  color: #fff;
}

.stellar-toggle.active {
  background: #5a5aff;
  border-color: #7a7aff;
  color: #fff;
}

/* Modal */
.foundry-surface-root .modal {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  padding:
    max(12px, var(--mf-safe-top))
    max(12px, var(--mf-safe-right))
    max(12px, var(--mf-safe-bottom))
    max(12px, var(--mf-safe-left));
  box-sizing: border-box;
  -webkit-user-select: none;
  user-select: none;
  /* The chooser owns taps, never page pan/pinch/double-tap magnification. */
  touch-action: manipulation;
}

.foundry-surface-root .modal.hidden {
  display: none;
}

.foundry-surface-root .modal-box {
  box-sizing: border-box;
  background: #1a1a2e;
  border: 1px solid #3a3a5a;
  border-radius: 12px;
  padding: clamp(16px, 2.2vmin, 32px) clamp(16px, 2.8vmin, 40px) clamp(18px, 2.5vmin, 36px);
  text-align: center;
  min-width: 0;
  width: min(calc(100vw - 24px), clamp(960px, 74vw, 1680px));
  max-width: 100%;
  /* dvh: svh never grows back when a phone hides its URL bar, which left
     this panel short of the page box until reload. */
  max-height: min(92dvh, 1100px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

.foundry-surface-root .spinner {
  width: 40px;
  height: 40px;
  border: 3px solid #3a3a5a;
  border-top-color: #7a7aff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
  margin: 0 auto 16px;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

#result-name {
  font-family: "Cinzel", serif;
  font-weight: 700;
  font-size: clamp(20px, 1.55vmin, 32px);
  margin-bottom: 4px;
}

#result-desc {
  color: #888;
  font-size: clamp(13px, 1vmin, 20px);
  margin-bottom: 10px;
}

#modal-result {
  display: flex;
  flex-direction: column;
  gap: 8px;
  overflow: hidden;
  padding-bottom: 12px;
  min-width: 0;
}

.art-options-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  text-align: left;
}

.art-actions {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  flex-wrap: wrap;
  gap: 8px;
}

#art-instructions {
  flex: 1;
  margin: 0;
  font-size: clamp(12px, 0.9vmin, 18px);
  color: #b9b9d6;
}

#art-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: clamp(8px, 1.2vmin, 20px);
  margin: clamp(10px, 1.2vmin, 20px) 0 6px;
  max-height: min(70svh, 820px);
  overflow-y: auto;
  padding-right: 6px;
  min-width: 0;
  overscroll-behavior: contain;
  touch-action: manipulation;
}

#art-grid .art-choice {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 5;
  min-width: 0;
  padding: 0;
  border-radius: 14px;
  cursor: pointer;
  border: 2px solid transparent;
  background: transparent;
  overflow: hidden;
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.35);
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  touch-action: manipulation;
}

#art-grid .art-choice > :is(img, canvas) {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  border-radius: inherit;
  pointer-events: none;
  -webkit-touch-callout: none;
  -webkit-user-drag: none;
  touch-action: manipulation;
}

#art-grid.art-grid-preparing,
#art-grid[aria-busy="true"] {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: clamp(108px, 20vmin, 220px);
  pointer-events: none;
}

#art-grid.art-grid-preparing .art-choice,
#art-grid[aria-busy="true"] .art-choice {
  position: absolute;
  inline-size: 1px;
  block-size: 1px;
  opacity: 0;
}

#art-grid.art-grid-preparing::after,
#art-grid[aria-busy="true"]::after {
  content: "";
  width: clamp(32px, 4vmin, 54px);
  aspect-ratio: 1;
  border: 3px solid rgba(122, 122, 255, 0.25);
  border-top-color: #a8a8ff;
  border-radius: 50%;
  animation: spin 850ms linear infinite;
}

#art-grid .art-choice[aria-disabled="true"] {
  pointer-events: none;
}

#art-grid .art-choice:hover {
  border-color: #7a7aff;
}

#art-grid .art-choice.selected {
  border-color: #fff;
}

#art-grid .art-choice:focus-visible {
  outline: 3px solid #bfc0ff;
  outline-offset: 3px;
  border-color: #fff;
}

#confirm-btn {
  background: #5a5aff;
  color: white;
  border: none;
  padding: 10px 18px;
  border-radius: 8px;
  cursor: pointer;
  font-family: "Cinzel", serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  align-self: center;
  position: static;
  z-index: 1;
  margin-top: 0;
  min-height: 44px;
}

#confirm-btn:hover {
  background: #7a7aff;
}

#confirm-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

#art-close-btn,
#retry-btn {
  background: #2a2a44;
  color: #e2e2ff;
  border: 1px solid #3a3a5a;
  padding: 10px 16px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 13px;
  min-height: 44px;
}

#art-close-btn:hover,
#retry-btn:hover {
  background: #3a3a5a;
}

#art-close-btn:disabled,
#retry-btn:disabled {
  opacity: 0.6;
  cursor: not-allowed;
}

#art-retry-error {
  color: #f7c873;
  font-size: 12px;
  margin: 4px 0 0;
  text-align: left;
}

@media (max-width: 760px) and (orientation: portrait) {
  .foundry-surface-root .modal {
    padding:
      max(8px, var(--mf-safe-top))
      max(8px, var(--mf-safe-right))
      max(8px, var(--mf-safe-bottom))
      max(8px, var(--mf-safe-left));
  }

  .foundry-surface-root .modal-box {
    width: 100%;
    height: 100%;
    max-height: 100%;
    padding: 10px;
    overflow: hidden;
  }

  #modal-result,
  #art-options {
    flex: 1 1 auto;
    min-height: 0;
  }

  #modal-result {
    gap: 5px;
    padding-bottom: 0;
  }

  #art-options {
    display: flex;
    flex-direction: column;
  }

  #result-name {
    margin-bottom: 0;
    font-size: clamp(17px, 5vw, 21px);
    line-height: 1.05;
  }

  #result-desc {
    /* Never let flex sizing shave the final line by a fractional pixel. The
       art grid absorbs the remaining height instead. */
    flex: 0 0 auto;
    margin-bottom: 1px;
    max-height: none;
    overflow: visible;
    padding-block: 1px;
    font-size: clamp(11px, 3vw, 13px);
    line-height: 1.2;
  }

  .art-options-header {
    flex: 0 0 auto;
    align-items: flex-start;
    gap: 6px;
  }

  .art-actions {
    flex-wrap: nowrap;
  }

  #art-grid {
    flex: 1 1 0;
    min-height: 0;
    grid-template-columns: repeat(var(--art-choice-columns, 2), minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr));
    align-items: center;
    justify-content: center;
    gap: 7px;
    margin: 5px 0 0;
    max-height: none;
    overflow: hidden;
    padding-right: 0;
  }

  #art-grid .art-choice {
    width: auto;
    max-width: 100%;
    height: 100%;
    min-height: 0;
    justify-self: center;
  }
}

@media (max-width: 760px) and (orientation: landscape),
       (max-height: 560px) and (orientation: landscape) {
  .foundry-surface-root .modal {
    padding:
      max(6px, var(--mf-safe-top))
      max(8px, var(--mf-safe-right))
      max(6px, var(--mf-safe-bottom))
      max(8px, var(--mf-safe-left));
  }

  .foundry-surface-root .modal-box {
    width: min(100%, 1100px);
    height: 100%;
    max-height: 100%;
    padding: 10px 14px 12px;
    overflow: hidden;
  }

  #modal-result,
  #art-options {
    flex: 1 1 auto;
    min-height: 0;
  }

  #modal-result {
    gap: 4px;
    padding-bottom: 0;
  }

  #art-options {
    display: flex;
    flex-direction: column;
  }

  #result-name {
    font-size: clamp(17px, 3.2vh, 22px);
    line-height: 1.05;
    margin-bottom: 0;
  }

  #result-desc {
    /* Keep the complete description as one physical block; the art row is
       the only flexible lane in this fixed-height phone dialog. */
    flex: 0 0 auto;
    max-height: none;
    margin-bottom: 2px;
    overflow: visible;
    padding-block: 1px;
    font-size: clamp(11px, 2.5vh, 14px);
    line-height: 1.2;
  }

  .art-options-header {
    gap: 8px;
    flex: 0 0 auto;
  }

  .art-actions {
    flex-wrap: nowrap;
  }

  #art-grid {
    flex: 1 1 0;
    min-height: 0;
    grid-template-columns: repeat(var(--art-choice-count, 4), minmax(0, 1fr));
    grid-template-rows: minmax(0, 1fr);
    align-items: center;
    gap: 8px;
    margin: 6px 0 0;
    max-height: none;
    overflow: hidden;
    padding-right: 0;
  }

  #art-grid .art-choice {
    width: auto;
    max-width: 100%;
    height: 100%;
    min-height: 0;
    justify-self: center;
  }
}

@media (prefers-reduced-motion: reduce) {
  #art-grid.art-grid-preparing::after,
  #art-grid[aria-busy="true"]::after {
    animation-duration: 1800ms;
  }
}

.card-hover-preview {
  position: fixed;
  z-index: 2147483647;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
  /* No ancestor filter: it softens the subtree and makes Gecko re-snap
     glyphs per tilt frame — see deck-builder.css's matching rule. And no
     shadow on THIS box: the host measures wider than the card it centres
     (393px vs 275px live), so a host shadow painted a rectangle whose edge
     crossed the canvas chips. The card below owns the depth. */
  filter: none;
}

.card-hover-preview > .card {
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.75);
}

.card-hover-preview .card {
  --card-hover-transform: scale(1);
  --card-base-transform: scale(1);
  transform-origin: center center;
  transition: none;
}

/* Moon Dream */
.moon-dream-header {
  position: relative;
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  gap: 5px;
  padding: 9px 16px;
  border-bottom: 1px solid #2a2a3a;
  background: linear-gradient(90deg, rgba(12, 12, 24, 0.9), rgba(23, 19, 43, 0.78));
}

.moon-dream-create-strip[hidden] {
  display: none !important;
}

.moon-dream-create-controls {
  display: flex;
  flex-direction: column;
  gap: 4px;
  min-width: 0;
}

.moon-dream-header h2 {
  font-family: "Cinzel", serif;
  font-weight: 700;
  font-size: 22px;
  color: #f7f1ff;
  margin-bottom: 6px;
}

.moon-dream-surface-title {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.moon-dream-header p {
  color: rgba(255, 255, 255, 0.65);
  font-size: 13px;
}

.moon-dream-stardust {
  /* Visually identical to .stardust-display so both tabs share one pill. */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  background: #18182a;
  border: 1px solid #2a2a3a;
  padding: 6px 10px;
  min-width: 0;
  text-align: left;
}

.moon-dream-stardust-label {
  font-size: 13px;
  color: #cfc7ff;
}

.moon-dream-stardust-value {
  font-size: 13px;
  font-weight: 600;
  color: #f6e27a;
}

.moon-dream-cost {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.72);
  font-weight: 600;
}

.moon-dream-create-meta {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  min-width: 0;
}

.moon-dream-content {
  display: grid;
  grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  gap: 12px;
  padding: 12px;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

/* A run-less desktop is one calm empty canvas, not two competing placeholders.
   Phones keep their master/detail state machine and are excluded below. */
@media (orientation: landscape) and (min-height: 561px),
       (orientation: portrait) and (min-width: 769px) {
  .moon-dream-content[data-library-empty="true"] {
    grid-template-columns: minmax(0, 1fr);
  }

  .moon-dream-content[data-library-empty="true"] .moon-dream-right {
    display: none;
  }
}

.moon-dream-left,
.moon-dream-right {
  display: flex;
  flex-direction: column;
  gap: 16px;
  min-height: 0;
}

.moon-dream-history-rail {
  padding: 12px;
  overflow: hidden;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(18, 17, 36, 0.72), rgba(10, 10, 21, 0.42));
}

.moon-dream-runs {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}

.moon-dream-right {
  gap: 0;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  border: 0;
  border-radius: 12px;
  background: linear-gradient(180deg, rgba(18, 17, 36, 0.68), rgba(10, 10, 21, 0.38));
}

/* A finished Moon Dream is a gallery, not a generation log. The wide summoner
   portrait and a complete page of physical cards share the available canvas;
   the compact lore/delete block remains secondary and no nested scrollbar is
   introduced. Generation-stage details retain the workflow layout below. */
.moon-dream-right.moon-dream-detail--complete {
  position: relative;
  display: grid;
  /* The summoner's column, not a sidebar. A Moon Dream is a paid,
     minutes-long generation and the picture is its payoff, so the column that
     holds it gets a share of the panel comparable to the deck's. The deck loses
     nothing measurable: its cards are sized by `--catalog-card-scale`, not by
     the column, and four of them occupy well under half the width this leaves. */
  grid-template-columns: minmax(260px, 0.85fr) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr) auto;
  gap: 10px;
  padding: 10px;
  overflow: hidden;
}

.moon-dream-detail--complete > .moon-dream-detail-command {
  position: absolute;
  inset: 6px auto auto 6px;
  z-index: 12;
  border: 0;
  border-radius: 8px;
  overflow: hidden;
}

.moon-dream-detail--complete .moon-dream-showcase {
  grid-column: 1;
  grid-row: 1;
  min-height: 0;
  justify-content: center;
  padding: 10px;
  border: 1px solid rgba(246, 226, 122, 0.22);
  border-radius: 14px;
  background: radial-gradient(circle at 50% 40%, rgba(103, 85, 220, 0.24), rgba(12, 10, 26, 0.2) 72%);
}

.moon-dream-detail--complete .moon-dream-showcase > .moon-dream-detail-title {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* NO FIXED WIDTH CAP. A 520px ceiling meant that on a 1920x1080 screen the portrait
   sat at 520x293 in the middle of a 660x780 column — the hero of the screen
   rendered at a quarter of the room it had been given, which is most of what
   "laughably small" was about. The column bounds it here; the wide-short rule
   below additionally constrains it by row height. */
.moon-dream-detail--complete .moon-dream-portrait-frame {
  width: 100%;
  max-width: none;
  aspect-ratio: 16 / 9;
  border-radius: 16px;
}

/* Wide, short desktop panels can give the left column more width than its
   first row has 16:9 height. Size the hero against BOTH axes: 100cqh is the
   showcase content height; 30px is the caption line plus its gap, converted
   to width at 16:9 (30 * 16/9 = 53.333). Container units keep this coupled to
   the real grid row rather than a viewport-height guess. */
@media (min-width: 1367px) and (min-height: 561px) {
  .moon-dream-detail--complete .moon-dream-showcase {
    container-type: size;
  }

  .moon-dream-detail--complete .moon-dream-portrait {
    width: 100%;
    height: 100%;
    min-height: 0;
    justify-content: center;
  }

  .moon-dream-detail--complete .moon-dream-portrait-frame {
    flex: 0 1 auto;
    min-height: 0;
    width: min(100%, calc(177.778cqh - 53.333px));
    aspect-ratio: 16 / 9;
  }

  .moon-dream-detail--complete .moon-dream-portrait:not(:has(> .moon-dream-portrait-name))
    > .moon-dream-portrait-frame {
    width: min(100%, 177.778cqh);
  }
}

.moon-dream-detail--complete .moon-dream-spells {
  grid-column: 2;
  grid-row: 1 / 3;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  min-height: 0;
  padding: 10px;
  overflow: hidden;
  border: 1px solid rgba(122, 122, 255, 0.22);
  border-radius: 14px;
  background: rgba(12, 10, 26, 0.28);
}

.moon-dream-detail--complete .moon-dream-spells > .moon-dream-detail-actions {
  display: none;
}

.moon-dream-detail--complete .moon-dream-spells-grid {
  --catalog-card-scale: 1.35;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  grid-template-rows: repeat(2, minmax(0, 1fr));
  align-items: center;
  justify-items: center;
  gap: 4px;
  min-height: 0;
  padding: 2px;
  overflow: visible;
}

/* The two spell rows split (viewport height − ~315px of fixed chrome), while
   the physical card is 22.2vmin × scale. On short desktop heights the pinned
   1.35 outgrows its row and the two rows overlap; step the scale down with
   the height band. Width-gated so tablet blocks keep their own scales. */
@media (min-width: 1300px) and (max-height: 800px) {
  .moon-dream-detail--complete .moon-dream-spells-grid {
    --catalog-card-scale: 1.2;
  }
}

@media (min-width: 1300px) and (max-height: 680px) {
  .moon-dream-detail--complete .moon-dream-spells-grid {
    --catalog-card-scale: 1.02;
  }
}

.moon-dream-detail--complete .moon-dream-complete-lore {
  grid-column: 1;
  grid-row: 2;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: 4px 10px;
  padding: 8px 10px;
  border: 1px solid rgba(70, 70, 104, 0.35);
  border-radius: 12px;
  background: rgba(11, 10, 24, 0.56);
}

.moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-title,
.moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-summary {
  grid-column: 1;
  min-width: 0;
  white-space: normal;
  overflow-wrap: anywhere;
}

.moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-summary {
  display: block;
  overflow: visible;
}

.moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-refresh-error {
  grid-column: 1 / -1;
  grid-row: 3;
  min-width: 0;
  margin: 0;
  padding: 3px 6px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  border: 1px solid rgba(255, 125, 145, 0.34);
  border-radius: 7px;
  background: rgba(66, 20, 38, 0.52);
  color: #ffd3db;
  font-size: 11px;
  line-height: 1.2;
}

.moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-secondary-actions {
  grid-column: 2;
  grid-row: 1 / 3;
  padding: 0;
  border: 0;
}

.moon-dream-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: #a7add8;
}

.moon-dream-input {
  display: flex;
  gap: 12px;
}

#moon-dream-prompt {
  flex: 1;
  min-width: 0;
  padding: 10px 12px;
  border-radius: 8px;
  border: 1px solid #2a2a3a;
  background: #141427;
  color: #f4f4ff;
  font-size: 14px;
}

#moon-dream-generate {
  background: linear-gradient(135deg, #5b6bff, #7c4dff);
  color: #fff;
  border: none;
  border-radius: 8px;
  padding: 10px 16px;
  cursor: pointer;
  font-family: "Cinzel", serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  min-width: 110px;
  max-width: 190px;
  line-height: 1.3;
  white-space: normal;
  transition: transform 0.2s ease, filter 0.2s ease;
}

#moon-dream-generate:hover {
  filter: brightness(1.08);
  transform: translateY(-1px);
}

#moon-dream-generate:disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none;
}

.moon-dream-hint {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.5);
}

#moon-dream-status {
  padding: 5px 8px;
  border-radius: 7px;
  background: rgba(246, 226, 122, 0.08);
  overflow-wrap: anywhere;
  line-height: 1.3;
  font-size: 13px;
  color: #f6e27a;
}

#moon-dream-status:empty {
  display: none;
}

#moon-dream-status.error {
  color: #ff8b8b;
  background: rgba(255, 139, 139, 0.1);
}

#moon-dream-status.success {
  color: #8fe1a1;
  background: rgba(143, 225, 161, 0.1);
}

.moon-dream-section-title {
  font-family: "Cinzel", serif;
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #f4f4ff;
}

#moon-dream-runs {
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding-right: 4px;
}

.moon-dream-pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-top: 12px;
  /* The rail above is `flex: 1 1 auto`; without a basis of its own this footer
     inherits flex-shrink:1 and gets squeezed as the list grows, until the page
     text spills back over the summoner rows instead of sitting under them.
     ("the page indicator ... overlaps their boxes and is not really visible").
     It is a footer: it reserves its height and the SCROLLING rail yields. */
  flex: 0 0 auto;
}

.moon-dream-pagination[hidden] {
  display: none;
}

.moon-dream-pagination button {
  background: #1c1c32;
  border: 1px solid #30304a;
  color: #c6c8e8;
  padding: 6px 12px;
  border-radius: 8px;
  font-size: 12px;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}

.moon-dream-pagination button:disabled {
  opacity: 0.5;
  cursor: default;
}

.moon-dream-pagination button:not(:disabled):hover {
  background: #262645;
  border-color: #4c4c76;
}

.moon-dream-page-info {
  font-size: 11px;
  color: rgba(198, 200, 232, 0.75);
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

.moon-dream-run {
  width: 100%;
  border: 1px solid #2a2a3a;
  border-radius: 12px;
  min-height: 48px;
  padding: 9px 10px;
  background: rgba(16, 16, 28, 0.8);
  display: flex;
  /* A row keeps its own height. As flex children of the scrolling rail these
     used to shrink to min-height once the list outgrew it, and a wrapped
     summoner name or status then painted outside the card, over the row
     below. The rail scrolls the overflow; a row never absorbs it. */
  flex: 0 0 auto;
  justify-content: space-between;
  gap: 12px;
  cursor: pointer;
  color: inherit;
  font: inherit;
  text-align: left;
  transition: border-color 0.2s, background 0.2s;
}

.moon-dream-run-copy {
  min-width: 0;
}

.moon-dream-run:hover {
  border-color: #4c4c76;
  background: rgba(26, 26, 46, 0.9);
}

.moon-dream-run.active {
  border-color: #7a7aff;
  background: rgba(36, 36, 64, 0.95);
}

.moon-dream-run:focus-visible {
  outline: 3px solid #aeb0ff;
  outline-offset: 2px;
}

.moon-dream-run-title {
  font-family: "Cinzel", serif;
  font-size: 14px;
  font-weight: 600;
  color: #f4f4ff;
  /* Player-authored names have no length contract; an unbreakable one wraps
     rather than running out past the card edge. */
  min-width: 0;
  overflow-wrap: anywhere;
}

.moon-dream-run-meta {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.55);
  margin-top: 4px;
}

.moon-dream-run-status {
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-weight: 700;
  /* "Choose permanent art" wraps at its spaces in a narrow rail, but a label
     word is never broken mid-word — min-content keeps "Completed" on one
     line. The name absorbs the remaining pressure instead (it wraps
     anywhere), so neither can be squeezed out of the card. */
  flex: 0 1 auto;
  min-width: min-content;
}

.moon-dream-run-status.loading {
  color: #7aa2ff;
}

.moon-dream-run-status.ready {
  color: #f6e27a;
}

.moon-dream-run-status.complete {
  color: #8fe1a1;
}

.moon-dream-run-status.failed {
  color: #ff8b8b;
}

.moon-dream-detail-card {
  border: 1px solid #2a2a3a;
  border-radius: 16px;
  padding: 20px;
  background: rgba(18, 18, 34, 0.9);
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.moon-dream-right > .moon-dream-detail-card {
  border: 0;
  border-bottom: 1px solid rgba(70, 70, 104, 0.45);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
}

.moon-dream-right > .moon-dream-detail-card:last-child {
  border-bottom: 0;
}

.moon-dream-detail-title {
  font-family: "Cinzel", serif;
  font-size: 18px;
  font-weight: 700;
  color: #f4f4ff;
}

.moon-dream-history-back {
  display: none;
}

.moon-dream-detail-command {
  position: sticky;
  top: 0;
  z-index: 8;
  background: rgba(11, 10, 24, 0.97);
  border-bottom: 1px solid rgba(70, 70, 104, 0.45);
}

.moon-dream-detail-identity {
  display: none;
}

.moon-dream-detail-command--back-only {
  display: none;
}

.moon-dream-stage-journey {
  list-style: none;
  margin: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 6px;
  padding: 10px 12px;
  background: transparent;
}

.moon-dream-stage-step {
  min-width: 0;
  padding: 7px 8px;
  border-radius: 6px;
  border: 0;
  border-bottom: 2px solid rgba(140, 140, 190, 0.25);
  color: rgba(238, 235, 255, 0.55);
  text-align: center;
  font: 700 11px/1.2 "Cinzel", serif;
  letter-spacing: 0.04em;
}

.moon-dream-stage-step.is-complete {
  color: #9de4b0;
  border-bottom-color: rgba(143, 225, 161, 0.5);
}

.moon-dream-stage-step.is-current {
  color: #fff4b3;
  border-bottom-color: rgba(246, 226, 122, 0.78);
  background: rgba(103, 85, 220, 0.18);
}

.moon-dream-stage-step.is-failed {
  color: #ff9b9b;
  border-bottom-color: rgba(255, 139, 139, 0.82);
  background: rgba(154, 45, 63, 0.18);
}

.moon-dream-detail-summary {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.7);
  line-height: 1.4;
}

.moon-dream-detail-actions {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.6);
}

.moon-dream-showcase {
  border-color: rgba(246, 226, 122, 0.35);
  background: linear-gradient(145deg, rgba(24, 24, 44, 0.95), rgba(40, 32, 68, 0.92));
}

.moon-dream-portrait {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 12px;
}

.moon-dream-portrait-frame {
  width: min(520px, 100%);
  aspect-ratio: 16 / 9;
  border-radius: 28px;
  overflow: hidden;
  position: relative;
  border: 1px solid rgba(246, 226, 122, 0.35);
  box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
}

/* The inner bevel has to SCALE with the frame. At a fixed `inset: 10px` /
   `border-radius: 22px` it degenerated on a phone page box: the frame is about
   78x44 there, so the inset ate a quarter of the box and a 22px radius on a
   ~38px-tall inner rect is a CAPSULE — a gold pill drawn across the summoner's
   face, and the loudest thing on the panel. The percentages keep the
   proportion at every size; the pixel clamps keep the desktop frame (520x292)
   at exactly the 10px / 22px it has always had. The reveal's rim bloom
   (`md-frame-bloom`) repeats this geometry verbatim so the two stay welded. */
.moon-dream-portrait-frame::after {
  content: '';
  position: absolute;
  inset: clamp(3px, 6%, 10px);
  border-radius: clamp(5px, 12%, 22px);
  border: 1px solid rgba(255, 255, 255, 0.18);
  pointer-events: none;
}

.moon-dream-portrait-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Loading shimmer for direct Moon Dream media (docs/asset-loading.md law 2 +
   grace amendment): invisible for the first --card-art-grace so fast loads
   show art directly with no placeholder flash; past the grace the placeholder
   fades in and shimmers until the media's own decode settles. */
img.moon-dream-media-loading {
  opacity: 0;
  background:
    linear-gradient(110deg, transparent 25%, rgba(246, 226, 122, 0.14) 50%, transparent 75%),
    linear-gradient(145deg, rgba(20, 20, 42, 0.85), rgba(47, 34, 72, 0.9));
  background-size: 250% 100%, 100% 100%;
  animation:
    moon-dream-media-appear 0.18s ease var(--card-art-grace, 200ms) forwards,
    moon-dream-media-shimmer 1.8s ease-in-out var(--card-art-grace, 200ms) infinite;
}

@keyframes moon-dream-media-appear {
  to {
    opacity: 1;
  }
}

@keyframes moon-dream-media-shimmer {
  0% {
    background-position: -125% 0, 0 0;
  }

  100% {
    background-position: 125% 0, 0 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  img.moon-dream-media-loading {
    animation: none;
    opacity: 1;
  }
}

/* Cohort hold: a freshly built media group appears together (bounded release
   in moon-dream.js). !important so the loading shimmer's appear animation
   cannot preempt the cohort's own release. */
img.moon-dream-media-cohort-hold {
  visibility: hidden !important;
}

.moon-dream-portrait-name {
  font-family: "Cinzel", serif;
  font-size: 14px;
  font-weight: 600;
  color: #f4f4ff;
  text-align: center;
}

/* PHYSICAL enlargement, not a composited one — see the catalog card in
   deck-builder.css for the measurement. A transform upscale here is what made
   Moon Dream "look low res when not hover-enlarged": the spell art rastered for
   its layout box and was then stretched by --catalog-card-scale (1.35 here,
   1.6 on the phone reveal). */
.moon-dream-card {
  margin: 0;
  /* The 1vmin fallback is insurance, not the design: the capturing
     ancestor is what carries the phone floor and the 4K counter-scale.
     If a card ever renders outside it, degrade to the desktop unit
     rather than collapsing to an invalid width. */
  --card-unit: calc(var(--catalog-card-unit, 1vmin) * var(--catalog-card-scale));
  --card-width: calc(12.29 * var(--card-unit));
  --card-height: calc(22.2 * var(--card-unit));
  border-width: calc(1px * var(--catalog-card-scale));
  box-shadow: 0 0 calc(8px * var(--catalog-card-scale)) rgba(238, 219, 241, 0.5);
  --card-base-transform: scale(1);
  transform-origin: center center;
  transition: filter 0.2s ease;
}

.moon-dream-spell-card {
  display: flex;
  justify-content: center;
  position: relative;
  /* Capture the ambient unit for .moon-dream-card to multiply — a custom
     property cannot reference itself, so the capture must sit one level up. */
  --catalog-card-unit: var(--card-unit);
}

.moon-dream-spell-card .card:hover {
  filter: brightness(1.08);
  z-index: 1000;
}

.moon-dream-spells-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(calc(var(--card-width) * var(--catalog-card-scale)), 1fr));
  gap: var(--catalog-card-gap);
  row-gap: calc(var(--catalog-card-gap) + 10px);
  padding-top: 20px;
  padding-bottom: 28px;
  overflow: visible;
}

/* Keep the section's final footprint while all newly generated source faces
   settle, then reveal the page as one physical set. */
.moon-dream-spells.moon-dream-spells-raster-staging {
  visibility: hidden;
  pointer-events: none;
}

.moon-dream-card-fallback {
  border: 1px solid rgba(255, 255, 255, 0.12);
  border-radius: 12px;
  padding: 10px;
  text-align: center;
  background: rgba(12, 12, 22, 0.9);
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.moon-dream-card-fallback:hover {
  transform: scale(1.3);
  box-shadow: 0 24px 60px rgba(0, 0, 0, 0.55);
}

.moon-dream-card-art {
  border-radius: 10px;
  overflow: hidden;
  margin-bottom: 8px;
}

.moon-dream-card-art img {
  width: 100%;
  height: auto;
  display: block;
}

.moon-dream-card-name {
  font-family: "Cinzel", serif;
  font-size: 14px;
  font-weight: 600;
  color: #f4f4ff;
}

.moon-dream-card-cost {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.6);
}

.moon-dream-delete {
  border: 1px solid rgba(255, 139, 139, 0.6);
  background: transparent;
  color: #ff8b8b;
  min-height: 44px;
  padding: 8px 12px;
  border-radius: 10px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, color 0.2s;
}

.moon-dream-delete:hover {
  border-color: #ff8b8b;
  background: rgba(255, 139, 139, 0.12);
}

.moon-dream-delete:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.moon-dream-progress {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.moon-dream-progress-bar {
  height: 8px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  overflow: hidden;
}

.moon-dream-progress-fill {
  height: 100%;
  width: 0;
  background: linear-gradient(90deg, #7aa2ff, #f6e27a);
  transition: width 0.4s ease;
}

.moon-dream-progress--indeterminate .moon-dream-progress-fill {
  width: 42%;
  animation: moon-dream-progress-indeterminate 1.25s ease-in-out infinite;
}

@keyframes moon-dream-progress-indeterminate {
  0% { transform: translateX(-115%); }
  50% { transform: translateX(70%); }
  100% { transform: translateX(240%); }
}

.moon-dream-progress-meta {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.6);
}

.moon-dream-progress--action-required .moon-dream-progress-fill {
  background: linear-gradient(90deg, #7aa2ff, #8fe1a1);
}

.moon-dream-art-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
}

.moon-dream-art-fieldset {
  min-width: 0;
  border: 0;
  display: grid;
  grid-template-columns: minmax(260px, 1.25fr) minmax(240px, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr) auto;
  gap: 16px;
}

.moon-dream-art-fieldset > legend,
.moon-dream-art-fieldset > .moon-dream-art-hint {
  grid-column: 1 / -1;
}

.moon-dream-art-preview {
  grid-column: 1;
  grid-row: 3 / 5;
  min-width: 0;
  min-height: 220px;
  aspect-ratio: 16 / 9;
  align-self: start;
  overflow: hidden;
  border: 1px solid rgba(246, 226, 122, 0.34);
  border-radius: 14px;
  background: rgba(9, 9, 20, 0.96);
  box-shadow: 0 18px 42px rgba(0, 0, 0, 0.34);
}

.moon-dream-art-preview img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.moon-dream-art-preview .moon-dream-empty {
  display: grid;
  width: 100%;
  height: 100%;
  min-height: 0;
  place-items: center;
  border: 0;
  border-radius: 0;
}

.moon-dream-art-selector {
  grid-column: 2;
  grid-row: 3;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-self: start;
}

.moon-dream-art-fieldset > .moon-dream-commit-bar {
  grid-column: 2;
  grid-row: 4;
}

.moon-dream-art-card {
  border: 1px solid #2a2a3a;
  border-radius: 14px;
  overflow: hidden;
  background: rgba(14, 14, 28, 0.9);
  cursor: pointer;
  position: relative;
  min-width: 0;
  transition: border-color 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease;
}

.moon-dream-art-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

.moon-dream-art-visual {
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: rgba(9, 9, 20, 0.96);
}

.moon-dream-art-visual img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.moon-dream-art-visual .moon-dream-empty {
  height: 100%;
  min-height: 0;
  border: 0;
  border-radius: 0;
  display: grid;
  place-items: center;
  padding: 16px;
}

.moon-dream-art-card.selected {
  border-color: #f6e27a;
  box-shadow: 0 0 0 2px rgba(246, 226, 122, 0.24), 0 12px 30px rgba(0, 0, 0, 0.32);
}

.moon-dream-art-card:hover:not(.disabled) {
  border-color: #8f91ff;
  filter: brightness(1.06);
}

.moon-dream-art-card:not(.disabled),
.moon-dream-art-card:not(.disabled) * {
  cursor: pointer;
}

.moon-dream-art-radio:focus + .moon-dream-art-visual,
.moon-dream-art-radio:focus-visible + .moon-dream-art-visual {
  outline: 3px solid #aeb0ff;
  outline-offset: -4px;
}

.moon-dream-art-card.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.moon-dream-art-card.disabled * {
  cursor: not-allowed;
}

.moon-dream-art-card .moon-dream-art-label {
  position: absolute;
  bottom: 8px;
  left: 8px;
  background: rgba(0, 0, 0, 0.65);
  color: #fff;
  font-size: 11px;
  padding: 4px 8px;
  border-radius: 999px;
}

.moon-dream-permanent-warning {
  color: #f1d98d;
  font-size: 13px;
  line-height: 1.5;
}

.moon-dream-commit-bar {
  position: sticky;
  bottom: 0;
  z-index: 7;
  display: grid;
  gap: 8px;
  margin: 0 -4px -4px;
  padding: 12px;
  border: 1px solid rgba(246, 226, 122, 0.3);
  border-radius: 12px;
  background: rgba(14, 12, 29, 0.97);
  box-shadow: 0 -12px 30px rgba(4, 3, 12, 0.42);
}

.moon-dream-commit-bar :is(.moon-dream-permanent-warning, .moon-dream-language-status, .moon-dream-balance-warning) {
  margin: 0;
}

.moon-dream-secondary-actions {
  display: flex;
  justify-content: flex-end;
  padding: 12px 16px 18px;
  border-top: 1px solid rgba(70, 70, 104, 0.32);
}

.moon-dream-language-status {
  color: rgba(240, 238, 255, 0.78);
  font-size: 12px;
  line-height: 1.5;
}

.moon-dream-commit {
  min-height: 44px;
  align-self: flex-start;
  border: 1px solid rgba(246, 226, 122, 0.66);
  border-radius: 10px;
  padding: 10px 16px;
  background: linear-gradient(135deg, #6755dc, #8b55d9);
  color: #fff;
  cursor: pointer;
  font-family: "Cinzel", serif;
  font-size: 13px;
  font-weight: 700;
  line-height: 1.35;
  transition: border-color 0.2s ease, filter 0.2s ease;
}

.moon-dream-commit:hover:not(:disabled) {
  border-color: #f6e27a;
  filter: brightness(1.09);
}

.moon-dream-commit:focus-visible {
  outline: 3px solid #f6e27a;
  outline-offset: 3px;
}

.moon-dream-commit:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.moon-dream-empty {
  border: 1px dashed #2a2a3a;
  border-radius: 16px;
  padding: 24px;
  color: rgba(255, 255, 255, 0.55);
  text-align: center;
}

.moon-dream-detail-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}

.moon-dream-detail-loading::before {
  content: "";
  width: 18px;
  height: 18px;
  flex: 0 0 auto;
  border: 2px solid rgba(174, 176, 255, 0.28);
  border-top-color: #aeb0ff;
  border-radius: 50%;
  animation: spin 0.8s linear infinite;
}

@media (max-width: 1200px) {
  .moon-dream-art-grid:not(.moon-dream-art-selector) {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 900px) {
  .foundry-workspace-notice {
    top: 8px;
    max-width: calc(100% - 24px);
  }

  .moon-dream-content {
    grid-template-columns: minmax(210px, 260px) minmax(0, 1fr);
  }
}

/* Portrait generation and permanent selection are ONE bounded desktop
   dashboard: same preview, same 3x2 picker, same slot under the picker. Only
   that slot's occupant changes -- the generation hint becomes the commit bar
   -- so finishing a run no longer resizes the portrait the player is judging,
   and generation no longer scrolls its own controls out of view. Compact
   phone layouts override this below with the no-preview three-by-two
   picker. */
@media (min-width: 901px) and (min-height: 561px) {
  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) {
    overflow-y: hidden;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) > .moon-dream-status-card {
    flex: 0 0 auto;
    padding: 6px 12px;
    gap: 4px;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) > .moon-dream-status-card > :not(.moon-dream-progress):not(.moon-dream-status-notice) {
    display: none;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-status-notice {
    overflow-wrap: anywhere;
    font-size: 10px;
    line-height: 1.2;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-progress {
    display: grid;
    grid-template-columns: minmax(150px, 0.28fr) minmax(0, 1fr);
    align-items: center;
    gap: 10px;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) > .moon-dream-art-workspace {
    flex: 1 1 auto;
    min-height: 0;
    padding: 8px 10px;
    overflow: hidden;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-art-fieldset {
    display: grid;
    height: 100%;
    min-height: 0;
    grid-template-columns: minmax(300px, 1.25fr) minmax(260px, 1fr);
    /* One reserved action slot in both stages, floored at the commit button's
       44px touch target plus the bar's padding, so swapping the generation
       hint for the commit bar does not move the preview. */
    --moon-dream-action-slot: 56px;
    grid-template-rows: minmax(0, 1fr) minmax(var(--moon-dream-action-slot), auto);
    gap: 8px 10px;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-art-fieldset > legend {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  /* The preview is the only surface where the portrait is large enough to
     judge, so it takes the workspace's full height beside the picker and
     scales with the viewport. A fixed cap used to pin it at 260px, which made
     it smaller than an option thumbnail above 1080p. Two-way containment lives
     on the replaced element (max-width + max-height with auto sizing), because
     a fixed-ratio *box* cannot be contained in both axes in CSS alone. */
  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-art-preview {
    display: block;
    grid-column: 1;
    grid-row: 1 / -1;
    position: relative;
    width: 100%;
    height: 100%;
    min-width: 0;
    min-height: 0;
    align-self: stretch;
    justify-self: stretch;
    /* No clipping: everything inside is absolutely positioned and bounded by
       max-width/max-height, and the portrait's drop shadow has to escape. */
    overflow: visible;
    border: 0;
    border-radius: 0;
    background: none;
    box-shadow: none;
  }

  /* inset:0 + margin:auto is the reliable two-way contain: an absolutely
     positioned replaced element resolves percentage max-* against its
     containing block's used size, so the portrait scales to the shorter of
     the cell's two axes and centres. The `height:100%` chain above this cell
     is not definite enough for a static child's percentage max-height. */
  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-art-preview img {
    display: block;
    position: absolute;
    inset: 0;
    margin: auto;
    /* The frame lives on the image, so its border counts against the cell. */
    box-sizing: border-box;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    border: 1px solid rgba(246, 226, 122, 0.34);
    border-radius: 14px;
    background: rgba(9, 9, 20, 0.96);
    box-shadow: 0 18px 42px rgba(0, 0, 0, 0.34);
  }

  /* The empty state is bounded the same way, so a short cell cannot push it
     out of a workspace that never scrolls. */
  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-art-preview .moon-dream-empty {
    display: grid;
    position: absolute;
    inset: 0;
    margin: auto;
    box-sizing: border-box;
    width: auto;
    height: 100%;
    max-width: 100%;
    max-height: 100%;
    min-height: 0;
    aspect-ratio: 16 / 9;
    place-items: center;
    border: 1px solid rgba(246, 226, 122, 0.34);
    border-radius: 14px;
    background: rgba(9, 9, 20, 0.96);
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-hint {
    grid-column: 2;
    grid-row: 2;
    align-self: center;
    margin: 0;
    padding: 6px 8px;
    font-size: 12px;
    line-height: 1.35;
    overflow-wrap: anywhere;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-hint {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-art-selector {
    grid-column: 2;
    grid-row: 1;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: repeat(2, auto);
    align-self: center;
    gap: 8px;
    min-height: 0;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-art-selector .moon-dream-art-card,
  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) .moon-dream-art-selector .moon-dream-art-visual {
    aspect-ratio: 16 / 9;
  }

  /* The commit bar sits under the picker rather than across the whole
     workspace, so confirming a choice costs the preview no height. */
  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar {
    position: static;
    grid-column: 2;
    grid-row: 2;
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.9fr);
    grid-template-rows: repeat(2, auto);
    align-items: center;
    gap: 6px 10px;
    margin: 0;
    padding: 6px 8px;
    overflow: hidden;
    box-shadow: none;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar
    :is(.moon-dream-permanent-warning, .moon-dream-language-status, .moon-dream-balance-warning, .moon-dream-selection-status) {
    grid-column: 1;
    min-width: 0;
    overflow-wrap: anywhere;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-permanent-warning,
  #moon-dream-detail[data-stage="selection"] .moon-dream-selection-status {
    grid-row: 1 / 3;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-language-status {
    grid-row: 1;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-balance-warning {
    grid-row: 2;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar:not(:has(.moon-dream-balance-warning))
    .moon-dream-language-status,
  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar:not(:has(.moon-dream-language-status))
    .moon-dream-balance-warning {
    grid-row: 1 / 3;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar:has(.moon-dream-selection-status:not(:empty))
    .moon-dream-permanent-warning,
  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar:has(.moon-dream-language-status, .moon-dream-balance-warning)
    .moon-dream-permanent-warning {
    display: none;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit {
    grid-column: 2;
    grid-row: 1 / 3;
    width: 100%;
    max-width: none;
    min-width: 0;
    align-self: stretch;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="selection"]) > .moon-dream-secondary-actions {
    flex: 0 0 44px;
    min-height: 44px;
    align-items: center;
    padding: 0 10px;
  }
}

@media (max-width: 600px) {
  .foundry-tabs {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 6px;
    padding: 6px;
    /* Same reason as the base rule: no corner control paints over the
       Foundry, so only the edge inset is reserved. */
    padding-right: calc(var(--mf-rail-edge) + 8px);
  }

  /* CONDENSED ON A PHONE: the arrow alone, in a square tap target.

     The word costs width the 600px tab bar does not have — it shares one row
     with two panel tabs, a pricing note, the Stardust pill and the language
     select, and every one of them was being squeezed to an ellipsis. The
     button keeps its full accessible name from `aria-label`, so this is
     presentation only, and the arrow is still an unambiguous "back". */
  .foundry-tab.foundry-tab-lobby {
    grid-column: 1;
    justify-self: start;
    justify-content: center;
    gap: 0;
    width: var(--mf-touch, 44px);
    min-width: var(--mf-touch, 44px);
    padding-inline: 0;
  }

  .foundry-tab-lobby-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .foundry-tab-lobby-glyph {
    font-size: 1.15em;
  }

  .foundry-tablist {
    grid-column: 2 / 4;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    width: 100%;
  }

  .foundry-context-tools {
    grid-column: 1 / 3;
    width: 100%;
  }

  .foundry-language-control {
    grid-column: 3;
    justify-self: end;
    min-width: 0;
    width: 100%;
  }

  .foundry-language-select {
    width: 100%;
  }

  .foundry-tab {
    min-width: 0;
    padding: 6px 5px;
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
  }

}

@media (prefers-reduced-motion: reduce) {
  #moon-dream-generate,
  .moon-dream-pagination button,
  .moon-dream-run,
  .moon-dream-card,
  .moon-dream-card-fallback,
  .moon-dream-delete,
  .moon-dream-progress-fill,
  .moon-dream-art-card,
  .moon-dream-commit {
    transition: none;
  }

  .moon-dream-detail-loading::before {
    animation: none;
  }

  .moon-dream-progress--indeterminate .moon-dream-progress-fill {
    width: 100%;
    transform: none;
    animation: none;
    opacity: 0.55;
  }
}

/* Scrollbar */
.foundry-surface-root ::-webkit-scrollbar {
  width: 8px;
}

.foundry-surface-root ::-webkit-scrollbar-track {
  background: #1a1a2e;
}

.foundry-surface-root ::-webkit-scrollbar-thumb {
  background: #3a3a5a;
  border-radius: 4px;
}

/* ============================================================================
 * MOBILE (phone) LAYER — appended last so it wins WITHIN foundry.css.
 * Every rule below is phone-gated, so desktop rendering stays byte-identical.
 * Canonical gates (mirror mobile.css / UI.Platform.isMobileViewport):
 *   portrait phone : (orientation: portrait)  and (max-width:  768px)
 *   landscape phone: (orientation: landscape) and (max-height: 560px)
 * Short-viewport compaction uses the LANDSCAPE (max-height) gate — a width-only
 * query would never match an 845x400 landscape phone.
 *
 * CASCADE NOTE: mobile.css is loaded AFTER this file and, inside its main phone
 * gate, re-asserts bare #play-area / #sidebar / #element-list (incl. the C4
 * #sidebar{min-width:300px} that crushes the drag canvas). To make the Moon
 * Fuse stack actually take effect rather than be clobbered by that later sheet,
 * those three selectors are rooted under .foundry-surface-root here (specificity
 * 1,1,0 > mobile.css's bare-id 1,0,0). Every other selector below has no
 * mobile.css counterpart and wins purely by source order.
 * ==========================================================================*/

/* ---- Universal phone rules (portrait OR landscape) ---------------------- */
@media (orientation: portrait) and (max-width: 768px),
       (orientation: landscape) and (max-height: 560px) {
  /* One compact command bar. Every control remains reachable without a nested
     horizontal scroller, while contextual tools disappear with their tab. */
  .foundry-tabs {
    min-height: 52px;
    padding-left: calc(var(--mf-safe-left) + 8px);
    /* NOT `safe-right + rail-edge`: on a phone the rail token already carries
       the inset, and adding it again over-reserved the bar by a whole notch
       (47-59px in landscape). The button term is gone with the chrome menu,
       which the Foundry does not paint. */
    padding-right: calc(var(--mf-rail-edge) + 8px);
    overflow: hidden;
    touch-action: manipulation;
  }
  .foundry-tab,
  .foundry-language-select,
  .foundry-tab-tools #search-input {
    min-height: 44px;
  }
  .foundry-language-select,
  .foundry-tab-tools #search-input,
  #moon-dream-prompt {
    font-size: 16px;
  }
  .foundry-tab {
    min-width: 0;
    padding: 5px clamp(6px, 1.2vw, 11px);
    font-size: max(11px, clamp(11px, 1.45vw, 12px) * var(--foundry-tab-fit, 1));
    overflow: visible;
    text-overflow: clip;
    white-space: nowrap;
  }
  .foundry-tab-tools {
    gap: 6px;
  }
  /* A flex item's `min-width: auto` pins it to its own content, so this note
     claimed its full width out of the row before the tabs were measured at
     all. It is prose with a complete accessible name; the navigation is not.

     `#moon-fuse-cost` ONLY, and that is not fussiness. Its compact text is
     `costSummaryShort` — "12 ☆ / discovery" — so an END ellipsis drops the
     word and keeps the number, which is exactly what this is for. The Moon
     Dream note next to it reads `stagePricingMoney` — "Summoner cost 40 ☆",
     number LAST — so the same rule there would hide the price instead of the
     label. It keeps its full width; it also competes with far less, because
     the Moon Fuse search field is not on screen while it is. */
  #moon-fuse-cost.foundry-cost-note {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .foundry-tab-tools #search-input {
    flex: 1 1 clamp(92px, 17vw, 170px);
    width: auto;
    max-width: clamp(132px, 22vw, 190px);
    padding: 7px 9px;
  }
  .foundry-balance-pill {
    min-height: 44px;
    padding: 6px 9px;
  }
  .foundry-balance-pill::before {
    content: "✦";
    margin-right: 5px;
    color: #f6e27a;
  }
  .foundry-balance-pill > :first-child {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .foundry-language-control {
    flex-basis: 54px;
    min-width: 54px;
    max-width: 54px;
  }
  .foundry-language-select {
    padding: 5px 18px 5px 6px;
    text-align: center;
    text-align-last: center;
  }

  /* Moon Fuse catalog pages contain only complete, readable rows. */
  .foundry-surface-root #element-list {
    --foundry-inventory-item-min-width: 180px;
    --foundry-inventory-item-height: 60px;
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(min(var(--foundry-inventory-item-min-width), 100%), 1fr));
    grid-auto-rows: var(--foundry-inventory-item-height);
    align-content: start;
    gap: 8px;
    overflow-x: hidden;
    overflow-y: hidden;
  }
  .sidebar-element {
    min-height: var(--foundry-inventory-item-height);
    height: var(--foundry-inventory-item-height);
    overflow: hidden;
  }
  .foundry-workspace-notice button { min-height: 40px; }
  .foundry-surface-root .foundry-workspace-notice {
    right: calc(var(--moon-fuse-sidebar-width) + 8px);
  }

  /* Legible empty-canvas hint on the dark gradient (was 0.3 alpha). */
  #hint-text { color: rgba(255, 255, 255, 0.5); }

  /* Moon Dream creation is a compact composer. Balance lives in the command
     bar; status consumes space only while it contains a useful message. */
  .moon-dream-header {
    display: flex;
    gap: 2px;
    padding: 4px 10px;
  }
  .moon-dream-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .moon-dream-create-controls { gap: 2px; }
  .moon-dream-create-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    min-height: 12px;
  }
  .moon-dream-hint {
    position: static;
    flex: 1 1 auto;
    width: auto;
    height: auto;
    min-width: 0;
    margin: 0;
    overflow: hidden;
    clip: auto;
    clip-path: none;
    white-space: nowrap;
    text-overflow: ellipsis;
    font-size: 10px;
    line-height: 12px;
  }
  .moon-dream-cost {
    display: block;
    white-space: normal;
    overflow-wrap: anywhere;
    font-size: 11px;
    line-height: 12px;
    text-align: right;
  }
  #moon-dream-prompt,
  #moon-dream-generate,
  .moon-dream-pagination button,
  .moon-dream-delete {
    min-height: 44px;
  }
  .moon-dream-input { gap: 8px; }
  #moon-dream-generate {
    flex: 0 1 clamp(132px, 27vw, 190px);
    min-width: 0;
    max-width: 190px;
    padding: 8px 12px;
    white-space: normal;
    overflow-wrap: anywhere;
  }
  #moon-dream-status {
    padding: 3px 7px;
    overflow-wrap: anywhere;
    font-size: 11px;
  }

  /* Phone Moon Dream is master/detail: the library and the chosen summoner
     never compete for width and only detail owns vertical scrolling. */
  .foundry-surface-root .moon-dream-content {
    display: block;
    min-width: 0;
    padding: 8px 10px;
    overflow: hidden;
    /* pinch-zoom too: the spell deck's cards live in here, and a pinch that
       touches a card is the phone's magnifier (touch-policy.css). */
    touch-action: pan-y pinch-zoom;
  }
  .foundry-surface-root .moon-dream-content[data-mobile-view="history"] .moon-dream-right {
    display: none;
  }
  .foundry-surface-root .moon-dream-content[data-mobile-view="detail"] .moon-dream-history-rail {
    display: none;
  }
  /* Permanent-art selection needs the full compact height for six portraits.
     Reclaim only the detail pane's decorative vertical inset; the Back,
     Delete, and Confirm actions keep their 44px touch targets. */
  #moon-dream-panel:has(#moon-dream-detail[data-stage="selection"]) > .moon-dream-content[data-mobile-view="detail"] {
    padding-block: 0;
  }
  .foundry-surface-root:has(#moon-dream-panel.active #moon-dream-detail[data-stage="selection"]) .foundry-tabs {
    min-height: 44px;
    padding-block: 0;
  }
  #moon-dream-panel:has(> .moon-dream-content[data-mobile-view="detail"]) > .moon-dream-create-strip {
    display: none;
  }
  .foundry-surface-root .moon-dream-history-rail {
    width: 100%;
    min-width: 0;
    height: 100%;
    padding: 0;
    overflow: hidden;
    background: transparent;
  }
  .foundry-surface-root .moon-dream-right {
    width: 100%;
    min-width: 0;
    height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
    overscroll-behavior: contain;
    touch-action: pan-y pinch-zoom;
    -webkit-overflow-scrolling: touch;
    background: transparent;
  }
  .moon-dream-runs { gap: 0; }
  .moon-dream-section-title {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
  .foundry-surface-root #moon-dream-runs {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(3, minmax(44px, 1fr));
    grid-auto-flow: row;
    gap: 5px 7px;
    padding: 0;
    overflow-x: visible;
    overflow-y: visible;
  }
  .foundry-surface-root #moon-dream-runs > .moon-dream-empty {
    grid-column: 1 / -1;
    grid-row: 1 / -1;
    display: grid;
    place-items: center;
    border: 0;
    background: radial-gradient(circle at center, rgba(103, 85, 220, 0.12), transparent 68%);
  }
  .foundry-surface-root .moon-dream-run {
    min-height: 44px;
    height: 100%;
    gap: 6px;
    padding: 6px 9px;
    align-items: center;
    overflow: hidden;
    border-radius: 8px;
  }
  .moon-dream-run-copy {
    min-width: 0;
    overflow: hidden;
  }
  .moon-dream-run-title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 12px;
  }
  .moon-dream-run-meta { display: none; }
  .moon-dream-run-status {
    flex: 0 0 auto;
    /* Compact truncates with an ellipsis, so it must be free to go narrower
       than the desktop min-content floor. */
    min-width: 0;
    max-width: 42%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11px;
  }
  .moon-dream-pagination {
    flex: 0 0 auto;
    gap: 8px;
    margin-top: 4px;
  }
  .moon-dream-pagination button {
    padding: 5px 12px;
  }
  .moon-dream-detail-command {
    display: grid;
    grid-template-columns: minmax(108px, auto) minmax(0, 1fr);
    align-items: stretch;
    min-height: 44px;
  }
  .moon-dream-detail-command--back-only {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
  }
  .moon-dream-history-back {
    display: block;
    min-height: 44px;
    border: 0;
    border-right: 1px solid rgba(70, 70, 104, 0.45);
    background: transparent;
    color: #e6ddff;
    text-align: left;
    padding: 7px 10px;
    cursor: pointer;
    touch-action: manipulation;
  }
  .moon-dream-stage-journey {
    align-self: stretch;
    gap: 3px;
    padding: 5px 7px;
  }
  .moon-dream-stage-step {
    display: grid;
    place-items: center;
    min-height: 34px;
    padding: 4px;
    font-size: 11px;
  }

  .moon-dream-detail-card {
    padding: 9px 10px;
    gap: 7px;
  }
  .moon-dream-detail-title { font-size: 15px; }
  .moon-dream-detail-summary,
  .moon-dream-detail-actions { line-height: 1.25; }
  .moon-dream-art-fieldset { gap: 8px; }
  .moon-dream-art-grid {
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 7px;
  }
  .moon-dream-art-card { border-radius: 8px; }
  .moon-dream-art-card .moon-dream-art-label {
    bottom: 4px;
    left: 4px;
    max-width: calc(100% - 8px);
    padding: 3px 5px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 11px;
  }
  .moon-dream-commit {
    width: 100%;
    max-width: none;
  }

  /* Spell reveal — desktop --catalog-card-scale (1.05) renders ~50px unreadable
     thumbnails against the phone --card-width floor (the deck-builder tiny-card
     bug, unfixed here). Scale up via the sanctioned knob (never px-override
     --card-*) and size the columns to that width so they sit snug + centred. */
  .moon-dream-spells-grid {
    --catalog-card-scale: 1.6;
    grid-template-columns: repeat(auto-fill, calc(var(--card-width) * var(--catalog-card-scale)));
    justify-content: center;
  }
}

/* Landscape tablets retain Moon Dream's desktop master/detail relationship,
   but paginate the physical spell deck as a readable 3x2 canvas. The viewport
   band is intentionally disjoint from the phone layer above and from existing
   wide/short desktop layouts. */
@media (orientation: landscape) and (min-width: 960px) and (min-height: 561px) and (max-width: 1560px) and (max-aspect-ratio: 17/10) {
  .foundry-surface-root .moon-dream-content {
    grid-template-columns: clamp(210px, 22vw, 248px) minmax(0, 1fr);
    gap: 10px;
    padding: 10px;
  }

  .foundry-surface-root .moon-dream-history-rail {
    padding: 10px;
  }

  .foundry-surface-root .moon-dream-right.moon-dream-detail--complete {
    /* Row 1 hugs the (width-capped) portrait; every remaining pixel goes to
       the lore row so the full summary paints on the shortest tablet band. */
    grid-template-columns: minmax(250px, 46%) minmax(0, 1fr);
    grid-template-rows: minmax(0, auto) minmax(0, 1fr);
    gap: 8px;
    padding: 8px;
    overflow: hidden;
  }

  .moon-dream-detail--complete .moon-dream-showcase,
  .moon-dream-detail--complete .moon-dream-spells {
    padding: 8px;
    border-radius: 12px;
  }

  /* The 600px-high tablet leaves exactly two 1vmin card heights plus the row
     gap. Return four decorative padding pixels to that canvas so WebKit's
     fractional card boxes never touch across rows. */
  .moon-dream-detail--complete .moon-dream-spells {
    padding-block: 6px;
  }

  .moon-dream-detail--complete .moon-dream-showcase {
    align-self: end;
    min-height: auto;
  }

  /* 260px was a thumbnail in a 460px column. Same reasoning as the desktop
     rule: the column bounds the hero, nothing else does. */
  .moon-dream-detail--complete .moon-dream-portrait-frame {
    width: 100%;
    max-width: none;
    margin-inline: auto;
    aspect-ratio: 16 / 9;
    border-radius: 12px;
  }

  .moon-dream-detail--complete .moon-dream-spells-grid {
    --catalog-card-scale: 1.5;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr));
    gap: 4px;
    padding: 2px;
  }

  /* Short tablet band (960x600-class): 1.5 outgrows the two spell rows and
     the rows overlap. Product call 2026-07-24: keep no-overlap strict and
     let the cards shrink proportionally on the shortest tablets. */
  @media (max-height: 700px) {
    .moon-dream-detail--complete .moon-dream-spells-grid {
      --catalog-card-scale: 1;
    }

    /* The lore row on this band is ~82px. Step the summary down enough to
       paint the complete biography outright; a finished paid summoner must
       not turn its description into a hidden inner scrollport. */
    .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-summary {
      font-size: 11px;
      line-height: 1.2;
    }
  }

  .moon-dream-detail--complete .moon-dream-complete-lore {
    grid-template-columns: minmax(0, 1fr);
    /* title / flexible summary / refresh notice / actions — the panel stays
       inside its fixed 1fr detail row. Every row must genuinely fit: the
       Spectral summary needs ~92px at this width, so the title stays a
       single tighter line and the gaps yield. */
    grid-template-rows: auto minmax(0, 1fr) auto auto;
    align-self: stretch;
    align-items: start;
    gap: 4px;
    padding: 8px 10px;
    min-height: 0;
    max-height: 100%;
    overflow: hidden;
  }

  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-title {
    font-size: 15px;
    line-height: 1.3;
  }

  /* The summary owns the flexible row. Its compact type above lets the whole
     description fit in that row without a nested scrollbar. */
  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-summary {
    align-self: stretch;
    min-height: 0;
    overflow-y: visible;
  }

  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-title,
  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-summary,
  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-refresh-error,
  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-secondary-actions {
    grid-column: 1;
    grid-row: auto;
  }

  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-secondary-actions {
    justify-content: flex-end;
    padding: 0;
  }

  .moon-dream-detail--complete .moon-dream-delete {
    min-height: 44px;
  }
}

@media (orientation: portrait) and (max-width: 768px) {
  #moon-dream-detail[data-stage="selection"] .moon-dream-art-fieldset {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto auto auto auto auto;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-preview,
  #moon-dream-detail[data-stage="selection"] .moon-dream-art-selector,
  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar {
    grid-column: 1;
    grid-row: auto;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-preview {
    min-height: 180px;
  }
}

/* ---- Moon Fuse PORTRAIT — stack a full-width canvas OVER a full-width,
   multi-column catalog. The shared desktop/mobile ROW split crushes the drag
   canvas to a ~90px sliver (sidebar min-width:300px) and collapses the catalog
   to one column. Rooted under .foundry-surface-root so it wins over mobile.css's
   later bare-id phone rules. ------------------------------------------------ */
@media (orientation: portrait) and (max-width: 768px) {
  .foundry-surface-root #moon-fuse-panel { flex-direction: column; }
  .foundry-surface-root #play-area {
    flex: 0 0 min(48svh, 420px);
    min-height: 180px;
  }
  .foundry-surface-root #sidebar {
    width: auto;
    min-width: 0;
    flex: 1 1 auto;
    border-left: none;
    border-top: 1px solid #2a2a3a;
  }
  .foundry-surface-root #element-list {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
    align-content: start;
    gap: 8px;
  }

  .foundry-surface-root .moon-dream-right.moon-dream-detail--complete {
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr) auto;
    gap: 8px;
    padding: 8px;
    overflow: hidden;
  }

  .moon-dream-detail--complete .moon-dream-portrait-frame {
    width: min(520px, 100%);
    aspect-ratio: 16 / 9;
  }

  .moon-dream-detail--complete .moon-dream-spells {
    grid-column: 1;
    grid-row: 2;
  }

  .moon-dream-detail--complete .moon-dream-spells-grid {
    --catalog-card-scale: 1.45;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr));
  }

  .moon-dream-detail--complete .moon-dream-complete-lore {
    grid-column: 1;
    grid-row: 3;
  }
}

/* ---- Moon Fuse + Moon Dream LANDSCAPE (short viewport ~400px tall) — reclaim
   vertical budget so the catalog / summoner reveal are not starved by chrome. */
@media (orientation: landscape) and (max-height: 560px) {
  /* Moon Fuse — canvas-first split, rooted to win the Foundry cascade. */
      .foundry-tabs {
        padding-block: 4px;
        padding-left: calc(var(--mf-safe-left) + 8px);
      }
  .foundry-surface-root #moon-fuse-panel {
    --moon-fuse-sidebar-width: clamp(200px, 24vw, 240px);
  }
  .foundry-surface-root #play-area {
    flex: 1 1 auto;
    min-width: 70%;
  }
  .foundry-surface-root #sidebar {
    width: var(--moon-fuse-sidebar-width);
    flex: 0 0 var(--moon-fuse-sidebar-width);
    min-width: 0;
    max-width: 30%;
    /* The sidebar owns the right screen edge, which on a notched phone in
       landscape is 59pt of Dynamic Island. Its background may bleed there;
       its Next button and element names may not. */
    padding-right: var(--mf-safe-right);
    box-sizing: border-box;
  }
  .foundry-surface-root #element-list {
    --foundry-inventory-item-height: 54px;
  }

  /* The canvas is ~640x235 here. A centred paragraph of instructions plus a
     dashed bin is most of it — the crowding the player reported. Keep the bin
     (it is a drop target) and retire the prose; the sidebar rows are visibly
     draggable and the tutorial covers the rest. */
  .foundry-surface-root #hint-text {
    display: none;
  }
  .foundry-surface-root .trash-zone {
    left: calc(var(--mf-safe-left) + 8px);
    bottom: calc(var(--mf-safe-bottom) + 6px);
    padding: 6px 10px;
    font-size: 11px;
  }
  .sidebar-pagination {
    gap: 3px;
    padding: 4px;
  }
  .sidebar-pagination button {
    min-width: 44px;
    min-height: 44px;
    padding: 4px 6px;
  }
  .sidebar-page-info {
    min-width: 0;
    white-space: nowrap;
    font-size: 11px;
    letter-spacing: 0.04em;
  }

  .moon-dream-portrait-frame { width: min(300px, 100%); }

  /* Active generation must remain a dashboard, not a scrollable document.
     Keep the journey + progress anchored, devote the middle to arriving art,
     and defer the already-selected large portrait until completion. */
  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"]) {
    position: relative;
    gap: 0;
    overflow-y: hidden;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-status-card {
    flex: 0 0 auto;
    display: block;
    padding: 2px 8px;
    border-bottom-color: rgba(70, 70, 104, 0.3);
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-detail-command .moon-dream-stage-journey {
    padding: 3px 7px;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-detail-command .moon-dream-stage-step {
    min-height: 30px;
    padding: 2px 4px;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-status-card
    > :not(.moon-dream-progress):not(.moon-dream-status-notice) {
    display: none;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    .moon-dream-status-notice {
    margin-top: 2px;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 9px;
    line-height: 1.1;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    .moon-dream-progress {
    display: grid;
    grid-template-columns: minmax(96px, 0.38fr) minmax(0, 1fr);
    align-items: center;
    gap: 7px;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    .moon-dream-progress-bar {
    height: 5px;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    .moon-dream-progress-meta {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: 10px;
  }

  #moon-dream-detail[data-stage="portraits"] > .moon-dream-art-workspace {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0 7px;
    overflow: hidden;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-fieldset {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    /* Was `minmax(148px, 1fr)`. That floor was set when this block's phone
       viewport was 844x390; e6e13c10 replaced it with the MEASURED page box
       844x288 — 102px shorter — and the floor became larger than the space
       that exists. The workspace is 94px tall there, so the row forced 148px
       and painted 54px of art cards outside their own container. A minimum
       that exceeds its container is an overflow, not a minimum: the row fills
       whatever height the workspace actually has, and the selector's 8/3
       aspect-ratio still governs the cards inside it. */
    grid-template-rows: minmax(0, 1fr);
    gap: 0;
    height: 100%;
    min-height: 0;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-fieldset > legend {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-hint {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-preview {
    display: none;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-selector {
    grid-column: 1;
    grid-row: 1;
    display: grid;
    width: auto;
    max-width: 100%;
    height: 100%;
    max-height: 100%;
    aspect-ratio: 8 / 3;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr));
    align-self: center;
    justify-self: center;
    gap: 4px;
    min-height: 0;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-selector .moon-dream-art-card {
    width: 100%;
    height: 100%;
    min-height: 0;
    aspect-ratio: 16 / 9;
    border-radius: 7px;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-selector .moon-dream-art-visual {
    height: auto;
    aspect-ratio: 16 / 9;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-selector
    .moon-dream-art-visual .moon-dream-empty {
    padding: 4px;
    overflow-wrap: anywhere;
    font-size: 10px;
    line-height: 1.05;
  }

  #moon-dream-detail[data-stage="portraits"] .moon-dream-art-selector .moon-dream-art-label {
    inset: auto 2px 2px;
    max-width: none;
    padding: 2px 4px;
    font-size: 9px;
    text-align: center;
  }

  #moon-dream-detail[data-stage="spells"] > .moon-dream-showcase {
    display: none;
  }

  /* Delete Summoner steps aside while spells are generating on a landscape
     phone. The detail is 203px at an 844x288 page box and the chrome wanted
     153 of it — back bar 44, this row 44, pagination 44, progress 21 — leaving
     50px for cards that need 110, so every card painted 28px outside its own
     container. Back and pagination are navigation and cannot go; the progress
     strip is contractually visible. A destructive action nobody needs mid
     generation is the one thing here that can wait for a bigger screen, and it
     is still available on every other stage and size. */
  #moon-dream-detail[data-stage="spells"] > .moon-dream-secondary-actions {
    display: none;
  }

  /* The progress strip stays VISIBLE — it is contractually required and it is
     the only feedback that generation is still running — but it does not need
     21px to say so. Trimmed to a thin bar, which is the last 16px the card row
     needs to reach a legible height in a 203px detail. */
  #moon-dream-detail[data-stage="spells"] > .moon-dream-status-card {
    padding: 1px 8px;
  }

  #moon-dream-detail[data-stage="spells"] .moon-dream-progress {
    min-height: 0;
    gap: 0;
    font-size: 8px;
    line-height: 1.05;
  }

  /* With the height finally available, size the cards from the container so
     four of them fit BY CONSTRUCTION rather than from a fixed unit floor that
     knows nothing about the row it is in. The completed detail already does
     exactly this; the generating state was the only one left out. */
  /* The slot and the card fill the row they were given. Without this the card
     kept a taller intrinsic height than the container query allowed and hung
     12px out of its own grid — better than the 56px it started at, but still
     painting over the pagination beneath it. */
  #moon-dream-detail[data-stage="spells"] .moon-dream-spells-grid .moon-dream-spell-card,
  #moon-dream-detail[data-stage="spells"] .moon-dream-spells-grid .moon-dream-spell-card .card {
    height: 100%;
    max-height: 100%;
    min-height: 0;
  }

  #moon-dream-detail[data-stage="spells"] .moon-dream-spells-grid {
    container-type: size;
    --card-unit: min(4.5cqh, calc((100cqw - 8px) / 4 / 12.29));
    --card-width: calc(12.29 * var(--card-unit));
    --card-height: calc(22.2 * var(--card-unit));
    --catalog-card-scale: 1;
  }

  #moon-dream-detail[data-stage="spells"] > .moon-dream-spells {
    display: grid;
    grid-template-rows: minmax(0, 1fr) auto;
    flex: 1 1 auto;
    min-height: 0;
    padding: 0 7px;
    overflow: hidden;
  }

  #moon-dream-detail[data-stage="spells"] > .moon-dream-spells
    > :is(.moon-dream-detail-title, .moon-dream-detail-actions) {
    display: none;
  }

  #moon-dream-detail[data-stage="spells"] .moon-dream-spells-grid {
    --catalog-card-scale: 1.55;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    grid-template-rows: minmax(0, 1fr);
    align-items: center;
    justify-items: center;
    gap: 2px;
    min-height: 0;
    padding: 0;
    overflow: visible;
  }

  #moon-dream-detail[data-stage="spells"]
    .moon-dream-spells--paginated .moon-dream-spells-grid {
    --catalog-card-scale: 1.133;
  }

  #moon-dream-detail[data-stage="spells"] .moon-dream-pagination {
    min-height: 44px;
    margin: 0;
  }

  #moon-dream-detail[data-stage="spells"] .moon-dream-pagination[hidden] {
    display: none;
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-secondary-actions {
    position: static;
    z-index: auto;
    flex: 0 0 44px;
    height: 44px;
    min-height: 44px;
    align-items: center;
    justify-content: flex-start;
    padding: 0 7px;
    border: 0;
    background: rgba(10, 9, 22, 0.28);
  }

  #moon-dream-detail:is([data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-secondary-actions .moon-dream-delete {
    min-height: 44px;
    padding: 5px 8px;
    background: rgba(10, 9, 22, 0.72);
    font-size: 10px;
  }

  #moon-dream-detail:is([data-stage="selection"], [data-stage="portraits"], [data-stage="spells"]) {
    --moon-dream-identity-lane-width: clamp(280px, 44vw, 320px);
  }

  #moon-dream-detail[data-stage="selection"] {
    position: relative;
    /* mobile.css gives this column an 8px gap. Around the 3px progress strip
       that is 19px of the ~138px between the command row and the commit bar
       at 844x288: 14% the six paid portrait choices never got (measured art
       grid share 0.862; the real-backend spec asks for more than 0.9). 3px
       keeps the strip legible as a separator and hands the rest to the grid. */
    gap: 3px;
    overflow-y: hidden;
  }

  /* Keep the generated summoner identifiable without taking height away from
     the six paid portrait choices. The identity occupies a dedicated slice of
     the existing 44px command row; the three journey steps use the remainder. */
  #moon-dream-detail:is([data-stage="selection"], [data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-detail-command {
    grid-template-columns:
      minmax(108px, auto)
      var(--moon-dream-identity-lane-width)
      minmax(0, 1fr);
  }

  #moon-dream-detail:is([data-stage="selection"], [data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-detail-command .moon-dream-stage-journey {
    min-width: 0;
    gap: 2px;
    padding: 2px 3px;
  }

  #moon-dream-detail:is([data-stage="selection"], [data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-detail-command .moon-dream-stage-step {
    min-width: 0;
    padding: 2px;
    overflow-wrap: anywhere;
    font-size: 9px;
    line-height: 1.05;
  }

  #moon-dream-detail:is([data-stage="selection"], [data-stage="portraits"], [data-stage="spells"])
    > .moon-dream-detail-command
    > .moon-dream-detail-identity {
    display: grid;
    grid-template-rows: auto auto;
    align-content: center;
    min-width: 0;
    padding: 0 7px;
    overflow: visible;
    border-right: 1px solid rgba(70, 70, 104, 0.45);
  }

  .moon-dream-detail-identity > .moon-dream-detail-title {
    min-width: 0;
    overflow: visible;
    overflow-wrap: anywhere;
    white-space: normal;
    font-size: 11px;
    line-height: 12px;
  }

  /* CLAMPED, and deliberately so — this is the one exception to the untruncated
     prose rule below it, and the reason is arithmetic. The identity sits in a
     fixed 44px command row beside the journey steps; the title takes 12px and
     the summary's own line box is 10.5px, so the row affords the summary two
     lines. A real lore sentence in a lane this narrow wraps to four or five and
     spilled out over the journey steps at 844x288 — visible only once the
     detail actually re-rendered at phone size, which it never used to do.
     Nothing is lost: the full text is on the element's `title` and printed in
     full on the completed detail. */
  .moon-dream-detail-identity > .moon-dream-detail-summary {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    min-width: 0;
    max-height: 21px;
    overflow: hidden;
    white-space: normal;
    overflow-wrap: anywhere;
    font-size: 10px;
    line-height: 10.5px;
  }

  /* A transient detail failure needs its own compact command lane. Keeping the
     notice beside (not on top of) the journey preserves the 3x2 art budget and
     leaves Portraits / Choose / Spells readable while the failed read retries. */
  #moon-dream-detail[data-stage="selection"]:has(> .moon-dream-status-card .moon-dream-detail-refresh-error) {
    --moon-dream-refresh-lane-width: clamp(180px, 29vw, 208px);
  }

  #moon-dream-detail[data-stage="selection"]:has(> .moon-dream-status-card .moon-dream-detail-refresh-error)
    > .moon-dream-detail-command {
    padding-right: calc(var(--moon-dream-refresh-lane-width) + 7px);
    /* The lane eats ~200px of this row. Both text columns keep bounded
       flexibility: an unbounded journey (fr labels reach ~315px) or identity
       collapses the other to 0 width, stacking its text letter-per-line and
       starving the 3x2 art grid below. */
    grid-template-columns:
      minmax(108px, auto)
      minmax(0, 1fr)
      minmax(140px, 1fr);
  }

  #moon-dream-detail[data-stage="selection"]:has(> .moon-dream-status-card .moon-dream-detail-refresh-error)
    > .moon-dream-detail-command .moon-dream-detail-identity > .moon-dream-detail-title,
  #moon-dream-detail[data-stage="selection"]:has(> .moon-dream-status-card .moon-dream-detail-refresh-error)
    > .moon-dream-detail-command .moon-dream-detail-identity > .moon-dream-detail-summary {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  #moon-dream-detail[data-stage="selection"] > .moon-dream-status-card {
    flex: 0 0 3px;
    min-height: 3px;
    display: block;
    padding: 0 7px;
    overflow: visible;
    border-bottom: 0;
  }

  #moon-dream-detail[data-stage="selection"] > .moon-dream-status-card > .moon-dream-detail-actions:not(.moon-dream-status-notice) {
    display: none;
  }

  #moon-dream-detail[data-stage="selection"] > .moon-dream-status-card:has(.moon-dream-status-notice) {
    overflow: visible;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-status-notice {
    position: absolute;
    z-index: 9;
    top: 5px;
    right: 7px;
    display: grid;
    width: var(--moon-dream-refresh-lane-width, min(208px, calc(100% - 14px)));
    min-height: 34px;
    max-height: 34px;
    place-items: center;
    margin: 0;
    padding: 3px 7px;
    overflow: hidden;
    overflow-wrap: anywhere;
    white-space: normal;
    border: 1px solid rgba(255, 125, 145, 0.34);
    border-radius: 8px;
    background: rgba(13, 10, 24, 0.94);
    color: #ffd3db;
    font-size: 10px;
    line-height: 1.15;
    text-align: center;
    pointer-events: none;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-progress {
    display: block;
    height: 3px;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-progress-bar {
    height: 3px;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-progress-meta {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  #moon-dream-detail[data-stage="selection"] > .moon-dream-art-workspace {
    flex: 1 1 auto;
    min-height: 0;
    padding: 0 7px;
    overflow: hidden;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-fieldset {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) 44px;
    gap: 0;
    height: 100%;
    min-height: 0;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-fieldset > legend {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-hint {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-preview {
    display: none;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-selector {
    grid-column: 1;
    grid-row: 1;
    display: grid;
    width: auto;
    max-width: min(100%, 560px);
    height: 100%;
    max-height: 100%;
    aspect-ratio: 8 / 3;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    grid-template-rows: repeat(2, minmax(0, 1fr));
    align-self: center;
    justify-self: center;
    gap: 4px;
    min-height: 0;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-selector .moon-dream-art-card {
    min-height: 0;
    aspect-ratio: 16 / 9;
    border-radius: 7px;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-selector .moon-dream-art-visual {
    height: auto;
    aspect-ratio: 16 / 9;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-art-selector .moon-dream-art-label {
    inset: auto 2px 2px;
    max-width: none;
    padding: 2px 4px;
    font-size: 11px;
    text-align: center;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar {
    position: static;
    grid-column: 1;
    grid-row: 2;
    width: calc(100% - 132px);
    height: 44px;
    justify-self: end;
    grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.75fr) minmax(160px, 1.2fr);
    grid-template-rows: 44px;
    align-items: center;
    gap: 4px;
    margin: 0;
    padding: 0 4px;
    overflow: hidden;
    border: 0;
    border-radius: 7px;
    box-shadow: inset 0 0 0 1px rgba(246, 226, 122, 0.3);
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar :is(.moon-dream-permanent-warning, .moon-dream-language-status, .moon-dream-balance-warning, .moon-dream-selection-status) {
    min-width: 0;
    max-height: 44px;
    overflow-wrap: anywhere;
    font-size: 10px;
    line-height: 1.25;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-permanent-warning {
    grid-column: 1 / 3;
    grid-row: 1;
    font-size: 10px;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-language-status {
    grid-column: 1;
    grid-row: 1;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-balance-warning {
    grid-column: 2;
    grid-row: 1;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-selection-status {
    grid-column: 1 / 3;
    grid-row: 1;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar:not(:has(.moon-dream-balance-warning))
    .moon-dream-language-status,
  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar:not(:has(.moon-dream-language-status))
    .moon-dream-balance-warning {
    grid-column: 1 / 3;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit-bar:has(.moon-dream-language-status, .moon-dream-balance-warning, .moon-dream-selection-status:not(:empty)) .moon-dream-permanent-warning {
    display: none;
  }

  #moon-dream-detail[data-stage="selection"] .moon-dream-commit {
    grid-column: 3;
    grid-row: 1;
    min-height: 44px;
    padding: 5px 7px;
    font-size: 10px;
  }

  #moon-dream-detail[data-stage="selection"] > .moon-dream-secondary-actions {
    position: absolute;
    z-index: 8;
    left: 7px;
    bottom: 0;
    width: 124px;
    height: 44px;
    min-height: 44px;
    justify-content: flex-start;
    padding: 0;
    border: 0;
    background: transparent;
  }

  #moon-dream-detail[data-stage="selection"] > .moon-dream-secondary-actions .moon-dream-delete {
    width: 100%;
    min-height: 44px;
    padding: 5px 8px;
    background: rgba(10, 9, 22, 0.72);
    font-size: 11px;
  }

  /* 31% was a sidebar for a thumbnail. The deck's cards are sized by
     `--catalog-card-scale` and four of them need about 390px here, so the
     column can take 38% of an 800px panel without the deck losing a pixel —
     measured, not assumed. */
  .foundry-surface-root .moon-dream-right.moon-dream-detail--complete {
    grid-template-columns: minmax(200px, 38%) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr) auto;
    gap: 6px;
    padding: 6px;
    overflow: hidden;
  }

  .moon-dream-detail--complete .moon-dream-showcase,
  .moon-dream-detail--complete .moon-dream-spells {
    padding: 6px;
    border-radius: 10px;
  }

  .moon-dream-detail--complete .moon-dream-portrait-frame {
    /* Ratio-true and shrink-whole: a width-driven frame in a short row was
       CROPPED to a horizontal sliver of the art ("do not crop part of the
       summoner pic", photographed). Height flexes with the row; width
       follows the ratio; the art always shows complete. */
    flex: 1 1 auto;
    min-height: 0;
    width: auto;
    max-width: 100%;
    align-self: center;
    aspect-ratio: 16 / 9;
    border-radius: 10px;
  }

  .moon-dream-detail--complete .moon-dream-portrait {
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
  }

  /* HIDDEN, not shrunk. This is the same string as the lore card's title two
     rows below it, and on a landscape page box those eighteen pixels are the
     difference between a portrait and a postage stamp. The name is still on
     screen, still once. */
  .moon-dream-detail--complete .moon-dream-portrait-name {
    display: none;
  }

  /* SIZED TO BOTH AXES, not just the row's height. `--catalog-card-scale: 1.55`
     was a fixed multiplier against a track it could not see, so on an 852x344
     page box four cards came out 119.7px wide in tracks narrower than that and
     overlapped each other by 26px — a defect that predates this change and got
     worse the moment the summoner's column took its share. A card is 12.29
     units wide and 22.2 tall, so taking the smaller of "the row's height" and
     "a quarter of the row's width" makes four of them fit by construction, at
     the largest size the space actually allows. */
  .moon-dream-detail--complete .moon-dream-spells-grid {
    container-type: size;
    --card-unit: min(4.05cqh, calc((100cqw - 8px) / 4 / 12.29));
    --card-width: calc(12.29 * var(--card-unit));
    --card-height: calc(22.2 * var(--card-unit));
    --catalog-card-scale: 1;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    grid-template-rows: minmax(0, 1fr);
    gap: 2px;
    padding: 0;
  }

  .moon-dream-detail--complete .moon-dream-spells .moon-dream-detail-title {
    font-size: 13px;
  }

  .moon-dream-detail--complete .moon-dream-spells .moon-dream-pagination {
    margin-top: 2px;
  }

  .moon-dream-detail--complete .moon-dream-complete-lore {
    grid-column: 1 / -1;
    grid-row: 2;
    gap: 2px 6px;
    padding: 5px 6px;
  }

  .moon-dream-detail--complete .moon-dream-spells {
    grid-row: 1;
  }

  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-summary {
    font-size: 11px;
  }

  .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-title {
    font-size: 12px;
  }

  .moon-dream-detail--complete .moon-dream-delete {
    min-height: 44px;
    padding: 5px 8px;
  }
}

@media (orientation: landscape) and (max-height: 560px) and (min-width: 760px) {
  .foundry-surface-root .moon-dream-art-selector {
    grid-template-columns: repeat(3, minmax(0, 1fr));
  }
}

/* ---- Moon Fuse empty combine-canvas ----------------------------------------
   A ghosted dashed square and a fusion glyph used to sit here to make the empty
   left half read as a drop target. Removed: on a phone the canvas is only about
   640x235, and a dashed box plus a glyph plus the trash bin plus the hint line
   filled it — reported twice as "crowded and not readable". The sidebar rows
   are visibly draggable and the bin already marks the canvas as a drop surface,
   so the empty state is left clean. Desktop never had this. ---------------- */

/* Non-blocking foundry notices (capacity/cooldown/queue) — replaces alert() */
#foundry-notice-stack {
  position: fixed;
  top: max(12px, env(safe-area-inset-top));
  left: 50%;
  transform: translateX(-50%);
  z-index: 100001;
  display: flex;
  flex-direction: column;
  gap: 8px;
  width: min(calc(100vw - 24px), 520px);
  pointer-events: none;
}

.foundry-notice-toast {
  pointer-events: auto;
  cursor: pointer;
  background: linear-gradient(135deg, #2a2a4a, #1a1a2e);
  border: 1px solid rgba(182, 156, 255, 0.65);
  border-radius: 10px;
  padding: 10px 16px;
  color: #e9e6f5;
  font-size: 13px;
  line-height: 1.4;
  box-shadow: 0 6px 22px rgba(0, 0, 0, 0.45);
  animation: foundryNoticeIn 0.25s ease-out;
}

.foundry-notice-toast.closing {
  opacity: 0;
  transition: opacity 0.3s ease-in;
}

@keyframes foundryNoticeIn {
  from { transform: translateY(-8px); opacity: 0; }
  to { transform: translateY(0); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .foundry-notice-toast { animation: none; }
}

/* ---------------------------------------------------------------------------
   MOON FUSE COMMAND BAR ON A LANDSCAPE PHONE

   The bar may not overflow (mobile.real.spec.js asserts scrollWidth fits), so
   when the content is wider than the box the tabs — which carry ellipsis — are
   what gives, and the user is left navigating "← Lob…", "Moon F…", "Moon Dre…".
   Truncating the NAVIGATION to preserve a search field and a price sentence is
   backwards. Free the width from those instead: the tabs keep their labels, the
   search field shrinks to a tappable stub, and the cost keeps its number while
   dropping the prose (its accessible name still reads in full).
   --------------------------------------------------------------------------- */
@media (orientation: landscape) and (max-height: 560px) {
    .foundry-tabs .foundry-tab {
        flex: 0 0 auto;
        overflow: visible;
        text-overflow: clip;
    }

    /* The tabs above are deliberately unshrinkable, but .foundry-tablist is a
       shrinkable flex item (flex: 0 1 auto) sitting next to .foundry-context-tools,
       which GROWS (flex: 1 1 auto). So the tools claimed the free space, the
       tablist was squeezed under its content width, and the tabs — unable to
       shrink — spilled out of their own container onto the search field beside
       them. That is the reported "search bar slightly overlaps Moon Dream".
       It only appears once env(safe-area-inset-left/right) take 59px EACH out
       of this row on a notched phone in landscape, which is why a zero-inset
       phone-viewport run always showed a comfortable 6-8px gap and never caught
       it. Pinning the tablist to its content makes the SEARCH FIELD absorb the
       deficit instead — which is exactly what the block above already intends
       ("the tabs keep their labels, the search field shrinks to a tappable
       stub"); the field is flex: 1 1 0 with min-width: 0, so it can. */
    .foundry-tabs .foundry-tablist {
        flex: 0 0 auto;
        min-width: max-content;
    }

    .foundry-tab-tools #search-input,
    .foundry-inventory-search-slot > #search-input {
        flex: 1 1 0;
        min-width: 0;
        width: auto;
    }

    /* Number + star only; `Discovery cost` is a label the screen cannot afford
       and the pill is self-evident beside the fuse controls.

       THIS RULE HAD NEVER FIRED. It selects `.moon-fuse-cost`, and no element
       in the app carries that class — the note is
       `<span id="moon-fuse-cost" class="foundry-cost-note">`. So the half of
       this block's stated design that was supposed to free width ("the cost
       keeps its number while dropping the prose") did nothing for years, and
       the note stayed the row's largest immovable object. It is worst on
       entry, where the placeholder is a whole sentence — French
       "Chargement des coûts de fusion..." is wider than both tabs put
       together. Its full text remains the element's `title`.

       Repaired onto `#moon-fuse-cost` rather than the shared class: this note
       leads with its number on a compact viewport, the Moon Dream one trails
       it, and a 6.5em end-ellipsis would erase the Moon Dream price. */
    #moon-fuse-cost.foundry-cost-note {
        font-size: 11px;
        max-width: 6.5em;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
    }
}


/* ---- Moon Fuse on a real phone PAGE BOX (~288pt, not the ~400pt "landscape
   phone" a screen-sized viewport implies). Here the 560px tier's 54px rows put
   just TWO elements in the 170px list — 65 pages of inventory — and the sidebar
   ran its Next button under the Dynamic Island. Everything below only tightens
   what that tier already established. ---------------------------------------- */
@media (orientation: landscape) and (max-height: 360px) {
  .foundry-surface-root #sidebar {
    /* The sidebar owns the right screen edge, which on a notched phone in
       landscape is 59pt of Dynamic Island. Its background may bleed there; its
       Next button and element names may not. */
    padding-right: var(--mf-safe-right);
    box-sizing: border-box;
  }

  /* 36px still holds two wrapped 11px lines (2 x 12.65 + 8 of padding = 33),
     so names stay whole — they are just set smaller. Five rows instead of two. */
  .foundry-surface-root #element-list {
    --foundry-inventory-item-height: 36px;
    padding: 6px 6px 6px 8px;
    gap: 4px;
  }

  .foundry-surface-root .sidebar-element {
    padding: 2px 8px;
    font-size: 11px;
    line-height: 1.1;
    letter-spacing: 0;
    gap: 5px;
  }

  /* The name is a flex item in a fixed-height row: without stretch the flex
     line hands it a cross-size smaller than its own two wrapped lines, and the
     row's overflow:hidden shaves the descenders off. */
  .foundry-surface-root .sidebar-element > span:nth-child(2) {
    align-self: stretch;
    display: flex;
    align-items: center;
  }

  .foundry-surface-root .sidebar-element .emoji {
    font-size: 14px;
    /* The row's tightened 1.1 line-height is for the wrapping NAME; an emoji
       glyph overflows a line box that snug. */
    line-height: 1.5;
    flex: 0 0 auto;
  }

  /* "Page 1 of 33" needs the middle; the two 44px buttons already have theirs. */
  .foundry-surface-root .sidebar-page-info {
    flex: 1 1 auto;
    text-align: center;
    font-size: 10px;
    letter-spacing: 0;
  }

  .foundry-surface-root .sidebar-pagination button {
    flex: 0 0 auto;
  }

  /* The canvas is ~640x235 here. A centred paragraph of instructions plus a
     dashed bin is most of it — the crowding the player reported. Keep the bin
     (it is a drop target) and retire the prose; the sidebar rows are visibly
     draggable and the tutorial covers the rest. */
  .foundry-surface-root #hint-text {
    display: none;
  }

  .foundry-surface-root .trash-zone {
    left: calc(var(--mf-safe-left) + 8px);
    bottom: calc(var(--mf-safe-bottom) + 6px);
    padding: 6px 10px;
    font-size: 11px;
  }
}

/* ---- Moon Dream on a real phone PAGE BOX (~288pt) --------------------------
   Reported from an iPhone: the "← Summoners" button printed on the summoner
   portrait, the "Spell deck" heading was covered by the first card, and Prev /
   Next sat on the fan. Two causes — a floating back button anchored to the same
   corner the portrait starts in, and a --catalog-card-scale tuned for a ~400pt
   screen, which makes the cards taller than the 1fr row they live in so they
   paint over the rows above and below. -------------------------------------- */
@media (orientation: landscape) and (max-height: 400px) {
  /* The lore row was `auto`, so a long summoner bio took whatever height it
     wanted and the showcase — the 1fr in the middle — was left with whatever
     remained. On this page box that was nothing: the portrait collapsed to a
     ~10px sliver while the bio still ran off the bottom of the screen
     ("Moon Dream is cursed... has cropped elements"). Give the portrait a real
     floor and make the LORE the part that yields, since text can scroll and a
     picture cannot. */
  .foundry-surface-root .moon-dream-right.moon-dream-detail--complete {
    /* Two rows, not three: the old dedicated Back row starved the portrait.
       Back and Delete now share the lore card's explicit bottom control band.
       Reserve one readable summary line as well as that 44px band; the former
       150px portrait floor left the 800x360 biography viewport shorter than a
       line box, so it could scroll but could never expose a whole character. */
    grid-template-rows: minmax(136px, 1fr) minmax(104px, auto);
  }

  /* Only the SUMMARY scrolls. Scrolling the whole lore let the control band sit
     below a fold nothing indicates — measured at y=250..294 on a 288 page box.
     Title and both actions stay pinned; refresh errors join the same honest
     summary scroll region. Every child is seated EXPLICITLY —
     auto-placement against the desktop's two-column lore is how the name
     ended up printed across the description ("their name overlaps the
     description", photographed). */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore {
    min-height: 0;
    overflow: hidden;
    position: relative;
    grid-template-columns: minmax(0, 1fr);
    /* title / flexible biography / controls. Keeping the buttons in a real
       track prevents the biography, Delete, and Back from painting through one
       another; only the biography yields and scrolls. */
    grid-template-rows: minmax(0, auto) minmax(0, 1fr) var(--mf-touch, 44px);
    /* `align-items: stretch`, at the tier where the controls anchor — the same
       correction the 320 block already carried, and for the same reason. The
       desktop lore CENTRES its items; inherited here, a description whose
       content is taller than its row stops being a scroller and becomes a box
       that overflows equally in both directions, painting straight through the
       title above it. Measured at 852x344: 67% of the title buried, with the
       account's own thirteen-character name, before any long-name fixture. */
    align-items: stretch;
  }

  /* The title owns the full row. Reserving horizontal gutters for controls in
     other rows reduced a valid 40-character name to a stack of single letters
     on the 667px page box. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-detail-title {
    grid-column: 1;
    grid-row: 1;
    height: auto;
    min-height: 40px;
    display: flex;
    align-items: center;
    /* 14px, not 16: at this tier the name shares its row with two reserved
       gutters, and two points of type is the difference between a 32-character
       compound name wrapping to two lines and wrapping to three. */
    font-size: 14px;
    line-height: 1.25;
    /* No gutters. Both controls live on the card's bottom band now, so the
       name owns the full width — which is the difference between a
       forty-character name wrapping twice and breaking mid-word four times. */
    padding-right: 0;
    padding-left: 0;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-detail-summary {
    grid-column: 1;
    grid-row: 2;
  }

  /* Delete owns the left half of a real bottom band. Back is the sibling grid
     item's right half of that same band. Each stays below 46%, so even the
     667px page box leaves a gap between the touch targets. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-secondary-actions {
    position: static;
    z-index: 3;
    grid-column: 1;
    grid-row: 3;
    align-self: end;
    justify-self: start;
    justify-content: stretch;
    width: 46%;
    max-width: 96px;
    padding: 0;
    border: 0;
    margin-left: 4px;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-secondary-actions .moon-dream-delete {
    width: 100%;
    height: var(--mf-touch, 44px);
    min-height: var(--mf-touch, 44px);
    padding: 4px 6px;
    font-size: 11px;
    line-height: 1.1;
    white-space: normal;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-summary {
    min-height: 0;
    overflow-y: visible;
    padding-left: 0;
    padding-right: 0;
  }

  /* Back owns the bottom-right half beside Delete's bottom-left. Both controls
     are normal-flow grid items, so neither can paint through the biography. */
  .foundry-surface-root .moon-dream-detail--complete > .moon-dream-detail-command {
    position: static;
    grid-column: 1;
    grid-row: 2;
    align-self: end;
    justify-self: end;
    z-index: 2;
    max-width: 46%;
    margin: 0 4px 4px 0;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-showcase {
    grid-row: 1;
    /* Not `min-height: 0`: that is exactly the permission the row needed to
       collapse the portrait to a sliver when the lore grew. The row already
       carries the floor; the showcase must not opt out of it. Raised with the
       row above, now that the Delete control no longer costs a row. */
    min-height: 136px;
    overflow: hidden;
  }

  /* The portrait is width-driven (width:100% + aspect-ratio), so on a short row
     it computes TALLER than the box holding it — and the showcase CENTRES its
     overflow, which spills equally top and bottom and is how it reached up into
     the command row. Anchor it to the top so what is lost is the bottom of the
     frame rather than the summoner's face, and let the showcase clip it.
     (Height-driving it instead collapses the box to the aspect ratio's minimum
     when the row height is not definite — measured at 16x9.) */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-showcase {
    justify-content: flex-start;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells {
    grid-row: 1 / 3;
  }

  /* ---- the very short page boxes (a landscape iPhone is ~288pt) -----------
     Measured on WebKit at 844x288: the panel starts at y=71 under the foundry
     header, so it has 217px — and the grid was asking for ~237. The overflow
     landed on the LAST row, which is where Delete Summoner lives: its box ran
     to y=315, twenty-seven points below a screen that ends at 288. The button
     was not merely below the fold, it was unreachable.
     The spec that was supposed to catch this only measured the lore box and
     passed; it now checks every control against the page box.
     The portrait floor is what yields, because it is the only row with slack:
     76px is still a portrait, while a Delete control off the bottom of the
     screen is a missing feature. */
  @media (max-height: 320px) {
    .foundry-surface-root .moon-dream-right.moon-dream-detail--complete {
      grid-template-rows: minmax(118px, 1fr) minmax(0, auto);
      /* The grid must be bounded by the panel, or the rows simply grow past
         the bottom again and nothing above can stop them. */
      min-height: 0;
      max-height: 100%;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-showcase {
      /* Raised from 76 with the Delete control's row: the summoner is the
         payoff of this screen and a 76px band rendered him at 76x43, smaller
         than one of his own spell cards. */
      min-height: 118px;
      gap: 2px;
      padding: 4px;
    }

    /* ONE LINE, with an ellipsis, and only at this tier — a deliberate,
       measured trade at the single smallest page box.
       Arithmetic: the lore card is 67px tall here and the control band it has
       to carry is 48 (a 44px touch target plus its margin). That leaves 19px,
       which is one line of 14px type. A name allowed to wrap to two lines
       reaches into the band and prints on the Delete button — measured at 21%
       and 25%. Every taller page box keeps the name whole and wrapping; only
       288pt cannot. Both rendered name copies carry the complete string in
       `title` and `aria-label`, so truncation never removes its accessible
       value. */
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-detail-title {
      min-height: 0;
      font-size: 14px;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      display: block;
    }

    /* Compact BOX, full touch target. The control keeps `--mf-touch` of height
       and its whole label; only the padding around the label gives way, which
       is what buys the name its room. */
    /* WRAPS, never truncates. Ellipsising a destructive control into "Delete
       Summo…" is worse than a second line: the whole point of the label is
       that the player knows exactly what the button destroys. Two 10px lines
       fit inside the 44px touch target that stays untouched. */
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-delete {
      padding-inline: 6px;
      font-size: 10px;
      line-height: 1.15;
      white-space: normal;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-secondary-actions {
      max-width: 76px;
    }

    /* The portrait's aspect-ratio height is a RIGID min-content: flex items
       default to min-height auto, so the frame could not shrink into its row
       and poked 6px past the page box (measured 294 on a 288 box). The
       min-height: 0 chain lets the column squeeze it; object-fit: cover crops
       the art gracefully instead of breaking the frame. */
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-showcase .moon-dream-portrait {
      flex: 1 1 auto;
      min-height: 0;
      gap: 4px;
    }

    /* THE FRAME IS SIZED BY THE LAYOUT, NEVER BY ITS IMAGE.
       `flex: 0 1 auto` in a COLUMN flex container makes the frame's flex BASE
       size content-derived, and `.moon-dream-portrait` centres its items — so
       the frame's cross size is shrink-to-fit, `aspect-ratio: 16/9` has no
       definite width to transfer from, and the base size is whatever the
       `<img>` contributes. With a decoded 1024x576 portrait that is fine. With
       an image contributing NOTHING — not yet decoded, swapped mid-render,
       errored — the frame collapses to its two borders and the aspect ratio
       then derives the width from a ~0 height, producing a 16/9 sliver.
       Measured on a synthetic page carrying the real stylesheets, in BOTH
       engines: a 1x1 image renders the frame 4x2, a 1024x576 image renders it
       192x108. A dogfooder reported this frame at 28x16 in WebKit after a
       viewport resize; 28/16 is 1.75, which is that sliver.
       `align-self: stretch` makes the cross size definite, so the frame takes
       its size from the column it sits in and the aspect ratio drives the
       height. It still shrinks (`flex-shrink: 1`, `min-height: 0`), so the
       288pt fitting above is unaffected — the frame simply no longer depends on
       whether its art has decoded. */
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-portrait-frame {
      flex: 0 1 auto;
      align-self: stretch;
      min-height: 0;
    }

    /* The lore is a scroller, so it is the right thing to squeeze — reading
       text by scrolling is normal, reaching a button that is off screen is
       not.

       `align-items: stretch` is what makes it ACTUALLY a scroller here, and it
       is load-bearing. The desktop lore is a two-column grid that CENTRES its
       items, and this layout inherited that. On a 288pt page box the summary's
       row is nine pixels, and at that size centring stops meaning "centre" and
       starts meaning "overflow equally in both directions": the box painted
       41px of text out of a 9px row, 16px of it ABOVE the row — straight under
       the back chip, whose 44px touch target the title row is carefully
       reserving space for. The chip printed across the summoner's first line,
       at rest, in all three engines. Stretching is what makes that reservation
       mean anything.
       This is deliberately scoped to the 320 tier: the taller landscape boxes
       give the summary a row big enough to hold the whole description, and
       `verify-moondream-fit.real.spec.js` holds them to painting all of it. */
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore {
      min-height: 0;
      align-items: stretch;
    }

    /* Whatever else happens, the destructive control keeps its tap target and
       stays on screen. */
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-delete {
      min-height: var(--mf-touch, 44px);
      flex: 0 0 auto;
      margin-bottom: max(2px, env(safe-area-inset-bottom, 0px));
    }
  }

  /* ---- THE NARROW PAGE BOX: THE NAME LEAVES THE LORE CARD ----------------
     PLACED LAST ON PURPOSE, AND IT HAS TO BE.
     Every rule below contradicts one in the `max-height: 320px` tier above,
     and 667x275 matches BOTH. When this block sat before that tier it lost
     every one of those conflicts at equal specificity: the lore title came
     back (so the name painted TWICE), and the lore card ended up with three
     grid rows AND the 48px of bottom padding the other tier reserves for a
     control that is no longer absolute — about 94px of reservation in a card
     67px tall. Measured, not reasoned: a cascade probe read
     `grid-template-rows: 17.5px 13.75px 46px` with `padding-bottom: 48px`
     at 667x275. Ordering is the fix; duplicating rules into the other tier
     (which is what the first draft did for the showcase floor alone) only
     patches whichever conflict someone happened to notice.

     WHY THE NAME MOVES AT ALL. At 667x275 the lore card is 220px wide and
     54px tall. Both controls share its bottom band everywhere else, which is
     what gives the summoner's name the card's full width; at this width the
     band is most of the card, so the controls go back into the flow — and
     that leaves no row for the name, which simply did not paint. A collision
     oracle cannot see that: an element squeezed to zero height is skipped as
     invisible, so the suite stayed green while the summoner had no name.
     The name therefore moves OUT of the lore card here and sits under the
     portrait, where the column is nearly 200px wide and the copy has room.
     The PICTURE does not pay for it: the name is a `flex: 0 0 auto` line in
     the portrait's own column, so left alone it takes its ~18px straight out
     of the frame (measured: 193.8x109 fell to 161.1x90.6). The lore card is
     what gave the name up, so the lore card is what pays — the showcase's
     floor and the detail grid's first row both rise by exactly that line. */
  @media (max-width: 700px) {
    /* AND THE PICTURE DOES NOT PAY FOR IT.
       The name is a `flex: 0 0 auto` line inside the portrait's own column, so
       left alone it takes its ~18px straight out of the frame — measured, the
       portrait fell from 193.8x109 to 161.1x90.6, a third of its area, at the
       one page box whose ratio was highest. The lore card is what gave the
       name up, so the lore card is what pays: the showcase's floor rises by
       exactly the line it gained, and the detail grid's first row rises with
       it. */
    .foundry-surface-root .moon-dream-right.moon-dream-detail--complete {
      grid-template-rows: minmax(136px, 1fr) minmax(0, auto);
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-showcase {
      min-height: 136px;
    }

    /* The name, under the picture it names. */
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-portrait-name {
      display: block;
      flex: 0 0 auto;
      max-width: 100%;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      font-size: 12px;
      line-height: 1.2;
      text-align: center;
    }

    /* ...and once only. The lore's title is the same string. */
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-detail-title {
      display: none;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore {
      grid-template-rows: minmax(0, 1fr) var(--mf-touch, 44px);
      padding-bottom: 6px;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-secondary-actions {
      position: static;
      width: 46%;
      max-width: 96px;
      grid-column: 1;
      grid-row: 2;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-detail-title,
    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore .moon-dream-detail-summary {
      padding-left: 0;
      padding-right: 0;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore > .moon-dream-detail-summary {
      grid-row: 1;
    }
  }

  /* The base grid has two rows and puts lore in its last row. Keep this
     explicit before the short-page-box override below, which deliberately
     spans the otherwise empty command rail in row 2. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore {
    grid-column: 1;
    grid-row: 2;
  }

  /* Short landscape page boxes have horizontal room but no spare vertical row.
     Keep the completed summoner and its description in the exact left-hand
     column, and spend that horizontal room on a narrow command rail between
     the summoner and spell deck. Back and Delete no longer consume the
     biography's line boxes; the complete text paints under the portrait while
     both 44px controls remain permanently visible and clear of all content. */
  @media (min-width: 620px) {
    .foundry-surface-root .moon-dream-content:has(.moon-dream-detail--complete) {
      /* Full-bleed portrait art may approach the hardware edge, but the spell
         pager is interactive. Reserve the real right inset only for this
         completed short-box workspace so Next never sits under the island. */
      padding-right: max(10px, var(--mf-safe-right));
    }

    .foundry-surface-root .moon-dream-right.moon-dream-detail--complete {
      grid-template-columns: minmax(200px, 40%) 92px minmax(0, 1fr);
      /* 118px -> 112px. Row 1 sat AT this floor on the shortest page box this
         band serves (844x280), so every pixel of it came straight out of the
         biography below: the summary's track resolved to 32.9px for 39px of
         prose and the last line was unreachable — the panel is `overflow:
         hidden` one tier up and this tier gives it no scroller. 112px still
         clears the two stacked 44px controls in column 2 that the note above
         this block is about, and costs the portrait 5% of its height. */
      grid-template-rows: minmax(112px, 1fr) minmax(0, 1fr);
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-showcase {
      grid-column: 1;
      grid-row: 1;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore {
      /* The controls live wholly in row 1, so the biography can keep its
         original starting point and use the empty rail width in row 2. */
      grid-column: 1 / 3;
      grid-row: 2;
      grid-template-rows: auto minmax(0, 1fr);
      /* Delete is a descendant projected into the adjacent rail, leaving the
         complete summary to paint in its original place. */
      overflow: visible;
      /* The remaining 2px the biography needs to paint its last line. */
      padding: 3px 6px;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore
      > .moon-dream-detail-title {
      grid-row: 1;
      min-height: 0;
      line-height: 1.15;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore
      > .moon-dream-detail-summary {
      grid-row: 2;
      overflow-y: visible;
      font-size: clamp(9px, 1.35vw, 11px);
      line-height: 1.15;
    }

    .foundry-surface-root .moon-dream-detail--complete > .moon-dream-detail-command {
      position: static;
      grid-column: 2;
      grid-row: 1;
      align-self: start;
      justify-self: stretch;
      width: 100%;
      max-width: none;
      margin: 0;
    }

    .foundry-surface-root .moon-dream-detail--complete
      > .moon-dream-detail-command .moon-dream-history-back {
      width: 100%;
      min-width: 0;
      border: 1px solid rgba(112, 104, 165, 0.42);
      border-radius: 8px;
      padding: 4px 6px;
      text-align: center;
      white-space: normal;
      font-size: 11px;
      line-height: 1.1;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore
      > .moon-dream-secondary-actions {
      position: absolute;
      right: 0;
      bottom: calc(100% + 6px);
      /* Earlier phone tiers place this as a grid item. An absolutely
         positioned grid child keeps that old grid area as its containing
         block unless it is explicitly cleared. */
      grid-area: auto;
      align-self: auto;
      justify-self: auto;
      width: 92px;
      max-width: 92px;
      margin: 0;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore
      > .moon-dream-secondary-actions .moon-dream-delete {
      /* The general 320px tier uses a bottom safe-area margin while Delete
         lives against the screen edge. In this rail it is nowhere near that
         edge; retaining the margin would push its ink back into the lore row. */
      margin-bottom: 0;
    }

    .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells {
      grid-column: 3;
      grid-row: 1 / 3;
    }

    /* The 667px page box also uses the free command rail. Its prior layout
       spent almost the entire lore row on the two buttons, leaving the
       biography a nominal scroll strip. The rail restores that row to the
       description while retaining both 44px controls. */
    @media (max-width: 700px) {
      .foundry-surface-root .moon-dream-detail--complete .moon-dream-showcase {
        min-height: 118px;
      }

      .foundry-surface-root .moon-dream-detail--complete .moon-dream-complete-lore
        > .moon-dream-secondary-actions {
        width: 76px;
        max-width: 76px;
      }
    }
  }

  /* Cards sized to the row they are actually given, and clipped to it, so the
     heading above and the pager below stay theirs. */
  /* Measured: the spells panel hands its grid ~56px, and cards at scale 1.05
     are 86px — so they were sliced top and bottom. Size them from the row they
     actually get (cqh against the grid itself) rather than a scale tuned for a
     taller screen. 22.2 card-units tall, so one unit is 100/22.2 = 4.5cqh, with
     a little held back for the row gap. Small and WHOLE beats big and sliced —
     a tap enlarges them anyway. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells-grid {
    container-type: size;
    /* The width term is what stops four cards overlapping when the summoner's
       column takes its share; the height term is what stops them being
       scissored by the row. Whichever binds, they fit whole. */
    --card-unit: min(4.2cqh, calc((100cqw - 8px) / 4 / 12.29));
    --card-width: calc(12.29 * var(--card-unit));
    --card-height: calc(22.2 * var(--card-unit));
    --catalog-card-scale: 1;
    min-height: 0;
    overflow: hidden;
    align-content: center;
    gap: 3px;
  }

  /* The panel is ~131px tall and was handing its grid only 56 — the heading
     and the pager took more than the cards. "Moon Dream cards should have
     about similar size" to the collection's, and since the card unit is cqh
     against this grid, the cards grow exactly as much as the grid does. So the
     chrome yields: the heading keeps its text but not its padding, and the
     pager keeps its 44px tap floor but not its margins. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells {
    padding: 2px 4px;
    gap: 0;
    grid-template-rows: auto minmax(0, 1fr) auto;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells > .moon-dream-detail-title {
    font-size: 11px;
    line-height: 1.1;
    margin: 0;
    padding: 0;
  }

  /* ---- the pager stops charging the cards rent ---------------------------
     "Collection looks good, Moon Dream cards should have about similar size."
     They were 35px wide against the collection's 77. The card unit is `cqh`
     against the grid, so the cards are exactly as big as the grid is tall —
     and a 44px pager stacked BELOW the grid was taking a third of the panel's
     height straight out of the cards.
     The pager has width to spare and no height to spare, so it moves to the
     SIDES. `display: contents` dissolves its wrapper into this grid, which lets
     the two buttons flank the cards and the page counter sit up beside the
     heading — without touching the markup, so the desktop layout is unchanged.
     Height reclaimed goes directly into the card size. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: auto minmax(0, 1fr);
    align-items: center;
    column-gap: 2px;
  }

  /* Every child of the flanking grid is seated EXPLICITLY. The first version
     placed classes the renderer never emits, so the
     title, the multi-line subtitle and the page counter all auto-flowed into
     row 1, the subtitle wrapped tall, and the minmax(0,1fr) card row collapsed
     to ~0 — ten zero-size spell cards behind a wrapper that ate every tap. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells > .moon-dream-detail-title {
    grid-column: 1 / 3;
    grid-row: 1;
    align-self: center;
    justify-self: start;
  }

  /* The informational subtitle ("These spells are now in your Collection.")
     wraps to several 11px lines in this width and crushes the card row. The
     heading already says what the panel is; the raster-failure notice is NOT
     informational and stays loud below the cards. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells > .moon-dream-detail-actions:not(.moon-dream-spells-raster-failure) {
    display: none;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells > .moon-dream-spells-raster-failure {
    grid-column: 1 / -1;
    grid-row: 3;
    align-self: end;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-pagination {
    display: contents;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-page-info {
    grid-column: 3;
    grid-row: 1;
    justify-self: end;
    margin: 0;
    font-size: 10px;
    line-height: 1.1;
    white-space: nowrap;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-pagination[hidden] {
    /* `display: contents` overrides the hidden attribute's own `display: none`,
       which would leak a single-page deck's dead arrows onto the screen. */
    display: none;
  }

  .foundry-surface-root .moon-dream-detail--complete #moon-dream-spells-prev {
    grid-column: 1;
    grid-row: 2;
  }

  .foundry-surface-root .moon-dream-detail--complete .moon-dream-spells-grid {
    grid-column: 2;
    grid-row: 2;
    /* The full row now, so the cards are sized by the panel rather than by
       what the pager left over — and by BOTH axes, so four of them cannot
       overlap when the summoner's column takes its share. A card is 12.29
       units wide; a quarter of the row minus the gaps is the widest a unit can
       be. (This rule is the one that actually binds at this tier: it comes
       after the grid rule above and overrode its guard.) */
    --card-unit: min(4.5cqh, calc((100cqw - 8px) / 4 / 12.29));
    /* The wrapper centers its items so the pager buttons sit mid-row — but
       centering removes the default stretch, and a `container-type: size` box
       contributes NO content height, so the grid measured 0px tall and every
       cqh-sized card collapsed to its 2px of border. The grid must take the
       whole track; only the buttons center. */
    align-self: stretch;
  }

  .foundry-surface-root .moon-dream-detail--complete #moon-dream-spells-next {
    grid-column: 3;
    grid-row: 2;
  }

  .foundry-surface-root .moon-dream-detail--complete #moon-dream-spells-prev,
  .foundry-surface-root .moon-dream-detail--complete #moon-dream-spells-next {
    align-self: center;
    /* Both axes stay physical touch targets. The completed grid's width-aware
       card unit yields before either pager control can become a narrow sliver. */
    min-height: var(--mf-touch, 44px);
    min-width: var(--mf-touch, 44px);
    padding: 0 6px;
    font-size: 11px;
    white-space: nowrap;
  }

  /* The counter keeps its meaning next to the heading instead of holding a
     44px row open beneath the cards. */
  .foundry-surface-root .moon-dream-detail--complete .moon-dream-page-info {
    grid-column: 1 / -1;
    grid-row: 1;
    justify-self: end;
    align-self: center;
    font-size: 10px;
    line-height: 1.1;
  }
}


/* ===========================================================================
   MOON DREAM — THE SUMMONER REVEAL

   Opening a finished Moon Dream is the payoff of a paid, minutes-long
   generation, and it used to just APPEAR: the portrait, the lore and the spell
   grid were simply present on the frame the panel repainted. This gives that
   moment an entrance.

   FOUR RULES SHAPE ALL OF IT.

   1. It plays ONCE, on navigation. `moon-dream.js` arms the reveal when the
      player crosses from the library into a run and consumes it on the next
      paint. The panel repaints on every 2s poll tick and on every spell page
      turn; without the arming, those repaints would replay the whole entrance
      forever and paging would feel slower than it is. Nothing here is reachable
      from a resting selector — every animation sits under `[data-reveal="play"]`,
      `[data-mobile-nav]`, or `.moon-dream-spells-dealing`, all three of which
      are one-shot flags that JS removes when their window closes.

   2. It NEVER waits for media, and it never RE-hides media that has already
      been released. The frame materializes and blooms whether or not the
      portrait has decoded; the art keeps arriving underneath through its own
      placeholder path (`img.moon-dream-media-loading`). The spell deck is a
      harder case, because it is legitimately withheld behind the pre-existing
      raster cohort gate: the answer is that the deal animates TRANSFORM ONLY
      and its schedule is anchored to the start of the entrance, so it burns
      down while the cohort is still staging. The instant the cohort releases,
      every card is opaque — the group appears together, exactly as
      docs/asset-loading.md law 2 requires — and what is left of the deal is
      pure settle motion on visible cards. An earlier version faded the cards in
      after the release and held the last of them at opacity 0 for ~400ms past
      the moment the deck was ready. That is the forbidden thing wearing a
      flourish, and it is why no opacity appears in `md-deal-in`.

   3. It moves NOTHING. Every animated property is transform or opacity, so the
      frozen phone grid (see moonDreamMobileFit.test.js) cannot be disturbed by
      it, and the card size variables are never touched, so a spell card still
      renders identically here and in the collection.

   4. It is calibrated to the size it renders at. This surface is phone-only,
      and the portrait frame runs from ~350px wide on a portrait phone down to
      78x44 on an 844x288 landscape page box. A fixed 1.4% overshoot is 5px on
      the first and ONE pixel on the second — the motion silently disappears
      exactly where the screen is smallest. Every amplitude is therefore
      `base * var(--md-amp)`, raised in the same media queries that shrink the
      frame.

   The layered read, loudest first: the portrait is the subject and gets the
   materialize, the rim bloom, a spark burst and the reveal chime; the deck is
   dealt in with a per-card stagger; the lore settles quietly; the panel's
   furniture wakes from dim; and the destructive Delete control arrives last
   and lowest-energy, because juice competes for attention and a delete button
   should never win.
   =========================================================================== */

/* ---- amplitude tiers ---------------------------------------------------- */
#moon-dream-detail {
  --md-amp: 1;
}

@media (orientation: landscape) and (max-height: 400px) {
  .foundry-surface-root #moon-dream-detail { --md-amp: 2.2; }
}

@media (orientation: landscape) and (max-height: 320px) {
  .foundry-surface-root #moon-dream-detail { --md-amp: 3; }
}

/* ---- the arrival: three panels, in order, not one big pane -------------- */
/* This used to translate `.moon-dream-right` — the entire detail panel — and
   animate five pieces of copy inside it at the same time. Two things were wrong
   with that, one visual and one measured.
   Visually it was a dozen things moving at once, which is the opposite of the
   restraint this is supposed to have: the eye had nowhere to go.
   Measured on real-page-box-firefox against a control that animates the same
   elements for the same milliseconds with plain opacity keyframes, the panel
   move is also the worst possible shape — an animating ANCESTOR keeps every
   descendant's animation inside its layer, so each frame of the portrait's
   materialize re-rastered the whole 844x203 panel at a 3x backing store.
   So the three cards carry the arrival themselves, staggered, and they are
   FINISHED before the portrait inside one of them starts. At no instant is an
   animating element the ancestor of another. That is both the cheaper shape and
   the clearer read: the panels arrive, then the subject materializes, then the
   deck deals. */
/* THE PANELS FADE; ONLY THE SUMMONER MOVES.
   Restraint, first: juice competes for attention and the loudest treatment
   belongs to the subject, so the three cards arrive on a stagger with opacity
   alone and the portrait frame inside the first of them is the only thing on
   screen with travel, scale and tilt.
   Cost, second, and it points the same way: these cards are the largest boxes
   on the panel, and a transform on a ~500x140 card is a full-area resample
   every frame on a compositor with no GPU, where a blend is not. Measured on
   Chrome, moving the showcase as well was worth about five extra frames over
   32ms per navigation. */
#moon-dream-detail[data-reveal="play"] > .moon-dream-showcase {
  animation: md-reveal-fade 240ms cubic-bezier(0.22, 0.86, 0.28, 1) both;
}

#moon-dream-detail[data-reveal="play"] > .moon-dream-spells {
  animation: md-reveal-fade 240ms cubic-bezier(0.22, 0.86, 0.28, 1) 70ms both;
}

#moon-dream-detail[data-reveal="play"] > .moon-dream-complete-lore {
  animation: md-reveal-fade 240ms cubic-bezier(0.22, 0.86, 0.28, 1) 140ms both;
}

/* Going back has no content reveal of its own, so the rail is free to be the
   whole move. It is alone in its window. */
.moon-dream-content[data-mobile-nav="back"] .moon-dream-history-rail {
  animation: md-push-in-back 200ms cubic-bezier(0.22, 0.86, 0.28, 1) both;
}

@keyframes md-push-in-back {
  from { transform: translate3d(-5%, 0, 0); }
  to { transform: translate3d(0, 0, 0); }
}

/* ---- the portrait: the subject of the reveal ---------------------------- */
/* Anticipation (undersized, low, tilted, and offset toward the side the
   detail pane came from) into an overshoot and a settle, so the frame arrives
   with mass instead of switching on. This is where the master/detail direction
   now reads, and it starts once the showcase card that holds it has come to
   rest — never while an ancestor of it is still animating.
   TRANSFORM AND OPACITY ONLY. An earlier version also ramped
   `filter: brightness()` here and bloomed a blurred `box-shadow` below.
   Measured A/B, those two HALVED the frame rate through the reveal window —
   Chrome's median frame went 16.7ms to 33.3ms. A paint property is not free
   just because it does not reflow. */
#moon-dream-detail[data-reveal="play"] .moon-dream-portrait-frame {
  animation: md-portrait-materialize 380ms cubic-bezier(0.2, 0.72, 0.24, 1) 250ms both;
}

@keyframes md-portrait-materialize {
  0% {
    transform:
      scale(calc(1 - 0.035 * var(--md-amp, 1)))
      translate3d(7%, 11%, 0)
      rotate(calc(-0.72deg * var(--md-amp, 1)));
    opacity: 0;
  }

  54% {
    transform:
      scale(calc(1 + 0.013 * var(--md-amp, 1)))
      translate3d(-1%, -3%, 0)
      rotate(calc(0.24deg * var(--md-amp, 1)));
    opacity: 1;
  }

  78% {
    transform:
      scale(calc(1 - 0.003 * var(--md-amp, 1)))
      translate3d(0.3%, 1%, 0)
      rotate(calc(-0.06deg * var(--md-amp, 1)));
    opacity: 1;
  }

  100% {
    transform: scale(1) translate3d(0, 0, 0) rotate(0deg);
    opacity: 1;
  }
}

/* A gold rim collapses onto the bevel and cools away — the lighting says "this
   just resolved" without a separate glow element and without a light sweep
   that, on a 78px-wide frame, nobody could see anyway.
   It is a SEPARATE ring rather than a colour ramp on the resting bevel because
   animating `border-color` REPAINTS that bevel every frame, and the bevel sits
   inside the frame's rounded `overflow: hidden`, so each repaint invalidated a
   clipped layer. A ring whose colour never changes and whose opacity and
   transform animate is composited. Its geometry tracks the bevel's exactly. */
#moon-dream-detail[data-reveal="play"] .moon-dream-portrait-frame::before {
  content: '';
  position: absolute;
  inset: clamp(3px, 6%, 10px);
  border-radius: clamp(5px, 12%, 22px);
  border: 1px solid rgba(246, 226, 122, 0.92);
  pointer-events: none;
  z-index: 2;
  animation: md-frame-bloom 340ms cubic-bezier(0.2, 0.7, 0.25, 1) 290ms both;
}

@keyframes md-frame-bloom {
  0% { opacity: 0; transform: scale(calc(1 + 0.02 * var(--md-amp, 1))); }
  24% { opacity: 1; }
  100% { opacity: 0; transform: scale(1); }
}

/* ---- the spell deck: dealt, never switched on --------------------------- */
/* Each card offsets from its neighbour, so the row reads as physical cards
   arriving rather than a grid becoming visible. The slot is what animates —
   the card inside keeps its own transform chain (`--card-base-transform`,
   CardTilt), so card identity is untouched.

   NO OPACITY. See rule 2 at the top: the cohort release is what makes this
   group visible, and it makes all of it visible at once. `--md-deal-offset` is
   the (usually negative) number of milliseconds the schedule had already
   consumed while the cohort was staging, so the wave straddles the release
   instead of starting after it. A deck that took its time simply appears
   settled. */
/* A short beat with a wide gap between cards, rather than a long one with a
   narrow gap: the wave reads more clearly as individual cards landing, and only
   two or three of them are resampling at any instant instead of all four. */
.moon-dream-spells-dealing .moon-dream-spell-card {
  animation: md-deal-in 240ms cubic-bezier(0.18, 0.78, 0.28, 1) both;
  animation-delay: calc(var(--md-deal-offset, 0ms) + var(--md-deal-index, 0) * 90ms);
}

/* SCALE ONLY — no rotation, for two independent reasons.
   Visually: `.moon-dream-spells-grid` clips to its row (`overflow: hidden`), so
   a rotated card has its corners scissored off for the length of the deal. A
   card that arrives with its corners cut is worse than a card that arrives
   without a tilt.
   Cost: profiled on real-page-box-firefox, a rotation is the expensive half of
   a transform under software compositing — the layer is resampled with
   anti-aliasing every frame, where a scale about the centre is one filtered
   resample and a translate is a blit. Four cards' worth of that is real. */
@keyframes md-deal-in {
  0% { transform: scale(calc(1 - 0.05 * var(--md-amp, 1))); }
  58% { transform: scale(calc(1 + 0.014 * var(--md-amp, 1))); }
  100% { transform: scale(1); }
}

/* ---- the quiet arrivals: opacity only ----------------------------------- */
/* Everything below rides in on its card and needs no travel of its own, so it
   costs nothing but a blend. Last, and quietest of all, the destructive Delete
   control: a delete button may not be the flashiest thing in a celebration.
   It fades in place and stays tappable the whole time — the entrance never
   gates input. */
#moon-dream-detail[data-reveal="play"] .moon-dream-complete-lore > .moon-dream-secondary-actions {
  animation: md-reveal-fade 200ms linear 420ms both;
}

@keyframes md-reveal-fade {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* ---- the furniture: dim, not orphaned ----------------------------------- */
/* The back chip and the deck's Prev / PAGE n / Next belong to content that has
   not arrived yet. Left at full strength they were the brightest things in an
   otherwise empty panel for the first third of a second — the eye went to a
   pager instead of to the summoner. They wake up with the content instead.
   Opacity only, and never below a legible 0.3: this control must stay visible
   and tappable throughout, so the entrance can never gate input. */
#moon-dream-detail[data-reveal="play"] > .moon-dream-detail-command,
#moon-dream-detail[data-reveal="play"] .moon-dream-spells > .moon-dream-pagination {
  animation: md-furniture-wake 360ms ease-out both;
}

@keyframes md-furniture-wake {
  from { opacity: 0.3; }
  to { opacity: 1; }
}

/* ---- reduced motion ------------------------------------------------------
   The event still has to READ — a reduced-motion user must still see that new
   content arrived — so every surface keeps a short opacity fade in the same
   order. What goes is travel, scale, rotation, overshoot and the rim bloom.
   The spark burst opts itself out inside UI.FX; the reveal chime stays, because
   a sound is not motion and it is the only cue left that something landed. */
@media (prefers-reduced-motion: reduce) {
  .moon-dream-content[data-mobile-nav="back"] .moon-dream-history-rail {
    animation: md-reveal-fade 120ms linear both;
  }

  #moon-dream-detail[data-reveal="play"] > .moon-dream-spells,
  #moon-dream-detail[data-reveal="play"] > .moon-dream-complete-lore,
  #moon-dream-detail[data-reveal="play"] .moon-dream-portrait-frame,
  #moon-dream-detail[data-reveal="play"] .moon-dream-complete-lore > .moon-dream-secondary-actions {
    animation: md-reveal-fade 140ms linear both;
  }

  #moon-dream-detail[data-reveal="play"] > .moon-dream-detail-command,
  #moon-dream-detail[data-reveal="play"] .moon-dream-spells > .moon-dream-pagination {
    animation: md-furniture-wake 140ms linear both;
  }

  #moon-dream-detail[data-reveal="play"] .moon-dream-portrait-frame::before {
    display: none;
  }

  .moon-dream-spells-dealing .moon-dream-spell-card {
    animation: md-reveal-fade 140ms linear both;
    animation-delay: 0ms;
  }
}

/* ---------- Stardust spend-source menu ----------
   Anchored to the Stardust pill and mounted on <body>, so the Foundry tab bar
   keeps its exact geometry. It exists only while open, and only for a player
   who actually holds reward Stardust. Sized in svh/px rather than vh: on a
   phone a vh-sized panel hangs below the visible page box. */
.stardust-spend-menu {
  position: fixed;
  z-index: 10000;
  width: min(280px, calc(100vw - 16px));
  max-height: min(70svh, 420px);
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px;
  border-radius: 14px;
  border: 1px solid rgba(246, 226, 122, 0.38);
  background: #1b1b2c;
  box-shadow: 0 18px 44px rgba(0, 0, 0, 0.55);
  color: #efe7f6;
  font-size: 0.85rem;
  line-height: 1.4;
}

.stardust-spend-heading {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: #f6e27a;
}

.stardust-spend-rows {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.stardust-spend-row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  padding: 4px 8px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.04);
}

.stardust-spend-row-value {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.stardust-spend-row-bound .stardust-spend-row-value { color: #c8a2f0; }
.stardust-spend-row-yours .stardust-spend-row-value { color: #f6e27a; }

.stardust-spend-toggle {
  display: flex;
  align-items: center;
  gap: 10px;
  /* A comfortable touch target without growing the panel on desktop. */
  min-height: 40px;
  cursor: pointer;
}

.stardust-spend-toggle-input {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  accent-color: #c8a2f0;
  cursor: pointer;
}

.stardust-spend-note,
.stardust-spend-error {
  margin: 0;
  font-size: 0.76rem;
  color: rgba(239, 231, 246, 0.72);
}

.stardust-spend-error { color: #ff9d9d; }

.stardust-spend-store {
  -webkit-appearance: none;
  appearance: none;
  min-height: 38px;
  border-radius: 999px;
  border: 1px solid rgba(246, 226, 122, 0.45);
  background: transparent;
  color: #f6e27a;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.stardust-spend-store:hover { background: rgba(246, 226, 122, 0.12); }
.stardust-spend-store:focus-visible { outline: 2px solid #f6e27a; outline-offset: 2px; }

@media (prefers-reduced-motion: no-preference) {
  .stardust-spend-menu { animation: stardust-spend-in 140ms ease-out; }
}

@keyframes stardust-spend-in {
  from { opacity: 0; transform: translateY(-4px); }
  to { opacity: 1; transform: translateY(0); }
}
