/* ==========================================================================
   Stardewpedia - "Valley GUI" theme
   Orange 9-slice panels on a dithered sky, over a cloud strip and a green
   footer. Every colour is sampled from the art in public/branding/, so
   recolour the site by editing :root, not rules.

   The sky itself lives in public/sky.css, generated by
   scripts/gen-bg-gradient.py. It is NOT an image: it is an 8x8 Bayer ordered
   dither built from stacked masked layers, so it is sharp at any zoom or DPR.
   ========================================================================== */

:root {
  /* Chrome scale. The border art is 16px, so --s: 32px draws it at 2x, and the
     menu, panels and ground all derive from this one value. */
  --s: 32px;
  --half: calc(var(--s) / 2);     /* the menu caps are 8px art, half a tile */

  /* The sky is deliberately NOT on --s. The dither was tuned to a 24px tile
     while the chrome wants 32px, and coupling them would force one to follow
     the other. Nothing outside sky.css reads --sky-s. */
  --sky-s: 24px;
  --sky-h: calc(var(--sky-s) * 46);        /* how far the ramp runs before it holds */
  --sky-cell: calc(var(--sky-s) * 2 / 16); /* one dither cell: 2 art pixels, so 3px */
  --sky-end: #44b6f4;                      /* the colour the ramp lands on */

  /* Panels, sampled from ui-middle.png and ui-middle-left.png */
  --panel: #f1a545;
  --panel-2: #e2913c;
  --panel-3: #d17c39;
  --slot: #b45834;
  --outline: #752c41;   /* every border, matching the frame art */
  --hilight: #fbf06b;   /* the frame's top highlight */

  /* Ink */
  --ink: #43280f;
  --ink-soft: #7c4a1c;
  --ink-invert: #fff6e0;

  /* Text sitting directly on the sky rather than inside a panel. The old theme
     used the parchment tones for this, which are unreadable on blue. */
  --on-sky: #ffffff;
  --on-sky-soft: #c3e0fb;

  /* Accents. The green continues bottom.png's last row into the footer. */
  --accent: #2b9b34;
  --accent-dark: #1f7526;
  --green: #2b9b34;
  --gold: #fbf06b;
  --danger: #b04a2f;

  --spring: #6fbf4a;
  --summer: #e8b62c;
  --fall: #cf6a2a;
  --winter: #63b3d8;

  --px: 4px;
  /* Stepped corner cut shared by every raised surface */
  --cut: polygon(
    0 var(--px), var(--px) var(--px), var(--px) 0,
    calc(100% - var(--px)) 0, calc(100% - var(--px)) var(--px), 100% var(--px),
    100% calc(100% - var(--px)), calc(100% - var(--px)) calc(100% - var(--px)),
    calc(100% - var(--px)) 100%, var(--px) 100%, var(--px) calc(100% - var(--px)),
    0 calc(100% - var(--px))
  );

  --col: 1180px;

  /* Full height of masthead + menu + search, measured. The planner is pinned
     below it, and body:has(.planner) overrides this to the compact value.
       35 padding + 114 logo (109x38 art at 3x) + 16 + 32 menu + 15
     Every part is a fixed size, so this cannot drift with content. The search sits
     inside the menu bar, so it costs no height. Verified by measurement. */
  --head-h: 212px;
}

*, *::before, *::after { box-sizing: border-box; }

/* Green backstop. On a page shorter than the viewport the footer does not reach
   the bottom, and without this a strip of sky shows below the green footer. */
html {
  -webkit-text-size-adjust: 100%;
  background: var(--green);
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  color: var(--ink);
  font: 16px/1.55 "Segoe UI", Roboto, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;

  /* .sky is absolutely positioned at z-index -1. That only paints above body's
     own background if body establishes a stacking context AND is a containing
     block; without the isolation it lands in the root stacking context and the
     background covers it. Same trap as .pl-stage in the planner. */
  position: relative;
  isolation: isolate;
  background-color: var(--sky-end);
}

/* Every sprite is real game pixel art. Never smooth it. */
img, canvas { image-rendering: pixelated; image-rendering: crisp-edges; }

a { color: var(--accent-dark); text-decoration: none; }
a:hover { text-decoration: underline; }

h1, h2, h3 { line-height: 1.2; margin: 0 0 .5em; letter-spacing: .01em; }
h1 { font-size: clamp(1.6rem, 4vw, 2.3rem); }
h2 { font-size: 1.3rem; }
h3 { font-size: 1.05rem; }

code {
  background: var(--panel-3);
  border: 2px solid var(--outline);
  padding: 1px 6px;
  font: 13px/1.5 "Courier New", monospace;
}

/* ------------------------------------------------------------------ shell */

main {
  flex: 1;
  width: 100%;
  max-width: var(--col);
  margin: 0 auto;
  padding: 0 20px 40px;
}

/* ------------------------------------------------------------------ masthead */

.masthead { text-align: center; padding: calc(var(--s) * 1.1) 20px 0; }
.brand { display: block; }
.brand:hover { text-decoration: none; }
.logo {
  display: block;
  margin: 0 auto;
  width: 327px;          /* 109px art at 3x */
  max-width: 70vw;
  height: auto;
}

/* ------------------------------------------------------------------ menu
   menu-left / menu-right are 8x16 caps, menu-middle is a 16x16 tile that
   repeats for however long the bar needs. Nothing is stretched. */

.menubar {
  display: flex;
  justify-content: center;
  margin: calc(var(--s) * .5) 0 calc(var(--s) * .45);
  padding: 0 20px;
}

.menu {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0 8px;
  min-height: var(--s);
  padding: 0 var(--half);
  background-image:
    url('/branding/menu-left.png'),
    url('/branding/menu-right.png'),
    url('/branding/menu-middle.png');
  background-repeat: no-repeat, no-repeat, repeat-x;
  background-position: left top, right top, left top;
  background-size:
    var(--half) var(--s),
    var(--half) var(--s),
    var(--s) var(--s);
  image-rendering: pixelated;
}

.menu a {
  display: block;
  padding: 0 12px;
  line-height: var(--s);
  font-size: .82rem;
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink);
}
.menu a:hover { color: #fff; text-decoration: none; text-shadow: 0 1px 0 rgba(0,0,0,.45); }

.menu-nav { display: flex; align-items: center; flex-wrap: wrap; }

/* ---------------------------------------------------------------- submenus
   CSS only, over real anchors. Every link is in the DOM on every page, so crawlers
   see all of them, they work with scripting off, and :focus-within makes them
   keyboard reachable for free. A JS menu that injected items on click would hide
   them from crawlers. */

.menu-group { position: relative; }
.menu-group > a::after {
  content: '';
  display: inline-block;
  margin-left: 5px;
  border: 4px solid transparent;
  border-top-color: currentColor;
  border-bottom: 0;
  vertical-align: middle;
}

.menu-drop {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 45;
  display: none;
  min-width: 190px;
  padding: 7px 0;
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: 0 5px 0 rgba(0,0,0,.35);
}
.menu-group:hover > .menu-drop,
.menu-group:focus-within > .menu-drop { display: block; }

.menu-drop a {
  display: block;
  line-height: 1.35;
  padding: 7px 14px;
  font-size: .78rem;
  letter-spacing: .02em;
  white-space: nowrap;
}
.menu-drop a:hover, .menu-drop a:focus-visible {
  background: var(--accent);
  color: #fff;
  text-shadow: none;
}

/* The last groups would open past the right edge of the bar. */
.menu-nav > .menu-group:nth-last-of-type(-n+2) .menu-drop { left: auto; right: 0; }

/* No hover on touch, so the panel would only ever open on a tap that also navigates.
   Showing every group expanded is honest there and costs one scroll. */
@media (hover: none) {
  .menu-group > a::after { display: none; }
  .menu-drop {
    position: static;
    display: block;
    min-width: 0;
    padding: 0;
    border: 0;
    box-shadow: none;
    background: none;
  }
  .menu-drop a { display: none; }
}

/* Search rides on the right of the same bar rather than taking a row of its own.
   Sized to sit inside the 32px bar with room to spare, so it reads as part of the
   pixel chrome instead of a form bolted under it. */
.menu #search {
  min-width: 0;
  width: 152px;
  height: 22px;          /* inside the 32px bar, so its top highlight stays clear */
  padding: 0 8px;
  line-height: 1;        /* the inherited 1.55 would push this past the bar */
  font-size: .74rem;
  border-width: 2px;
  background: var(--panel-3);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.2);
}
.menu #search::placeholder { color: var(--ink-soft); opacity: 1; }
.menu #search:focus { outline-width: 2px; outline-offset: 0; }

/* The panel hangs off .topsearch, which is only as wide as the input. Anchor it to
   the right or it runs off the viewport on this side of the bar. */
.menu .suggest { left: auto; right: 0; }

.topsearch { display: flex; }
#search, #hero-search, #filter-search, #filter-sort, #tbl-search {
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.22);
  color: var(--ink);
  font: inherit;
  font-size: .95rem;
  padding: 8px 12px;
  min-width: 200px;
}
#search:focus, #hero-search:focus, #filter-search:focus, #filter-sort:focus, #tbl-search:focus {
  outline: 3px solid var(--gold); outline-offset: 1px;
}

/* Search suggestions. The wrapper is positioned so the panel can hang below. */
.topsearch, .hero-search { position: relative; }
.suggest {
  position: absolute;
  top: 100%; left: 0;
  z-index: 40;
  min-width: 260px; max-width: 380px;
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: 0 6px 0 rgba(0,0,0,.3);
  margin-top: 4px;
  text-align: left;
}
.hero-search .suggest { left: 50%; transform: translateX(-50%); }
.suggest-item {
  display: flex; align-items: baseline; gap: 10px;
  padding: 7px 12px;
  color: var(--ink);
  border-bottom: 2px solid var(--panel-2);
}
.suggest-item:last-child { border-bottom: 0; }
.suggest-item:hover, .suggest-item.is-active { background: var(--accent); color: #fff; text-decoration: none; }
.suggest-name { font-weight: 600; font-size: .9rem; }
.suggest-group { margin-left: auto; font-size: .74rem; opacity: .7; }

/* ------------------------------------------------------------------ pieces */

.btn {
  display: inline-block;
  background: var(--accent);
  color: #fff;
  border: 3px solid var(--outline);
  box-shadow: inset 0 -4px 0 rgba(0,0,0,.28), inset 0 3px 0 rgba(255,255,255,.24);
  clip-path: var(--cut);
  padding: 9px 18px;
  font: inherit; font-weight: 700;
  cursor: pointer;
}
.btn:hover { background: var(--accent-dark); text-decoration: none; }
.btn:active { box-shadow: inset 0 4px 0 rgba(0,0,0,.28); }

.btn-ghost {
  background: var(--panel-2);
  color: var(--ink);
}
.btn-ghost:hover { background: var(--panel-3); }

/* ---------------------------------------------------------------- 9-slice frame
   The big containers wear the real border art. Every tile is drawn at its native
   16px scaled to --s and then REPEATED, never stretched: repeat-y down the sides,
   repeat-x along the top and bottom.

   The Branding set shipped no top row, so ui-top* are derived by
   scripts/gen-ui-top.py, which asserts the corners and the middle tile agree
   column for column. Do not hand-edit those three files.

   Only these four selectors get the frame. Cards, pills and slots keep the flat
   bevel: framing every card would bury the content and cost three extra
   background layers each. */

.panel, .hero, .filters, .pl-side {
  position: relative;
  /* The frame sits BEHIND the content, at z-index -1. It has to: ui-top.png and
     ui-bottom.png are 16px tiles whose edge is only their first few rows and whose
     remainder is plain fill, so at --s each paints an OPAQUE band a whole tile deep.
     Painted in front (the default for ::after) that band covers the top and bottom
     of the content unless the padding is a full tile on both edges.
     z-index -1 only lands above the element's own background, rather than dropping
     into the ancestor stacking context and hiding behind it, because of the
     isolation here. */
  isolation: isolate;
  background-color: var(--panel);
  background-image:
    url('/branding/ui-middle-left.png'),
    url('/branding/ui-middle-right.png');
  background-repeat: repeat-y, repeat-y;
  background-position: left top, right top;
  background-size: var(--s) var(--s), var(--s) var(--s);
  border: 0;
  box-shadow: none;
  clip-path: none;          /* the corners are art now, never clip them */
  image-rendering: pixelated;
}

/* Corners are listed before the repeating edge so they win where they overlap. */
.panel::before, .hero::before, .filters::before, .pl-side::before,
.panel::after, .hero::after, .filters::after, .pl-side::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: var(--s);
  background-repeat: no-repeat, no-repeat, repeat-x;
  background-size: var(--s) var(--s);
  z-index: -1;
  pointer-events: none;
}

.panel::before, .hero::before, .filters::before, .pl-side::before {
  top: 0;
  background-image:
    url('/branding/ui-top-left.png'),
    url('/branding/ui-top-right.png'),
    url('/branding/ui-top.png');
  background-position: left top, right top, left top;
}

.panel::after, .hero::after, .filters::after, .pl-side::after {
  bottom: 0;
  background-image:
    url('/branding/ui-bottom-left.png'),
    url('/branding/ui-bottom-right.png'),
    url('/branding/ui-bottom.png');
  background-position: left bottom, right bottom, left bottom;
}

/* Content must clear the frame, which is one tile deep on every edge. */
.panel > *, .hero > *, .filters > *, .pl-side > * { position: relative; }

.panel {
  padding: 20px 22px 22px;
  margin: 0 0 22px;
}
.panel > h2 { margin-top: 0; }

.pill {
  display: inline-block;
  background: var(--panel-3);
  border: 2px solid var(--outline);
  padding: 2px 9px;
  font-size: .78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.pill-cooking { background: #f0b76a; }
.pill-crafting { background: #9fc6e8; }

.note {
  color: var(--ink-soft);
  font-size: .85rem;
  margin: 10px 0 0;
}
.more { margin: 12px 0 0; font-weight: 600; }

.crumbs {
  color: var(--on-sky-soft);
  font-size: .88rem;
  margin: 0 0 16px;
}
.crumbs a { color: #fff; }
.crumbs span { opacity: .5; margin: 0 4px; }
.crumbs em { color: var(--on-sky); font-style: normal; }

/* ------------------------------------------------------------------ sprites */

/* Sprites fit their slot, never overflow it.
   The slots MUST be flex, not grid. With `display: grid` the single implicit row is
   an AUTO track, so `height: 100%` here is a percentage against an indefinite size
   and is dropped: the image then falls back to its aspect-ratio height and spills
   over the slot and the label under it. A 48x80 furniture sprite rendered 66.7px
   tall inside a 56px box. In a flex slot with a fixed height the percentage
   resolves and object-fit does the rest. */
.sprite, .fallback {
  display: block;
  width: 100%; height: 100%;
  object-fit: contain;
}
.fallback {
  display: flex; align-items: center; justify-content: center;
  background: var(--slot);
  color: var(--ink);
  font-size: .7rem; font-weight: 800;
}
.sprite-lg { width: 100%; height: 100%; object-fit: contain; }

/* ------------------------------------------------------------------ home */

.hero {
  /* The search suggestions hang out of the hero's bottom edge. .hero is in the
     isolation:isolate group above (the frame needs it), which traps .suggest's z-index
     INSIDE the hero, so the sections below painted straight over the dropdown. Raising
     the hero itself lifts the trapped panel with it. */
  z-index: 20;
  padding: 40px 26px 32px;
  text-align: center;
  margin-bottom: 26px;
}
.hero h1 { margin-bottom: .25em; font-size: clamp(2rem, 6vw, 3rem); }
.hero-sub { margin: 0 auto 22px; max-width: 62ch; color: var(--ink-soft); font-size: 1.05rem; }
.hero-search { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
.hero-search input { min-width: min(420px, 80vw); }
.hero-strip { display: flex; gap: 14px; justify-content: center; margin-top: 26px; flex-wrap: wrap; }
.hero-sprite {
  display: block; width: 52px; height: 52px;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 5px;
}

.hub-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
  gap: 16px;
}
.hub-card {
  display: block;
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 3px var(--panel-2), inset 0 -6px 0 rgba(0,0,0,.16);
  clip-path: var(--cut);
  padding: 22px 20px;
  color: var(--ink);
}
.hub-card:hover { background: var(--panel-2); text-decoration: none; transform: translateY(-2px); }
.hub-count { display: block; font-size: 2rem; font-weight: 800; color: var(--accent-dark); line-height: 1; }
.hub-name { display: block; font-size: 1.15rem; font-weight: 700; margin: 6px 0 8px; }
.hub-blurb { display: block; font-size: .88rem; color: var(--ink-soft); }

/* ------------------------------------------------------------------ headings */

.page-head { margin-bottom: 20px; text-align: center; }
/* Breadcrumb, h1 and standfirst are centred on every page. The planner has none of
   them: its heading lives inside the floating tool panel. */
.crumbs { text-align: center; }

/* The villager intro line. Centred to match the breadcrumb and heading above it, and held to
   the same 70ch measure as .page-sub so prose width is consistent across the site. */
.page-intro {
  color: var(--on-sky-soft);
  text-align: center;
  max-width: 70ch;
  margin: 0 auto 18px;
  line-height: 1.6;
}
.page-intro strong { color: var(--on-sky); }

/* The villager h1 sits on the sky between the breadcrumb and the intro, so .page-head owns
   its colour. The intro follows immediately, so the header's usual bottom margin is dropped
   to keep the two as one block. */
.page-head:has(+ .page-intro) { margin-bottom: 10px; }

/* The villager hero pill used to sit flush against the facts grid. With the h1 moved out of
   the hero there is nothing between them, so the gap has to come from the pill itself. */
.item-hero-info > .pill { margin-bottom: 14px; }

/* The Best gifts strip in the villager hero. It answers the highest-volume query on the page,
   so it sits with the facts rather than below the fold. The h2 is visually a label, not a
   section heading, which is why it is sized down rather than left at panel-heading weight. */
.hero-best { margin-top: 14px; }
.hero-best-title {
  font-size: 14px;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--ink-soft, var(--ink));
  margin: 0 0 8px;
  border: 0;
  padding: 0;
}
.hero-best .chips { margin: 0; }
.page-head h1 { color: var(--on-sky); text-shadow: 0 2px 0 rgba(0,0,0,.35); }
/* One rule, or the later `margin: 0` wipes the auto margins and the 70ch block sits
   flush left under a centred heading. That split is what made the intro paragraph look
   off-centre on every page. */
.page-sub { color: var(--on-sky-soft); margin: 0 auto; max-width: 70ch; }

/* ------------------------------------------------------------------ filters */

.filters {
  padding: 16px 18px;
  margin-bottom: 16px;
}
.filter-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 12px; }
.filter-row input { flex: 1 1 240px; }

.facet-group { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 8px; }
.facet-label {
  font-size: .78rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-soft);
  min-width: 74px;
}
.facet {
  background: var(--panel-2);
  border: 2px solid var(--outline);
  color: var(--ink);
  font: inherit; font-size: .82rem; font-weight: 600;
  padding: 4px 9px;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
}
.facet:hover { background: var(--panel-3); }
.facet[aria-pressed="true"] { background: var(--accent); color: #fff; border-color: var(--ink); }
.facet-count { opacity: .65; font-size: .74rem; font-weight: 700; }
.facet[aria-pressed="true"] .facet-count { opacity: .85; }

/* The live result count under the crops filters. */
.filter-count {
  margin: 10px 0 0;
  font-size: .82rem;
  font-weight: 700;
  color: var(--ink);
  opacity: .75;
}

.result-count { color: var(--on-sky-soft); font-size: .9rem; margin: 0 0 12px; font-weight: 600; }

/* ------------------------------------------------------------------ grids */

.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(132px, 1fr));
  gap: 12px;
}
.grid-sm { grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)); }

.tile {
  display: flex; flex-direction: column; align-items: center; gap: 7px;
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 2px var(--panel-2), inset 0 -4px 0 rgba(0,0,0,.14);
  clip-path: var(--cut);
  padding: 12px 8px 10px;
  color: var(--ink);
  text-align: center;
}
.tile:hover { background: var(--panel-2); text-decoration: none; }
.tile-icon {
  display: flex; align-items: center; justify-content: center;
  width: 56px; height: 56px;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 5px;
  flex: none;
}
.tile-name { font-size: .84rem; font-weight: 700; line-height: 1.25; }
.tile-meta { font-size: .72rem; color: var(--ink-soft); }

.is-hidden { display: none !important; }

.pager { display: flex; gap: 6px; justify-content: center; flex-wrap: wrap; margin-top: 22px; }
.pager button {
  background: var(--panel-2);
  border: 3px solid var(--outline);
  color: var(--ink);
  font: inherit; font-weight: 700;
  padding: 6px 13px;
  cursor: pointer;
}
.pager button:hover { background: var(--panel-3); }
.pager button[aria-current="true"] { background: var(--accent); color: #fff; }
.pager button:disabled { opacity: .4; cursor: default; }

/* ------------------------------------------------------------------ item page */

.item-hero {
  display: grid;
  grid-template-columns: 180px 1fr;
  gap: 24px;
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 3px var(--panel-2), inset 0 -6px 0 rgba(0,0,0,.14);
  clip-path: var(--cut);
  padding: 24px;
  margin-bottom: 22px;
}
.item-hero-art {
  display: flex; align-items: center; justify-content: center;
  aspect-ratio: 1;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 3px 3px 0 rgba(0,0,0,.25);
  padding: 18px;
}
.item-hero-info h1 { margin: 8px 0 .35em; }
.item-desc { margin: 0 0 14px; color: var(--ink-soft); max-width: 66ch; }

.seasons { display: flex; gap: 6px; flex-wrap: wrap; margin: 0 0 14px; }
.season {
  display: inline-block;
  border: 2px solid var(--outline);
  padding: 2px 9px;
  font-size: .76rem; font-weight: 800;
  text-transform: capitalize;
  background: var(--panel-3);
}
.season-spring { background: var(--spring); }
.season-summer { background: var(--summer); }
.season-fall { background: var(--fall); color: #fff; }
.season-winter { background: var(--winter); }

.facts {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin: 0 0 14px;
}
.facts > div {
  background: var(--panel-2);
  border: 2px solid var(--outline);
  padding: 8px 11px;
}
.facts dt { font-size: .72rem; text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); font-weight: 700; }
.facts dd { margin: 2px 0 0; font-size: 1.05rem; font-weight: 700; }

.item-id { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin: 0; font-size: .9rem; }
.copy-id { padding: 4px 12px; font-size: .8rem; }

.buffs { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); gap: 8px; }
.buffs li {
  display: flex; align-items: baseline; gap: 8px;
  background: var(--panel-2); border: 2px solid var(--outline); padding: 7px 11px;
}
.buffs strong { color: var(--accent-dark); }
.buffs em { margin-left: auto; font-style: normal; font-size: .78rem; color: var(--ink-soft); }

/* ------------------------------------------------------------------ recipes */

.recipes { display: grid; grid-template-columns: repeat(auto-fit, minmax(330px, 1fr)); gap: 14px; }

.recipe {
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 2px var(--panel-2), inset 0 -5px 0 rgba(0,0,0,.13);
  clip-path: var(--cut);
  padding: 14px 16px;
}
.recipe-head { display: flex; align-items: center; gap: 10px; justify-content: space-between; margin-bottom: 8px; }
.recipe-unlock { font-size: .78rem; color: var(--ink-soft); font-weight: 600; text-align: right; }
.recipe-title { margin: 0 0 12px; font-size: 1.02rem; }
.recipe-body { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.recipe-ings { display: flex; gap: 6px; flex-wrap: wrap; flex: 1 1 180px; }
.recipe-arrow { font-weight: 800; color: var(--ink-soft); font-size: 1.1rem; }
.recipe-out {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  color: var(--ink); text-align: center; min-width: 84px;
}
.recipe-out:hover { text-decoration: none; }
.recipe-out .tile-icon { width: 52px; height: 52px; }

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--panel-2);
  border: 2px solid var(--outline);
  padding: 3px 8px 3px 4px;
  color: var(--ink);
  font-size: .82rem;
}
.chip:hover { background: var(--panel-3); text-decoration: none; }
.chip-icon { width: 26px; height: 26px; flex: none; object-fit: contain; }
.chip-name { font-weight: 600; }
.chip-count { font-weight: 800; color: var(--accent-dark); }
.chip-category { background: var(--panel-3); font-style: italic; padding-left: 8px; }

.crop-summary { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }

/* ------------------------------------------------------------------ stages */

.stages {
  list-style: none; margin: 0; padding: 0;
  display: flex; gap: 10px; flex-wrap: wrap;
}
.stages li {
  display: flex; flex-direction: column; align-items: center;
  text-align: center; width: 84px;
}
.stage-art {
  display: flex; align-items: center; justify-content: center;
  width: 84px; height: 96px;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 8px;
  /* Crop cells are 16x32 with the plant sitting in the lower half, so the sprite
     carries real transparent headroom. Clip rather than let it bleed onto the label. */
  overflow: hidden;
}
.stage-art img, .stage-art .fallback {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto;
  object-fit: contain;
}
.stage-label { display: block; font-size: .8rem; font-weight: 700; margin-top: 6px; }
.stage-day { display: block; font-size: .72rem; color: var(--ink-soft); }

/* ------------------------------------------------------------------ tables */

.table-wrap {
  overflow-x: auto;
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 3px var(--panel-2);
  background: var(--panel);
  clip-path: var(--cut);
}
.data-table { width: 100%; border-collapse: collapse; font-size: .92rem; min-width: 620px; }
.data-table th, .data-table td { padding: 9px 14px; text-align: left; border-bottom: 2px solid var(--panel-3); }
.data-table thead th {
  background: var(--panel-3);
  font-size: .76rem; text-transform: uppercase; letter-spacing: .05em;
  cursor: pointer; user-select: none;
  border-bottom: 3px solid var(--outline);
  white-space: nowrap;
}
.data-table thead th:hover { background: var(--slot); }
/* A dim up-down glyph on every sortable column, so the affordance is visible before the
   first click. The solid arrow replaces it once that column is the active sort. These
   three rules have equal specificity, so the resting state MUST come first. */
.data-table thead th[data-sort]::after {
  content: " \21C5";            /* up-down arrows: reads as "sortable" at this size */
  font-size: .95em;
  opacity: .5;
  margin-left: 3px;
}
.data-table thead th[data-sort]:hover::after { opacity: .85; }
.data-table thead th[aria-sort="ascending"]::after { content: " \25B2"; font-size: .8em; opacity: 1; }
.data-table thead th[aria-sort="descending"]::after { content: " \25BC"; font-size: .8em; opacity: 1; }
/* Only sortable headers should look clickable. */
.data-table thead th { cursor: default; }
.data-table thead th[data-sort] { cursor: pointer; }
.data-table tbody tr:hover { background: var(--panel-2); }
.data-table .num { text-align: right; font-variant-numeric: tabular-nums; }

.row-item { display: flex; align-items: center; gap: 10px; color: var(--ink); font-weight: 600; }
.row-item:hover { text-decoration: none; }
.row-item .tile-icon { width: 42px; height: 42px; padding: 3px; }

/* ------------------------------------------------------------------ villagers */

.grid-npc { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }

.portrait {
  display: flex; align-items: center; justify-content: center;
  width: 88px; height: 88px;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 4px;
  flex: none;
}
.portrait { position: relative; }
.portrait > img, .portrait > .fallback { width: 100%; height: 100%; object-fit: contain; }
.portrait-art { padding: 10px; }

/* Full character and portrait sheets on the villager page. */
.sheets { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-start; }
.sheet { margin: 0; text-align: center; }
.sheet-art {
  display: flex; align-items: center; justify-content: center;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 8px;
  max-height: 520px; overflow: auto;
}
/* Doubled so the pixel art reads at a comfortable size without blurring. */
.sheet-art img { image-rendering: pixelated; zoom: 2; }
.sheet figcaption {
  margin-top: 6px; font-size: .78rem; font-weight: 700; color: var(--ink-soft);
  text-transform: capitalize;
}

/* Portrait and overworld sprite read as ONE piece of art.
   They used to stack as two separate framed boxes, and the sprite is 16x32 in a 76x96 slot,
   so most of the second box was empty and the pair looked unfinished. The sprite now stands
   on the portrait's bottom edge, overlapping it, which is also how the game shows a
   character: face above, body below. */
.npc-art {
  position: relative;
  display: block;
  /* MUST NOT stretch. As a grid item it defaults to stretching to the full row height, so the
     sprite's `bottom` resolved against the whole hero and it floated far below the portrait,
     landing on the Best gifts row. Pinning to start makes this box exactly the portrait. */
  align-self: start;
}
.npc-art .item-hero-art { width: 100%; }

.npc-walk {
  position: absolute;
  /* INSIDE the portrait's lower right, not hanging below it. Sitting outside the frame left
     the sprite stranded in the gap next to the Best gifts row, which read as a stray image
     rather than part of the portrait. */
  right: 10px;
  bottom: 10px;
  /* No frame and no fill. A second border is what made it read as a separate widget. */
  background: none;
  border: 0;
  box-shadow: none;
  padding: 0;
  pointer-events: none;
}
/* 16x32 art at an INTEGER 3x. Integer only: a fractional scale blurs the pixels even under
   image-rendering: pixelated, because source rows stop landing on whole device pixels. */
.npc-walk img {
  width: 48px;
  height: 96px;
  /* Reads against whatever part of the portrait sits behind it. */
  filter: drop-shadow(0 0 2px rgba(0,0,0,.85)) drop-shadow(2px 3px 0 rgba(0,0,0,.35));
}

@media (max-width: 560px) {
  .npc-walk { right: 6px; bottom: 6px; }
  .npc-walk img { width: 32px; height: 64px; }
}

/* Secret notes that are pictures rather than prose. */
.note-image { display: flex; flex-direction: column; align-items: center; }
.note-art {
  display: flex; align-items: center; justify-content: center;
  width: 100%; max-width: 256px; aspect-ratio: 1;
  background: var(--panel-3);
  border: 2px solid var(--outline);
  padding: 6px;
}
.note-art img { width: 100%; height: 100%; object-fit: contain; }

.panel-gifts { padding-bottom: 12px; }

.gift-tier { margin-bottom: 18px; border-left: 6px solid var(--panel-3); padding-left: 14px; }
.gift-tier h3 {
  display: flex; align-items: center; gap: 8px;
  margin: 0 0 6px; font-size: 1rem;
}
.tier-count {
  background: var(--panel-3); border: 2px solid var(--outline);
  font-size: .72rem; padding: 0 6px; font-weight: 800;
}
.tier-love   { border-left-color: #c9455f; }
.tier-like   { border-left-color: var(--accent); }
.tier-neutral{ border-left-color: var(--panel-3); }
.tier-dislike{ border-left-color: var(--gold); }
.tier-hate   { border-left-color: var(--danger); }

.gift-quote {
  margin: 0 0 8px; font-style: italic; color: var(--ink-soft); font-size: .88rem;
  max-width: 70ch;
}
.chips { display: flex; gap: 6px; flex-wrap: wrap; }

.relations { list-style: none; margin: 0; padding: 0; display: flex; gap: 8px; flex-wrap: wrap; }
.relations li {
  background: var(--panel-2); border: 2px solid var(--outline);
  padding: 5px 11px; font-size: .88rem;
}
.relations li span { color: var(--ink-soft); font-size: .8rem; }

/* ------------------------------------------------------------------ calendar */

.cal-jump { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 14px; justify-content: center; }
.cal-jump a { border-width: 3px; padding: 5px 16px; font-size: .9rem; }
.cal-jump a:hover { text-decoration: none; filter: brightness(1.08); }

.cal-season {
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 3px var(--panel-2), inset 0 -6px 0 rgba(0,0,0,.14);
  clip-path: var(--cut);
  padding: 20px 22px 22px;
  margin-bottom: 24px;
  scroll-margin-top: 16px;
}
.cal-head { display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap; margin-bottom: 14px; }
.cal-head h2 { margin: 0; }
.cal-head .season { font-size: 1.05rem; padding: 4px 14px; }
.cal-sum { margin: 0; color: var(--ink-soft); font-size: .88rem; }

.cal-grid {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  gap: 5px;
  margin-bottom: 18px;
}
.cal-dow {
  text-align: center; font-size: .7rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .06em;
  color: var(--ink-soft); padding-bottom: 2px;
}
.cal-day {
  position: relative;
  min-height: 78px;
  background: var(--panel-2);
  border: 2px solid var(--outline);
  padding: 3px 4px 5px;
  display: flex; flex-direction: column; gap: 3px;
  scroll-margin-top: 90px;
}
.cal-day.has-event { background: var(--panel); box-shadow: inset 0 0 0 2px var(--gold); }
.cal-num { font-size: .72rem; font-weight: 800; color: var(--ink-soft); }

.cal-fest {
  display: block;
  background: var(--gold); color: var(--ink);
  border: 2px solid var(--outline);
  font-size: .64rem; font-weight: 800; line-height: 1.2;
  padding: 2px 4px;
  overflow-wrap: anywhere;
}
.cal-birthday {
  display: flex; flex-direction: column; align-items: center; gap: 2px;
  color: var(--ink); text-align: center;
}
.cal-birthday:hover { text-decoration: none; }
.cal-portrait {
  display: flex; align-items: center; justify-content: center;
  width: 40px; height: 40px;
  background: var(--slot); border: 2px solid var(--outline);
  padding: 2px;
}
.cal-portrait img, .cal-portrait .fallback { width: 100%; height: 100%; object-fit: contain; }
.cal-bname { font-size: .62rem; font-weight: 700; line-height: 1.15; overflow-wrap: anywhere; }

.cal-fest-list { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
.fest-card {
  background: var(--panel-2); border: 2px solid var(--outline);
  padding: 10px 14px; min-width: 190px;
}
.fest-card h3 { margin: 0 0 3px; font-size: .95rem; }
.fest-when { margin: 0; font-size: .8rem; font-weight: 700; color: var(--accent-dark); }
.fest-where { margin: 2px 0 0; font-size: .78rem; color: var(--ink-soft); }

a.cal-fest:hover { background: #d99b2f; text-decoration: none; }
a.fest-card { display: block; color: var(--ink); }
a.fest-card:hover { background: var(--panel-3); text-decoration: none; }

.cal-links { display: grid; gap: 16px; }

/* ------------------------------------------------------------------ festivals */

.fest-season { margin-bottom: 20px; }
.fest-season h2 { margin: 0 0 14px; }
.fest-season .season { font-size: 1.05rem; padding: 4px 14px; }

.fest-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 12px; }
.fest-tile {
  display: flex; flex-direction: column; gap: 6px; align-items: flex-start;
  background: var(--panel-2);
  border: 3px solid var(--outline);
  box-shadow: inset 0 -4px 0 rgba(0,0,0,.14);
  clip-path: var(--cut);
  padding: 14px 16px;
  color: var(--ink);
}
.fest-tile:hover { background: var(--panel-3); text-decoration: none; transform: translateY(-2px); }
.fest-tile-name { font-size: 1.08rem; font-weight: 800; line-height: 1.2; }
.fest-tile-meta { font-size: .82rem; color: var(--ink-soft); }
.fest-tile-count { font-size: .76rem; font-weight: 700; color: var(--accent-dark); }

.fest-art { background: var(--panel-2); box-shadow: inset 3px 3px 0 rgba(0,0,0,.18); }
.fest-badge { font-size: 1rem; padding: 8px 16px; text-align: center; }

.fest-people {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: 10px;
}
.fest-person {
  display: flex; gap: 11px; align-items: flex-start;
  background: var(--panel-2);
  border: 2px solid var(--outline);
  padding: 9px 11px;
  color: var(--ink);
}
.fest-person:hover { background: var(--panel-3); text-decoration: none; }
.fest-person-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fest-person-name { font-weight: 800; font-size: .88rem; }
.fest-line { font-size: .82rem; color: var(--ink-soft); line-height: 1.35; }
/* "%" lines are narration about the character, not speech, so they stay unquoted. */
.fest-line:not(.is-narration)::before { content: '\201C'; }
.fest-line:not(.is-narration)::after { content: '\201D'; }
.fest-line.is-narration { font-style: italic; opacity: .85; }
.cal-link-group h3 {
  display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap;
  font-size: .95rem; margin: 0 0 8px;
  border-bottom: 2px solid var(--panel-3); padding-bottom: 5px;
}
.all-link { font-size: .78rem; font-weight: 600; margin-left: auto; }
.group-count {
  background: var(--panel-3); border: 2px solid var(--outline);
  font-size: .7rem; padding: 0 6px; font-weight: 800;
}

/* ------------------------------------------------------------------ bundles */

.bundle-tools { display: flex; align-items: center; gap: 14px; margin-bottom: 16px; flex-wrap: wrap; }
.bundle-progress { color: var(--on-sky-soft); font-size: .9rem; font-weight: 700; }

.bundle-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 14px; }
.bundle {
  background: var(--panel-2);
  border: 3px solid var(--outline);
  box-shadow: inset 0 -4px 0 rgba(0,0,0,.12);
  padding: 12px 14px;
}
.bundle.is-complete { background: #d9ecc9; box-shadow: inset 0 0 0 3px var(--accent); }
.bundle-head { display: flex; align-items: center; gap: 10px; justify-content: space-between; flex-wrap: wrap; }
.bundle-head h3 { margin: 0; font-size: 1rem; }
.bundle-need { margin: 2px 0 8px; font-size: .76rem; color: var(--ink-soft); font-weight: 700; }

.chip-tick { cursor: pointer; font: inherit; }
.chip-tick.is-ticked { background: var(--accent); color: #fff; }
.chip-tick.is-ticked .chip-count { color: #fff; }
.chip-tick.is-ticked .chip-icon { opacity: .55; }
.chip-money { background: var(--gold); }
.chip-q { font-size: .66rem; font-weight: 800; text-transform: uppercase; opacity: .75; }

/* ------------------------------------------------------------------ museum */

.rewards { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 10px; }
.reward-card {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--panel-2); border: 2px solid var(--outline); padding: 9px 12px;
}
.reward-card .chips { flex: 1 1 120px; }

/* ------------------------------------------------------------------ wide tiles */

.grid-wide { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); }
.tile-wide { padding: 14px 10px 12px; }
.tile-icon-lg { width: 104px; height: 104px; padding: 8px; }
.tile-icon-lg img, .tile-icon-lg .fallback { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }

.shops { list-style: none; margin: 0; padding: 0; display: grid; gap: 6px; }
.shops li {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  background: var(--panel-2); border: 2px solid var(--outline); padding: 7px 12px;
}
.shop-name { font-weight: 700; min-width: 170px; }
.shop-price strong { color: var(--accent-dark); }
.shop-cond { margin-left: auto; font-style: normal; font-size: .78rem; color: var(--ink-soft); }
.muted { color: var(--ink-soft); font-weight: 400; }
.num.neg, dd.neg { color: var(--danger); }

.data-table .cell-sm { font-size: .8rem; }
.data-table .season { padding: 1px 5px; font-size: .66rem; margin-right: 2px; }

/* ------------------------------------------------------------------ locations */

.loc-thumb {
  display: block; width: 100%; aspect-ratio: 4/3; overflow: hidden;
  background: var(--slot); border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
}
.loc-thumb img { width: 100%; height: 100%; object-fit: cover; object-position: center; }

.loc-seasons {
  display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 10px;
  grid-template-columns: none;
}
.loc-seasons .pl-season { padding: 6px 18px; font-size: .84rem; }

.loc-map {
  overflow: auto; max-height: 78vh;
  background: #0d1a3a;              /* same well as the planner stage */
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 3px #071026;
}
.loc-map img { display: block; max-width: none; image-rendering: pixelated; }

/* ------------------------------------------------------------------ cards */

.grid-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 12px; }
.grid-cards-sm { grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); }
.card {
  background: var(--panel-2); border: 2px solid var(--outline);
  padding: 11px 14px;
}
.card h3 { margin: 0 0 5px; font-size: .98rem; }
.card-text { margin: 0 0 6px; font-size: .84rem; color: var(--ink-soft); line-height: 1.4; }
.card-obj { margin: 0 0 6px; font-size: .84rem; font-weight: 700; }
.card-meta { margin: 0; font-size: .76rem; color: var(--ink-soft); font-weight: 700; }
.card-list { margin: 6px 0 0; padding-left: 18px; font-size: .82rem; }
.note-card .card-text { white-space: normal; font-style: italic; }

/* ------------------------------------------------------------------ swatches */

.swatches { display: flex; flex-wrap: wrap; gap: 6px; }
.swatch {
  display: flex; align-items: center; justify-content: center;
  background: var(--slot); border: 2px solid var(--outline);
  padding: 3px;
}
.swatches.wallpaper .swatch { width: 44px; height: 108px; }
.swatches.flooring .swatch { width: 76px; height: 76px; }
.swatch img { width: 100%; height: 100%; object-fit: contain; }

/* Hairstyles: the style drawn on the farmer's standing frame rather than floating alone.
   The hair cell is the top 16x16 of a 16x32 block and the body is that whole block, so at
   the SAME integer scale the two align with no offset. 2x here (32x64) because a 16px head
   is too small to tell two styles apart. Integer scale only, or the pixels blur. */
/* One card per hairstyle, the style shown on a dressed farmer from three angles. */
.hairgrid { display: flex; flex-wrap: wrap; gap: 10px; }
.haircard {
  margin: 0; padding: 6px;
  background: var(--slot); border: 2px solid var(--outline);
}
.hair-poses { display: flex; gap: 4px; }
/* 4x the 16x32 source. Integer only, or the pixel art blurs. */
.hair-figure { position: relative; display: block; width: 64px; height: 128px; }
/* MUST out-specify `.swatch img`-style rules: width/height 100% with object-fit: contain
   would stretch the 16x16 hair cell to the full 32x64 box and then centre it, dropping the
   hairstyle onto the farmer's chest. fill, not none: the box is an exact 2x of the source,
   so filling scales 16x32 to 32x64 and 16x16 to 32x32. `none` drew both at intrinsic 1x. */
.hair-figure img.hair-body,
.hair-figure img.hair-top { position: absolute; left: 0; top: 0; object-fit: fill; }
.hair-figure img.hair-body { z-index: 0; width: 64px; height: 128px; }
.hair-figure img.hair-top { z-index: 1; width: 64px; height: 64px; }
.haircard figcaption { font-size: .72rem; text-align: center; opacity: .75; margin-top: 3px; }

/* The garment shown on a farmer, on shirt item pages. 4x the 16x32 source, integer only. */
.worn-preview { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; }
.worn-preview img.worn-art {
  width: 64px; height: 128px;
  object-fit: fill;
  background: var(--slot);
  border: 2px solid var(--outline);
  padding: 4px;
}
.worn-preview .note { margin: 0; }

/* ------------------------------------------------------------------ planner */

/* Fullscreen tool: the map is the page, everything else floats over it. Fixed rather
   than tall-and-scrolling so the header stays put and only the map pans.

   The header is much taller than the old wood bar, and a fullscreen editor cannot
   afford 232px of logo. So on this route only, the masthead shrinks and the search
   is dropped, and --head-h drops to match.

   --head-h is declared at :root as the FULL header height on purpose. A browser
   without :has() keeps the full header AND the offset that matches it, so this
   degrades to correct-but-taller rather than to a broken overlap. */

/* Base rule, and the no-:has() fallback. Correct in that case precisely because a
   browser that cannot match :has() also never applies the compaction below, so the
   header really is --head-h tall. */
.planner {
  position: fixed;
  inset: var(--head-h) 0 0 0;
  z-index: 5;
  background: #17120d;
}

/* With :has(), the planner simply fills whatever the header leaves, so there is no
   offset to keep in step at all. That matters: the menu bar wraps to a second row at
   narrow widths, and any hardcoded offset would desync from it. An earlier version
   pinned this at a measured 138px and left a 37px band of sky at 860px wide. */
body:has(.planner) { height: 100vh; overflow: hidden; }
body:has(.planner) main { flex: 1; min-height: 0; position: relative; padding: 0; max-width: none; }
body:has(.planner) .planner { position: absolute; inset: 0; }

/* Nothing above the map. The masthead goes entirely (its logo reappears inside the tool
   panel instead) and the menu bar comes OUT of flow to float over the map, so the editor
   gets the whole viewport and no band of sky sits above it. */
body:has(.planner) .masthead,
body:has(.planner) .ground,
body:has(.planner) .footer { display: none; }

body:has(.planner) .menubar {
  position: fixed;
  top: 14px;
  left: 14px;
  right: 14px;
  z-index: 30;
  margin: 0;
  padding: 0;
  justify-content: flex-start;
  /* The strip spans the width so the menu sits left and the status bar right on the same
     line, but only the bar itself may swallow clicks meant for the map. */
  pointer-events: none;
}
body:has(.planner) .menu { pointer-events: auto; }

/* Minimising the tool panel takes the floating menu with it, so the map is fully clear. */
body:has(.planner):has(.pl-side.is-min) .menubar { display: none; }

.pl-stage {
  position: absolute; inset: 0;
  overflow: auto;
  background: #17120d;
  box-shadow: inset 0 0 0 3px #0d0a07;
  /* Placed objects carry z-index 100+ so they layer front-to-back by row. Without
     isolation those values compete with the floating panel and render over it. */
  z-index: 0;
  isolation: isolate;
}
.pl-canvas { position: relative; }
.pl-bg { display: block; image-rendering: pixelated; user-select: none; }
.pl-fx { position: absolute; inset: 0; pointer-events: none; }
.pl-objects { position: absolute; inset: 0; }
.pl-obj { position: absolute; image-rendering: pixelated; user-select: none; cursor: grab; }
.pl-obj:active { cursor: grabbing; }

/* Floating control panel. Wears the same 9-slice frame as .panel, so the frame
   metrics come from the shared rule and only the box geometry lives here. */
.pl-side {
  /* Below the floating menu strip: 14 top + --s bar + 14 gap. */
  position: absolute; top: calc(var(--s) + 28px); left: 14px;
  z-index: 20;
  width: 322px;
  max-height: calc(100% - var(--s) - 42px);
  overflow: hidden;
  filter: drop-shadow(0 5px 0 rgba(0,0,0,.4));
  padding: 14px;
  display: flex; flex-direction: column; gap: 9px;
}
.pl-side[hidden] { display: none; }

.pl-title { display: flex; align-items: flex-start; gap: 8px; }
.pl-title h1 { font-size: 1.05rem; margin: 0; flex: 1; line-height: 1.25; }
.pl-winbtn {
  background: var(--panel-2); border: 2px solid var(--outline); color: var(--ink);
  font: inherit; font-size: 1rem; font-weight: 800; line-height: 1;
  padding: 2px 8px; cursor: pointer; flex: none;
}
.pl-winbtn:hover { background: var(--panel-3); }

/* Minimised: only the title bar remains. */
.pl-body { display: flex; flex-direction: column; gap: 9px; min-height: 0; }
.pl-side.is-min { max-height: none; }
.pl-side.is-min .pl-body { display: none; }

.pl-io .btn { font-size: .74rem; padding: 4px 8px; }
.pl-lede { margin: 0; font-size: .74rem; color: var(--ink-soft); line-height: 1.35; }
.pl-hint { margin: 0; font-size: .72rem; color: var(--ink-soft); line-height: 1.4; }

.pl-reopen {
  position: absolute; top: calc(var(--s) + 28px); left: 14px; z-index: 20;
  background: var(--accent); color: #fff;
  border: 3px solid var(--outline);
  box-shadow: inset 0 -4px 0 rgba(0,0,0,.28), 0 4px 0 rgba(0,0,0,.35);
  clip-path: var(--cut);
  font: inherit; font-weight: 800; padding: 8px 16px; cursor: pointer;
}
.pl-reopen[hidden] { display: none; }

.pl-farm-pick label {
  display: block; font-size: .7rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .05em; color: var(--ink-soft); margin-bottom: 3px;
}
#pl-farm, #pl-search {
  width: 100%;
  background: var(--panel-2);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.2);
  color: var(--ink); font: inherit; font-size: .88rem; padding: 6px 9px;
}
.pl-farm-blurb { margin: 5px 0 0; font-size: .74rem; color: var(--ink-soft); line-height: 1.3; }

.pl-seasons { display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px; }
.pl-season {
  font: inherit; cursor: pointer; padding: 5px 0; font-size: .72rem;
  opacity: .45; filter: saturate(.4);
}
.pl-season[aria-pressed="true"] { opacity: 1; filter: none; box-shadow: 0 0 0 2px var(--ink); }
.pl-season:hover { opacity: .85; }

.pl-tools { display: flex; gap: 4px; flex-wrap: wrap; }
.pl-tools .btn { padding: 4px 10px; font-size: .78rem; }
.pl-tools .btn[aria-pressed="true"] { background: var(--accent); color: #fff; }

.pl-palette { overflow-y: auto; margin: 0 -4px; padding: 0 4px; flex: 1; min-height: 90px; }
.pl-group h3 {
  display: flex; align-items: center; gap: 6px;
  font-size: .78rem; text-transform: uppercase; letter-spacing: .04em;
  margin: 10px 0 4px; padding-bottom: 3px; border-bottom: 2px solid var(--panel-3);
}
.pl-group-n {
  margin-left: auto; background: var(--panel-3); border: 2px solid var(--outline);
  font-size: .66rem; padding: 0 5px;
}
.pl-note { margin: 0 0 6px; font-size: .7rem; color: var(--ink-soft); }
.pl-items { display: grid; grid-template-columns: repeat(auto-fill, minmax(54px, 1fr)); gap: 4px; }

.pl-item {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  background: var(--panel-2); border: 2px solid var(--outline);
  padding: 4px 2px; cursor: pointer; font: inherit; color: var(--ink);
}
.pl-item:hover { background: var(--panel-3); }
.pl-item.is-active { background: var(--accent); color: #fff; box-shadow: 0 0 0 2px var(--gold); }
.pl-item-art {
  display: flex; align-items: center; justify-content: center; width: 32px; height: 32px;
  background: var(--slot); border: 2px solid var(--outline);
}
.pl-item-art img, .pl-item-art .fallback { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; }
.pl-item-name {
  font-size: .56rem; line-height: 1.15; text-align: center;
  overflow: hidden; text-overflow: ellipsis; display: -webkit-box;
  -webkit-line-clamp: 2; -webkit-box-orient: vertical;
}

/* Floating status bar, top right so it never sits under the panel */
.pl-bar {
  /* Fixed, not absolute, and on the same top as the floating menu so the two read as one
     strip across the top of the editor. min-height matches the menu bar's tile height. */
  position: fixed; top: 14px; right: 14px; z-index: 31;
  display: flex; align-items: center; gap: 8px;
  min-height: var(--s);
  max-width: min(520px, calc(100% - 340px));
  background: var(--panel); border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 2px var(--panel-2), 0 4px 0 rgba(0,0,0,.35);
  clip-path: var(--cut);
  padding: 7px 12px;
}
.pl-brush { font-weight: 700; font-size: .82rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.pl-spacer { flex: 1; }
.pl-count { font-size: .76rem; color: var(--ink-soft); font-weight: 700; white-space: nowrap; }
.pl-zoom { padding: 1px 10px; font-size: 1rem; line-height: 1.2; }
.pl-zoomlevel { font-size: .78rem; font-weight: 800; min-width: 32px; text-align: center; }

@media (max-width: 780px) {
  .pl-side { width: calc(100% - 28px); max-height: 58%; }
  .pl-bar { top: auto; bottom: 14px; left: 14px; right: 14px; max-width: none; }
}

/* ------------------------------------------------------------------ ground
   bottom.png is 64x41: clouds down to row 22, a transition, then solid green
   from row 28, which the footer continues exactly. Repeats horizontally with
   no seam. */

.ground {
  height: calc(var(--s) * 2.5625);                              /* 41px art */
  margin-top: calc(var(--s) * 1.2);
  background-image: url('/branding/bottom.png');
  background-repeat: repeat-x;
  background-position: left bottom;
  background-size: calc(var(--s) * 4) calc(var(--s) * 2.5625);  /* 64x41 art */
  image-rendering: pixelated;
}

/* ------------------------------------------------------------------ footer */

.footer {
  background: var(--green);
  color: #eafbe9;
  padding: 30px 20px 40px;
  text-align: center;
  font-size: .88rem;
}
.footer-brand { color: #fff; font-weight: 800; font-size: 1.05rem; margin: 0 0 18px; }

/* The full sitemap, in the same four groups as the top bar, rather than a flat row of
   every section. Same links, but scannable. */
.footer-map {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 18px 24px;
  max-width: 820px;
  margin: 0 auto 22px;
  text-align: left;
}
.footer-col h2 {
  margin: 0 0 8px;
  font-size: .72rem;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: #b6ecb2;
}
.footer-col a { display: block; color: #fff; padding: 2px 0; font-size: .85rem; }
.footer-meta { margin: 0 0 8px; }
.footer-legal { margin: 0 auto; max-width: 60ch; font-size: .78rem; opacity: .68; }

/* ------------------------------------------------------------------ narrow */

@media (max-width: 720px) {
  .item-hero { grid-template-columns: 1fr; }
  .item-hero-art { max-width: 190px; margin: 0 auto; }
  .nav { order: 3; width: 100%; margin-right: 0; }
  .facet-label { min-width: 100%; }
}

/* ------------------------------------------------------------------ heart events */

.event-npc { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.event-portrait { width: 56px; height: 56px; flex: none; }
.event-npc .pl-group-n { margin-left: 0; }

.event-card { display: flex; flex-direction: column; }
.event-head { display: flex; align-items: center; gap: 8px; margin-bottom: 4px; }
.hearts { color: #c9455f; font-size: .82rem; letter-spacing: -1px; line-height: 1; }
.event-hearts-n {
  background: var(--panel-3); border: 2px solid var(--outline);
  font-size: .68rem; font-weight: 800; padding: 0 5px;
}
.event-lines { list-style: none; margin: 8px 0 0; padding: 0; display: grid; gap: 5px; }
.event-lines li {
  font-size: .82rem; line-height: 1.4; color: var(--ink-soft);
  border-left: 3px solid var(--panel-3); padding-left: 9px;
}
.event-lines strong { color: var(--ink); }

/* ------------------------------------------------------------------ layouts */

.layout-map { margin-bottom: 14px; }
.layout-map img { display: block; max-width: none; image-rendering: pixelated; }

.layout-stats {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; margin-bottom: 14px;
}
.layout-stat {
  background: var(--panel); border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 2px var(--panel-2);
  clip-path: var(--cut); padding: 10px 13px;
}
.layout-stat dt {
  font-size: .7rem; text-transform: uppercase; letter-spacing: .05em;
  color: var(--ink-soft); font-weight: 800;
}
.layout-stat dd { margin: 3px 0 0; font-size: 1.25rem; font-weight: 800; }

.layout-actions { display: flex; gap: 9px; flex-wrap: wrap; margin-bottom: 22px; }

/* ------------------------------------------------------------------ store */

.store-hero {
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 3px var(--panel-2), inset 0 -6px 0 rgba(0,0,0,.14);
  clip-path: var(--cut);
  padding: 32px 26px 26px;
  text-align: center;
  margin-bottom: 18px;
}
.store-hero h1 { margin-bottom: .3em; }

#store-sort {
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.22);
  color: var(--ink); font: inherit; font-size: .95rem; padding: 8px 12px;
}

.prod-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }

.prod {
  display: flex; flex-direction: column;
  background: var(--panel);
  border: 3px solid var(--outline);
  box-shadow: inset 0 0 0 2px var(--panel-2), inset 0 -5px 0 rgba(0,0,0,.13);
  clip-path: var(--cut);
  overflow: hidden;
}
.prod-art {
  display: block; aspect-ratio: 1;
  background: var(--slot);
  border-bottom: 3px solid var(--outline);
  overflow: hidden;
}
.prod-art img { width: 100%; height: 100%; object-fit: cover; transition: transform .25s; }
.prod-art:hover img { transform: scale(1.04); }

.prod-body { display: flex; flex-direction: column; flex: 1; padding: 14px 16px 15px; }
.prod-head { display: flex; align-items: flex-start; gap: 10px; justify-content: space-between; }
.prod-head h3 { margin: 0; font-size: 1.02rem; line-height: 1.25; }
.prod-price { font-weight: 800; color: var(--accent-dark); white-space: nowrap; font-size: .9rem; }
.prod-desc { margin: 8px 0 0; font-size: .85rem; color: var(--ink-soft); line-height: 1.45; flex: 1; }
.prod-foot { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-top: 14px; }
.prod-shop { font-size: .68rem; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-soft); font-weight: 800; }
.prod-foot .btn { font-size: .74rem; padding: 6px 12px; }

.store-disclosure {
  margin: 26px auto 0; max-width: 62ch; text-align: center;
  font-size: .78rem; color: var(--on-sky-soft); line-height: 1.5;
}

/* ------------------------------------------------------------------ spoilers */

.spoiler { border: 3px solid var(--outline); background: var(--panel-2); }
.spoiler > summary {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  cursor: pointer; list-style: none;
  padding: 12px 15px;
  font-weight: 700; font-size: .92rem;
  background: var(--panel-3);
  user-select: none;
}
.spoiler > summary::-webkit-details-marker { display: none; }
.spoiler > summary::after {
  content: 'Show';
  margin-left: auto;
  background: var(--accent); color: #fff;
  border: 2px solid var(--outline);
  font-size: .74rem; font-weight: 800; text-transform: uppercase; letter-spacing: .06em;
  padding: 3px 12px;
}
.spoiler[open] > summary::after { content: 'Hide'; background: var(--ink-soft); }
.spoiler > summary:hover { background: var(--slot); }
.spoiler > summary:focus-visible { outline: 3px solid var(--gold); outline-offset: -3px; }

.spoiler-tag {
  background: var(--danger); color: #fff;
  border: 2px solid var(--outline);
  font-size: .68rem; font-weight: 800; text-transform: uppercase; letter-spacing: .08em;
  padding: 2px 8px;
}
.spoiler-body { padding: 14px 15px 15px; }

/* Compact reveal, for one-per-card use like the secret notes. */
.spoiler-sm > summary { padding: 7px 10px; font-size: .8rem; gap: 7px; }
.spoiler-sm > summary::after { font-size: .66rem; padding: 2px 9px; }
.spoiler-sm .spoiler-tag { font-size: .6rem; padding: 1px 6px; }
.spoiler-sm .spoiler-body { padding: 10px; }
.note-card .spoiler { width: 100%; }
.note-card.note-image .spoiler-body { display: flex; align-items: center; justify-content: center; }

/* ------------------------------------------------------------------ gift guide */

.gg-head { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.gg-portrait {
  flex: none; width: 64px; height: 64px;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  display: flex; align-items: center; justify-content: center;
}
.gg-id h2 { margin: 0 0 3px; font-size: 1.2rem; }
.gg-id h2 a { color: var(--ink); }
.gg-meta { margin: 0; font-size: .8rem; font-weight: 700; color: var(--ink-soft); }
.gg-meta .pill { margin-left: 6px; vertical-align: 1px; }
.chip-jump { cursor: pointer; }

/* ------------------------------------------------------------------ games */

.game-bar {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; flex-wrap: wrap; margin-bottom: 16px;
}
.game-modes { display: flex; gap: 6px; }
.game-modes .btn { font-size: .78rem; padding: 5px 14px; }
.game-modes .btn[aria-pressed="true"] { background: var(--accent); color: #fff; }
.game-progress { margin: 0; font-size: .8rem; font-weight: 800; color: var(--ink-soft); }

.game-stage { text-align: center; }
.game-q { margin: 0 0 16px; font-size: 1.15rem; font-weight: 700; }

.game-art { display: flex; justify-content: center; margin-bottom: 18px; }

/* The frame is a separate element from the image because the zoom needs something to
   clip against. Putting overflow on the image itself does nothing. */
.game-frame {
  display: block;
  overflow: hidden;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 6px;
}
.frame-portrait { width: 176px; height: 176px; }
.frame-item { width: 120px; height: 120px; }
.game-frame img {
  display: block;
  width: 100%; height: 100%;
  object-fit: contain;
  transition: transform .22s ease-out;
}

/* Zoomed, NOT silhouetted. Blacking a portrait out is the Pokemon trick and it does not
   transfer: those sprites are transparent cutouts with a readable outline, whereas a
   Stardew portrait is an opaque 64x64 image with its own background, so brightness(0)
   renders a solid black rectangle with nothing to go on. The origin sits above centre so
   the crop lands on eyes and hair rather than the mouth.
   NOT called .is-hidden either: that is a site-wide utility (display: none !important)
   used by the filter modules, and its !important silently collapsed the portrait. */
.game-frame img.is-zoomed { transform: scale(6); transform-origin: 50% 36%; }

.game-choices {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px; max-width: 640px; margin: 0 auto;
}
.game-again { max-width: 460px; margin-top: 18px; }

.game-choice {
  display: flex; align-items: center; justify-content: center; gap: 9px;
  background: var(--panel-2);
  border: 3px solid var(--outline);
  box-shadow: inset 0 -4px 0 rgba(0,0,0,.18);
  clip-path: var(--cut);
  padding: 10px 12px;
  font: inherit; font-weight: 700; font-size: .92rem;
  color: var(--ink);
  cursor: pointer;
}
.game-choice:hover:not(:disabled) { background: var(--panel-3); }
.game-choice:disabled { cursor: default; }
.game-choice-art {
  width: 34px; height: 34px; flex: none; object-fit: contain;
  background: var(--slot); border: 2px solid var(--outline);
}
.game-choice.is-right { background: var(--accent); color: #fff; }
.game-choice.is-wrong { background: var(--danger); color: #fff; }

.game-verdict {
  display: flex; align-items: center; justify-content: center; gap: 14px;
  flex-wrap: wrap; margin: 18px 0 0; font-weight: 700;
}
.game-verdict:empty { margin: 0; }
.is-right-text { color: var(--accent-dark); }
.is-wrong-text { color: var(--danger); }
.game-done-sub { margin: 0 0 4px; color: var(--ink-soft); font-size: .9rem; }

/* Announced to screen readers, not shown: the board already says it visually. */
.game-live {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ------------------------------------------------------------------ movies */

.movie-head { display: flex; gap: 18px; align-items: flex-start; margin-bottom: 16px; flex-wrap: wrap; }
.movie-poster {
  flex: none; display: block;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 5px;
}
.movie-poster img { display: block; width: 180px; height: auto; }   /* 90x62 art at 2x */
.movie-head h2 { margin: 0 0 6px; }
.movie-meta { margin: 0 0 10px; font-size: .82rem; font-weight: 700; color: var(--ink-soft); }
.movie-meta span { opacity: .5; margin: 0 5px; }
.movie-desc { margin: 0; font-size: .92rem; max-width: 68ch; }

/* ------------------------------------------------------------------ house upgrades */

.upgrade-art { display: flex; align-items: center; gap: 16px; flex-wrap: wrap; margin: 0 0 16px; }
.upgrade-art figure { margin: 0; text-align: center; }
.upgrade-art figure span {
  display: block;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 6px;
}
.upgrade-art img { display: block; width: 240px; height: auto; }   /* 160x144 art at 1.5x */
.upgrade-art figcaption {
  margin-top: 5px; font-size: .74rem; font-weight: 800;
  text-transform: uppercase; letter-spacing: .08em; color: var(--ink-soft);
}
.upgrade-arrow { font-size: 1.6rem; font-weight: 800; color: var(--ink-soft); }

/* ------------------------------------------------------------------ home sections
   Each section is a wide half of prose and a narrow half of art. `is-flipped` swaps
   the order so the columns alternate down the page. Below 820px they stack, art last,
   because on a phone the words are the point and the art is decoration. */

.hub-sec {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 220px;
  gap: 30px;
  align-items: start;
  background-color: var(--panel);
  background-image: url('/branding/ui-middle-left.png'), url('/branding/ui-middle-right.png');
  background-repeat: repeat-y, repeat-y;
  background-position: left top, right top;
  background-size: var(--s) var(--s), var(--s) var(--s);
  isolation: isolate;
  position: relative;
  padding: 22px 26px 24px;
  margin: 0 0 22px;
  image-rendering: pixelated;
}
.hub-sec::before, .hub-sec::after {
  content: '';
  position: absolute;
  left: 0; right: 0;
  height: var(--s);
  background-repeat: no-repeat, no-repeat, repeat-x;
  background-size: var(--s) var(--s);
  z-index: -1;
  pointer-events: none;
}
.hub-sec::before {
  top: 0;
  background-image: url('/branding/ui-top-left.png'), url('/branding/ui-top-right.png'), url('/branding/ui-top.png');
  background-position: left top, right top, left top;
}
.hub-sec::after {
  bottom: 0;
  background-image: url('/branding/ui-bottom-left.png'), url('/branding/ui-bottom-right.png'), url('/branding/ui-bottom.png');
  background-position: left bottom, right bottom, left bottom;
}
.hub-sec.is-flipped { grid-template-columns: 220px minmax(0, 1fr); }
.hub-sec.is-flipped .hub-sec-text { order: 2; }
.hub-sec.is-flipped .hub-sec-art { order: 1; }
.hub-sec h2 { margin: 0 0 .55em; font-size: 1.35rem; }
.hub-sec p { margin: 0 0 .85em; font-size: .95rem; line-height: 1.6; }
.hub-sec p:last-child { margin-bottom: 0; }
.hub-sec-art { display: flex; justify-content: center; }

.illus {
  margin: 0;
  width: 100%;
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--slot);
  border: 3px solid var(--outline);
  box-shadow: inset 2px 2px 0 rgba(0,0,0,.25);
  padding: 14px;
}
.illus img { width: 100%; height: 100%; object-fit: contain; }

/* The homepage taster of the villager quiz. */
.hub-sec-game .hub-sec-art { width: 100%; }
.mini-game {
  position: relative;
  width: 100%;
  text-align: center;
  pointer-events: none;    /* a simulation, not a game */
  user-select: none;
}
.mini-game .game-frame { margin: 0 auto 12px; width: 160px; height: 160px; }
.mini-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.mini-choices .game-choice { padding: 7px 6px; font-size: .78rem; cursor: default; }
.mini-choices .game-choice.is-right { background: var(--accent); color: #fff; }

/* The pointer that acts the click out. Positioned from the top left of .mini-game and
   moved with a transform, so one transition covers travel and the click dip. */
.mini-cursor {
  position: absolute;
  top: 0; left: 0;
  z-index: 2;
  transition: transform .7s cubic-bezier(.4, 0, .2, 1);
  filter: drop-shadow(0 2px 0 rgba(0,0,0,.35));
}
.mini-cursor[hidden] { display: none; }
.mini-cursor.is-clicking svg { transform: scale(.8); }
.mini-cursor svg { display: block; transition: transform .12s; }

@media (prefers-reduced-motion: reduce) {
  .mini-cursor { transition: none; }
}

@media (max-width: 820px) {
  .hub-sec { grid-template-columns: 1fr; }
  .hub-sec.is-flipped .hub-sec-text,
  .hub-sec.is-flipped .hub-sec-art { order: initial; }
  .hub-sec-art { justify-content: flex-start; }
}

/* The masthead is hidden on this route, so the brand lives in the tool panel instead,
   centred above the title. Stays visible when the panel is minimised. */
.pl-logo { display: block; margin: 0 auto 10px; }
/* MUST be an integer multiple of the 109x38 art. 164px was 1.5x, which resamples every
   pixel unevenly and reads soft and undersized. The panel is 322px wide with 14px padding,
   so 294px of room: 2x (218px) is the largest clean scale that fits. 3x would be 327px. */
.pl-logo img { display: block; width: 218px; height: 76px; }
.pl-logo:hover { text-decoration: none; }

/* ------------------------------------------------------------------ related strip */

.related-strip {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  background: var(--panel);
  border: 3px solid var(--ink);
  box-shadow: inset 0 0 0 3px var(--panel-2);
  clip-path: var(--cut);
  padding: 12px 16px;
  margin-top: 22px;
}
.related-label {
  font-size: .74rem; font-weight: 800; text-transform: uppercase;
  letter-spacing: .05em; color: var(--ink-soft); margin-right: 4px;
}

/* Farmhouse upgrade level, shown in the status bar only while a levelled object is the
   brush. The farmhouse is the one building with three exteriors, so this appears for it
   and nothing else. */
.pl-levels { display: inline-flex; align-items: center; gap: 4px; margin-left: 10px; font-size: .78rem; }
.pl-level {
  min-width: 22px; padding: 2px 6px; cursor: pointer;
  border: 2px solid var(--outline); background: var(--panel-2); color: var(--ink);
  font: inherit; line-height: 1.1;
}
.pl-level[aria-pressed="true"] { background: var(--gold); font-weight: 700; }
.pl-level:hover { background: var(--slot); }
.pl-level:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }

/* Links inside the intro sit on the sky, where the default link colour reads as low
   contrast green on blue. White with an underline is legible against the gradient and
   still obviously a link. */
.page-sub a { color: #fff; text-decoration: underline; text-underline-offset: 2px; }
.page-sub a:hover { color: var(--gold); }
.page-sub a:focus-visible { outline: 3px solid var(--gold); outline-offset: 2px; }
