/* styles.css — MHGU Fishing
 *
 * Visual language is the MHGU family's: the same CSS variable names the Quest
 * Randomizer and Charm Farm use, the same texture plates layered under a
 * color-mix tint, and the same button/panel vocabulary. Every colour comes from
 * the theme at runtime (see theme.js) — nothing is hardcoded except the status
 * colours, which have to stay readable against every swatch in the palette.
 */

/* MHFU game font (falls back to sans-serif for glyphs it lacks) */
@font-face{
  font-family:'MHFU';
  src:url('fonts/mhfu_font.ttf') format('truetype');
  font-display:swap;
}

/* Theme variables are set at runtime from the chosen colour (see theme.js). */
:root{
  --bg:#3e3e3e; --bg1:#000000; --bg2:#333; --hover:#2f2f2f; --accent:#7a7a7a; --accent-hover:#9a9a9a;
  --text:#f3f3f3; --text-dim:#ffffff; --line:#ffffff1f; --card:#ffffff0d;
  --titlebar-overlay:rgba(0,0,0,0.18);
  /* Fixed status colours — these read against every swatch, so they deliberately
     do not follow the theme. --gold is now only the catch gauge needle; money figures
     use --money, which theme.js derives from the chosen colour. */
  --hp:#d64a4a; --sta:#6fc98a;
  --gauge-hp:#5bb85f; --gauge-sta:#e3c545; --gold:#e3c545; --danger:#e0603c;
  /* Climate, not theme: blue reads as cold and orange as heat to everyone, and
     that meaning is the whole point of the pulse. Deliberately NOT derived. */
  --chill:#6fc3e8; --scorch:#f0a070;
  --slack:#6fc3e8;
  --money:#e3c545;
}

*{box-sizing:border-box;scrollbar-color:var(--accent) var(--bg2);scrollbar-width:thin}
::-webkit-scrollbar{width:6px;height:6px}
::-webkit-scrollbar-track{background:var(--bg2)}
::-webkit-scrollbar-thumb{background:var(--accent);border-radius:3px}
::-webkit-scrollbar-thumb:hover{background:var(--accent-hover)}
input,select,button,textarea{font-family:inherit}

html,body{margin:0;height:100%;overflow:hidden}
body{
  font-family:'MHFU',"Segoe UI",system-ui,sans-serif;
  background:var(--bg); color:var(--text); font-size:14px;
  user-select:none; -webkit-user-select:none;
}
#app{display:flex;flex-direction:column;height:100vh}
.hidden{display:none !important}

/* ── Title bar ─────────────────────────────────────────────────────────── */
.titlebar{display:flex;align-items:center;gap:10px;padding:8px 14px;flex-wrap:wrap}
.titlebar-header{
  background-image:
    linear-gradient(color-mix(in srgb,var(--accent) 90%,transparent),color-mix(in srgb,var(--accent) 90%,transparent)),
    url('assets/titlebar-background.png');
  background-size:auto 100%;
  background-position:left center;
  background-repeat:repeat;
}
.title-icon{width:32px;height:32px;object-fit:contain}
.titlebar h1{font-size:15px;font-weight:600;margin:0}
.icon-btn{
  background:transparent;border:none;color:var(--text);font-size:18px;
  cursor:pointer;padding:6px 9px;border-radius:6px;line-height:1;
}
.icon-btn:hover{background:#ffffff1a}

.stats{display:flex;gap:14px;align-items:center;margin-left:auto;font-size:13px}
.stat{display:flex;align-items:baseline;gap:4px}
.stat b{font-size:15px;font-weight:700}
.stat i{font-style:normal;opacity:.75;font-size:11px}
.stat.zenny b{color:var(--money)}
.xpbar{display:inline-block;width:90px;height:6px;background:var(--bg1);border:1px solid var(--line);border-radius:3px;overflow:hidden}
.xpbar i{display:block;height:100%;background:var(--accent-hover);width:0}

/* Tabs get the rough stone rather than the banner strip — the banner is the
   buttons' surface, and the tabbar sitting on it made the two read as one piece
   of chrome. Irregular grain, so it also stays clear of the titlebar's regular
   brick above it. Tiled at native size and held under a heavier wash, because
   this is the darkest texture in the set and the tab labels sit on top of it. */
.tabbar{
  display:flex;gap:4px;padding:6px 14px;
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg2) 88%,transparent),color-mix(in srgb,var(--bg2) 88%,transparent)),
    url('assets/rockyTextureDark2.png');
  background-size:auto,auto;background-position:left center;background-repeat:repeat;
  border-top:1px solid var(--line);border-bottom:1px solid var(--line);
}
.tab{
  background:transparent;border:1px solid transparent;color:var(--text);
  opacity:.7;padding:5px 14px;border-radius:7px;cursor:pointer;font-size:14px;
}
.tab:hover:not(:disabled){opacity:1;background:#ffffff14}
.tab.active{opacity:1;background:var(--bg1);border-color:var(--line);font-weight:600}
.tab:disabled{opacity:.3;cursor:not-allowed}

/* ── Content ───────────────────────────────────────────────────────────────
   The window itself does NOT scroll. Everything is sized to the viewport and
   whatever is too tall scrolls inside its own panel, so the titlebar, the tabs
   and the Depart button all stay where you left them. */
.content{
  flex:1;min-height:0;overflow:hidden;padding:16px;
  background-color:var(--bg1);display:flex;
}
.inner{max-width:1400px;margin:0 auto;width:100%;min-height:0;display:flex}
.screen{display:none;min-height:0;width:100%}
.screen.active{display:flex;flex-direction:column}

/* The screens that are one long list scroll as one, since they have no fixed
   furniture of their own to keep in place. */
#guide.screen.active,#combos.screen.active,#shop.screen.active,
#smithy.screen.active{overflow-y:auto}
/* The Smithy's two halves each scroll as one list, same as the screen did. */
#smithy .subview.active{min-height:0}
/* The per-set strip is a second rank of tabs under the first, so it reads as
   subordinate to it rather than as a competing row. */
.subtabs.setline{flex-wrap:wrap;gap:3px;margin-bottom:8px;opacity:.95}
.subtabs.setline .subtab{font-size:12px;padding:4px 10px}

/* ── Buttons ───────────────────────────────────────────────────────────── */
.btn{
  background:#ffffff14;color:var(--text);border:1px solid var(--line);
  border-radius:7px;padding:8px 14px;cursor:pointer;font-size:14px;
}
.btn:hover:not(:disabled){background:#ffffff24}
.btn:disabled{opacity:.45;cursor:not-allowed}
.btn.block{width:100%}
.btn.tiny{padding:3px 10px;font-size:12px}
.btn.block,.btn.tiny{
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg2) 80%,transparent),color-mix(in srgb,var(--bg2) 80%,transparent)),
    url('assets/banner-background.png');
  background-position:right;
}
/* --text flips to black on the near-white swatch, which would vanish against
   --bg1 (always dark) — hardcode white, exactly as the Randomizer does. */
.btn.accent{background:var(--bg1);border-color:#000;color:#fff;font-weight:600;
  text-shadow:-1px 0 rgba(0,0,0,.55),1px 0 rgba(0,0,0,.55),0 -1px rgba(0,0,0,.55),0 1px rgba(0,0,0,.55)}
.btn.accent:hover:not(:disabled){background:var(--bg2)}
.btn.accent:disabled{opacity:.8;cursor:not-allowed}
.btn.secondary{background:var(--bg1);border:1px solid var(--line);color:#fff}
.btn.secondary:hover:not(:disabled){background:var(--hover)}
.btn.big{font-size:16px;padding:12px 30px}

/* Save bar: the file controls sit with the title, the way the other apps do. */
/* The four file controls wrap as ONE block, so a narrow window never strands
   Open on a line of its own away from the rest. */
.filebar{display:flex;gap:6px;align-items:center}
.dirty-dot{color:var(--money);font-size:10px;vertical-align:middle}
.titlebar .btn.tiny{white-space:nowrap}

/* A quiet line that says what just happened and gets out of the way. */
.toast{
  position:fixed;left:50%;bottom:18px;transform:translateX(-50%);z-index:60;
  background:var(--bg1);color:var(--text);
  border:1px solid var(--line);border-radius:8px;padding:9px 16px;font-size:13px;
  box-shadow:0 4px 20px rgba(0,0,0,.5);
}

/* ── Panels ────────────────────────────────────────────────────────────── */
.panel{border:1px solid var(--line);border-radius:8px;overflow:clip;background:var(--bg2)}
/* Panel heads take the stone plate, NOT the banner strip the tabbar wears — two
   different pieces of chrome should not read as the same one. Left at its native
   size rather than stretched to the head's height, so it stays a broad mottle
   instead of tiling into a visible pattern. */
.panel-head{
  padding:8px 16px;font-size:21px;font-weight:600;
  display:flex;justify-content:space-between;align-items:center;gap:10px;
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg2) 82%,transparent),color-mix(in srgb,var(--bg2) 82%,transparent)),
    url('assets/sidebar-background-texture1.png');
  background-size:auto,auto;background-position:left center;background-repeat:repeat;
}
.panel-head .sub{font-size:12px;font-weight:400;opacity:.85}
.panel-body{
  padding:10px 12px 12px;
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg1) 60%,transparent),color-mix(in srgb,var(--bg1) 60%,transparent)),
    url('assets/panel-body-bg.png');
  background-size:contain 100%;background-position:left center;background-repeat:repeat;
}
.hint{color:var(--text-dim);opacity:.75;font-size:12px;margin:4px 0 10px}

/* ── Camp ──────────────────────────────────────────────────────────────── */
/* Two columns, both full height, each scrolling its own body. */
.camp-grid{
  display:grid;grid-template-columns:minmax(0,1.25fr) minmax(0,1fr);gap:14px;
  align-items:stretch;flex:1;min-height:0;
}
.camp-grid > .panel{display:flex;flex-direction:column;min-height:0}
.camp-grid > .panel > .panel-body{flex:1;min-height:0;overflow-y:auto}
/* Stacked, the two panels cannot each own a slice of the height — the second
   would simply be below the fold with nothing to scroll it. So when they stack,
   the grid scrolls as one and the panels go back to their natural height. */
/* Camp is one tabbed panel and one fixed column. The tabs carry the three prep
   decisions; the column carries the quest you are making them against and the
   button that ends the screen. The tab panel is the WIDER of the two because it
   holds the meal table, which needs three columns of its own. */
.tabs-panel{display:flex;flex-direction:column;min-height:0}
.tab-head{padding:0;gap:0}
.ptab{
  flex:1;appearance:none;background:transparent;border:0;cursor:pointer;
  color:inherit;font:inherit;font-size:15px;font-weight:600;
  padding:9px 10px;opacity:.55;border-bottom:2px solid transparent;
}
.ptab:hover{opacity:.85}
.ptab.active{opacity:1;border-bottom-color:var(--accent-hover)}
/* Only the live pane is laid out. The rest keep their DOM so nothing re-renders
   on a tab press and the locale list holds its scroll position. */
.tabs-panel .pane{display:none}
.tabs-panel .pane.active{display:flex;flex-direction:column;flex:1;min-height:0}
#paneLocale{overflow-y:auto}
/* One panel per rung. A rung is a set of quests that open together and are
   cleared together, so it reads as a thing rather than as a heading with rows
   loose underneath it — and the rung you are ON is the one that gets the accent. */
.rung-panels{display:grid;gap:12px}
.rung-panel{background:var(--bg2)}
.rung-head{
  font-size:14px;font-weight:600;padding:7px 12px;
  display:flex;justify-content:space-between;align-items:baseline;gap:10px;
}
.rung-head .rmeta{font-size:11.5px;font-weight:400;opacity:.75;white-space:nowrap}
.rung-panel.current{border-color:color-mix(in srgb,var(--accent-hover) 55%,var(--line))}
.rung-panel > .panel-body{padding:8px 10px 10px}
#paneCanteen .meal-wrap{max-height:none;flex:1;min-height:0}
#paneCanteen .mealstats,#paneCanteen .mealfresh,#paneCanteen .hint{flex:none}
#paneCanteen .mealstats{margin:0 0 2px}
#paneCanteen .hint{margin:0 0 8px}
/* The Items pane is itself two tabs, so its own subtabs sit above a pane that
   takes the rest and scrolls. */
#paneItems .subtabs{flex:none;margin:0 0 8px}
#paneItems .subtab{display:flex;align-items:center;gap:8px}
#paneItems .subtab .slots{font-size:11.5px;font-weight:400;opacity:.8}
#paneItems .subpane{display:none}
#paneItems .subpane.active{display:block;flex:1;min-height:0;overflow-y:auto}

/* The right column is a stack: Quest takes the six lines it needs and Services
   the little it needs, both fixed, so the quest never moves while you work. */
.camp-right{display:flex;flex-direction:column;gap:14px;min-height:0}
.camp-right > .panel{display:flex;flex-direction:column;min-height:0}
.camp-right > .quest-panel,
.camp-right > .services-panel{flex:0 0 auto}
.camp-right > .quest-panel > .panel-body,
.camp-right > .services-panel > .panel-body{flex:none;min-height:auto;overflow:visible}
/* The slot count rides in the sub-tab now that the pouch owns a tab. */
.panel-head .slots{margin-left:auto;font-size:12px;font-weight:400;opacity:.85}

@media (max-width:900px){
  .camp-grid{grid-template-columns:minmax(0,1fr);overflow-y:auto;align-items:start}
  .camp-grid > .panel{min-height:auto}
  .camp-right{min-height:auto}
  .camp-right > .panel{min-height:auto}
  .camp-grid > .panel > .panel-body{flex:none;min-height:auto;overflow:visible}
  .tabs-panel{min-height:auto}
  .tabs-panel .pane.active{flex:none;min-height:auto}
  #paneLocale{overflow:visible}
  #paneCanteen .meal-wrap{max-height:34vh;flex:none}
  #paneItems .subpane.active{flex:none;min-height:auto;overflow:visible}
  .depart{position:sticky;bottom:0}
}

.locale-list{list-style:none;margin:0;padding:0;display:grid;gap:5px}
.locale-list li{
  display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:10px;
  padding:7px 10px;border:1px solid var(--line);border-radius:7px;
  background:var(--card);cursor:pointer;
}
.linfo{min-width:0}
.ltags{display:flex;gap:4px;flex-wrap:wrap;justify-content:flex-end}
.locale-list li:hover{border-color:var(--accent-hover)}
.locale-list li.sel{border-color:var(--accent-hover);background:var(--hover)}
.locale-list li.locked{opacity:.4;cursor:not-allowed}
.locale-list li.rung{
  display:flex;justify-content:space-between;align-items:baseline;gap:8px;
  background:transparent;border:none;padding:8px 2px 3px;cursor:default;
  font-size:11px;text-transform:uppercase;letter-spacing:.8px;opacity:.6;
}
.locale-list li.rung:first-child{padding-top:0}
.locale-list li.rung.current{opacity:1;color:var(--accent-hover)}
.locale-list li.rung .rmeta{text-transform:none;letter-spacing:0;font-size:11px;opacity:.85}
.locale-list li.done .lname{opacity:.75}
.tag.done{color:var(--sta);border-color:var(--line)}
.lname{font-weight:600}
.lmeta{font-size:11px;opacity:.75;display:block;margin-top:1px}
.tag{font-size:10px;padding:1px 6px;border-radius:4px;border:1px solid var(--line);white-space:nowrap;background:#0000004d}
/* Cold, Hot and Danger were three fixed hexes that took no notice of the theme —
   the same problem the quest summary had. All three now read in --money, so they
   match the summary lines they are warning about and stay legible on every
   swatch, including the light one where --money flips to dark ink. */
.tag.hot,.tag.cold,.tag.boss{color:var(--money);font-weight:600}

.field{display:flex;flex-direction:column;gap:4px;font-size:12px;margin-bottom:10px}
.field>span{text-transform:uppercase;letter-spacing:.5px;opacity:.75}
select{
  background:var(--bg2);color:var(--text);border:1px solid var(--line);
  border-radius:7px;padding:8px 10px;font-size:14px;line-height:1.4;width:100%;
}
.desc{font-size:12px;opacity:.8;margin:0 0 12px;display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.desc img{width:22px;height:22px;object-fit:contain;image-rendering:pixelated}

.pouch{list-style:none;margin:0;padding:0;display:grid;gap:4px}
.pouch li{display:grid;grid-template-columns:24px minmax(0,1fr) auto 76px;align-items:center;gap:8px;
  padding:5px 8px;background:var(--card);border:1px solid var(--line);border-radius:6px;font-size:13px}
/* Packed. The row used to differ only by its button reading Remove, which is a
   lot to ask of someone scanning eleven rows for what they are actually taking.
   --accent-hover rather than --accent: the theme derives accent DARK on dark
   themes, where it would vanish into the card it is meant to stand out from. */
.pouch li.packed{
  border-color:color-mix(in srgb,var(--accent-hover) 55%,var(--line));
  background:color-mix(in srgb,var(--accent-hover) 13%,var(--card));
  box-shadow:inset 3px 0 0 var(--accent-hover);
}
.pouch li.packed .qty{font-weight:600}
.pouch li.nofit{opacity:.45}
/* Slot held, but you have run out — it keeps its place and refills when you restock. */
.pouch li.dry .qty{color:var(--money);opacity:.9}
.pouch li.empty{display:block;opacity:.6;font-size:12px;padding:7px}
.slotrow{flex-direction:row;align-items:baseline;justify-content:space-between;margin-bottom:5px}
.slots{font-size:12px;font-variant-numeric:tabular-nums;opacity:.85}
.pouch img{width:24px;height:24px;object-fit:contain;image-rendering:pixelated}
.pouch .role{font-size:10px;opacity:.7;text-transform:uppercase;letter-spacing:.4px;display:block}
.pouch .qty{font-variant-numeric:tabular-nums;min-width:44px;text-align:right;opacity:.9}
.pouch .stepper{display:flex;gap:3px}
.pouch li>.btn.tiny{width:100%}
.pouch .stepper .btn{padding:1px 8px;font-size:14px;line-height:1.3}

/* Pinned under the Loadout panel rather than sitting at the end of its scroll —
   the one button the whole screen exists to press should not need finding. */
.depart{
  flex:0 0 auto;display:grid;gap:8px;
  padding:10px 12px 12px;border-top:1px solid var(--line);
  background:var(--bg2);
}
/* Its own panel now, rather than a block leading the Loadout. The separating
   rule and the space under it went with the move — a panel already ends where it
   ends, and the border was drawing a second line just inside the real one. */
.depart-summary{
  font-size:14.5px;line-height:1.45;display:grid;gap:4px;align-content:start;
  margin:0;
}
/* Locale and Rank are rows like everything else now, so nothing in the block
   wraps and the height no longer needs reserving against a long locale name. */
/* The lightest green that still clears 4.5:1 on the sand. A brighter one reads
   better as "green" but sand is a light warm ground and green sits close to it —
   #22CC44, the Randomizer's own green pill, manages 1.9:1 here. */
.depart-summary .goal{color:#146023;font-weight:700}
/* Fixed label column, so the readings line up down the block instead of each
   starting wherever its own label happens to end. */
.depart-summary .line{display:grid;grid-template-columns:126px minmax(0,1fr);gap:8px;align-items:baseline}
.depart-summary .k{opacity:.65}
/* ── The quest card ────────────────────────────────────────────────────────
   Sand and brown ink, taken from the Quest Randomizer's result card, which is
   itself the game's own quest card. Sampled from that PNG rather than guessed:
   #E0D0B0 is its most common pixel and the point samples across the paper sit
   around #DED4B5.

   Fixing the ground is what earns the colours below. The readings used to be
   painted in --money — one tint for all three — because a theme-derived colour
   was the only thing guaranteed to stay legible on a ground that changed with
   the palette. On a fixed sand they can say what they actually mean: red for a
   large monster, blue for small ones, orange for heat, teal for cold. */
/* The HEAD is left alone — it stays the theme's stone plate like every other
   panel head in the app, so the chrome still reads as one set. Only the card
   inside it is sand. */
.quest-panel .panel-body{
  --sand:#DFD4B4;
  --ink:#4D391F;
  color:var(--ink);
  background-image:
    linear-gradient(var(--sand),var(--sand)),
    url('assets/panel-body-bg.png');
}
.depart-summary .k{color:var(--ink);opacity:.78;font-weight:700}
.depart-summary .warn{font-weight:700}
/* Standard colours, darkened from the Randomizer's pills only where the pill hex
   itself would not clear 4.5:1 on sand — a pill has white text on colour, this is
   colour on paper, and the two do not want the same value. */
.depart-summary .danger{color:#B01E15}   /* large monster */
.depart-summary .pests{color:#14528F}    /* small monsters */
.depart-summary .hot{color:#8C3F00}      /* elevated temperature */
.depart-summary .cold{color:#0D5A72}     /* lower temperature */

/* ── Quest ─────────────────────────────────────────────────────────────── */
.quest-head{display:flex;align-items:center;gap:18px;flex-wrap:wrap;margin-bottom:14px}
/* ONE grid across both gauges, not a grid each: separate grids sized their own
   number column to their own text, so "100 / 100" and "31 / 148" gave the two
   bars different lengths. `display:contents` drops the rows into the shared
   grid so all three columns line up.
   The block is also a FIXED share of the head rather than flex:1, so the locale
   name, the climate and danger pills and the bait can grow and wrap without
   shortening the bars. */
.gauges{
  display:grid;grid-template-columns:64px minmax(0,1fr) 72px;
  gap:5px 8px;align-items:center;font-size:12px;
  flex:0 0 clamp(300px,46%,620px);min-width:0;
}
.gauge{display:contents}
.gauge span{text-align:right}
.gauge label{text-transform:uppercase;letter-spacing:.5px;opacity:.75}
.gauge span{font-variant-numeric:tabular-nums;opacity:.85}
.bar{height:11px;background:var(--bg1);border-radius:6px;overflow:hidden;border:1px solid var(--line)}
.bar.wide{width:220px}
.bar i{display:block;height:100%;width:100%;transition:width .18s}
.hp .bar i{background:var(--gauge-hp)}
.sta .bar i{background:var(--gauge-sta)}

/* Unprotected in a cold or hot locale, the gauge the climate is eating pulses
   toward that climate's colour and back. It is the bar itself that flashes, so
   the warning sits exactly where the damage is being done — and it stops the
   moment a drink goes down, which is the feedback the drink never had. */
@keyframes chill  { 50% { background:var(--chill);  } }
@keyframes scorch { 50% { background:var(--scorch); } }
.sta .bar i.chill  { animation:chill  1.5s ease-in-out infinite }
.hp  .bar i.scorch { animation:scorch 1.5s ease-in-out infinite }
@media (prefers-reduced-motion:reduce){
  .sta .bar i.chill { animation:none; background:var(--chill) }
  .hp  .bar i.scorch{ animation:none; background:var(--scorch) }
}
.quest-meta{display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:13px}
.climate{font-size:11px;padding:1px 7px;border-radius:4px;border:1px solid var(--line);background:#0000004d}
.climate.hot{color:#f0a070}.climate.cold{color:#8fd0e8}

.quest-body{
  display:grid;grid-template-columns:minmax(0,1fr) 320px;gap:14px;align-items:stretch;
  flex:1;min-height:0;overflow:hidden;
}
/* A long haul scrolls INSIDE the pane. The body used to scroll instead, which
   dragged the water and the gauges off the top of the screen mid-cast. */
.quest-side{min-height:0;display:flex}
.side-pane{display:flex;flex-direction:column;min-height:0;flex:1}
.side-head{flex:none;flex-wrap:wrap;row-gap:6px}
.side-head .subtabs{margin-bottom:0;flex:1 1 auto;gap:2px}
/* Five tabs in a 320px column is a near thing: at the usual 4px gap and 10px
   padding they came to 294px inside a 286px strip and wrapped by eight pixels.
   Tighter here only, so the wider tab strips elsewhere keep their spacing. */
.side-head .subtabs .subtab{padding:4px 8px}
.side-body{overflow-y:auto;min-height:0;flex:1}
.side-view{display:none}
.side-view.active{display:block}
@media (max-width:900px){.quest-body{grid-template-columns:minmax(0,1fr)}}

/* Three fixed rows: the button, the pond, the foot. The pond sits in the same
   place in every state because the rows above and below it never change height —
   which they did when hiding something removed it from the layout entirely. */
.water{
  border:1px solid var(--line);border-radius:8px;min-height:390px;
  display:grid;grid-template-rows:auto auto auto;gap:14px;
  justify-items:center;align-content:center;padding:24px;
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg1) 55%,transparent),color-mix(in srgb,var(--bg2) 75%,transparent)),
    url('assets/panel-body-bg.png');
  background-size:contain 100%;background-position:left center;background-repeat:repeat;
}
.cast-area{text-align:center;display:grid;place-items:center;min-height:42px}
.cast-prompt{opacity:.85;margin:0;min-height:1.4em;text-align:center}

/* Inside the water, HIDING MUST NOT MOVE ANYTHING. `.hidden` is display:none,
   which takes an element out of the layout and drags the pond up behind it; here
   it only needs to stop being seen. The id beats .hidden's !important. */
#water .cast-area.hidden,#water .reel.hidden,#water .tension.hidden{
  display:grid !important;visibility:hidden;
}

/* The foot holds the bars and the message in ONE cell, so whichever is showing,
   the pond above it has not moved. Its height is FIXED, not a minimum: the water
   centres its rows, so a foot that grew by a line would re-centre the lot and
   carry the pond up with it. Sized for a hint that wraps to two lines; a longer
   one clips rather than moves anything. */
.water-foot{width:100%;max-width:520px;display:grid;height:148px;align-content:start;overflow:hidden}
.water-foot > *{grid-area:1 / 1}
/* Only as tall as its own line. Stretched to fill the cell it would sit over the
   bars and swallow anything aimed at them. */
.cast-prompt{align-self:start}

.reel{width:100%;display:grid;gap:10px;justify-items:center}

/* Meals as a table. What a meal gives and what it costs are the two things you
   compare, and a dropdown could only ever show them as one run-on line. */
.meal-wrap{max-height:34vh;overflow-y:auto;border:1px solid var(--line);border-radius:8px;background:var(--bg1)}
.meal-table{width:100%;border-collapse:collapse;font-size:12.5px}
.meal-table td{padding:4px 6px;vertical-align:middle}
/* Scoped to tbody, all of it — the header row is not a meal and must not take a
   pointer, a hover or the row rule. */
.meal-table tbody tr{cursor:pointer;border-bottom:1px solid color-mix(in srgb,var(--line) 55%,transparent)}
.meal-table tbody tr:last-child{border-bottom:0}
.meal-table tbody tr:hover{background:#ffffff10}
.meal-table tbody tr.sel{background:color-mix(in srgb,var(--money) 18%,transparent)}
.meal-table tbody tr.sel .nm{font-weight:700}
.meal-table tbody tr.cant{opacity:.4;cursor:not-allowed}
/* Floating header. The rule under it is a box-shadow rather than a border,
   because border-collapse drops the borders of a sticky cell and the line
   scrolls away with the rows instead of staying put. */
.meal-table thead th{
  position:sticky;top:0;z-index:2;
  background:var(--bg2);
  padding:6px;font-size:10px;font-weight:700;
  text-transform:uppercase;letter-spacing:.55px;
  /* Dim the INK, never the element. opacity here fades the background with the
     text, and rows then show through the header as they scroll under it. */
  color:color-mix(in srgb,var(--text) 78%,transparent);
  text-align:right;white-space:nowrap;
  box-shadow:inset 0 -1px 0 var(--line);
}
.meal-table thead th:first-child{text-align:left}
/* The name gives way, not the price — what a meal costs is half of what you are
   comparing, and a greedy nowrap name was pushing it out of the panel. */
.meal-table .nm{white-space:normal;line-height:1.25}
.meal-table .fr{display:block;font-size:10.5px;opacity:.95;margin-top:1px;white-space:normal}
.meal-table .n{text-align:right;font-variant-numeric:tabular-nums;width:1%;white-space:nowrap;opacity:.85}
.meal-table .pr{text-align:right;color:var(--money);font-variant-numeric:tabular-nums;width:1%;white-space:nowrap}

.mealstats{margin:6px 0 2px;font-size:12.5px;opacity:.9;font-variant-numeric:tabular-nums}
.mealfresh{margin:0 0 6px;font-size:12px;opacity:.95;display:flex;align-items:center;gap:6px;flex-wrap:wrap}
.mealfresh:empty{display:none}
.fresh-tag{
  font-size:9.5px;text-transform:uppercase;letter-spacing:.6px;font-weight:700;
  color:var(--money);border:1px solid var(--money);border-radius:4px;padding:0 5px;
}
.ingr-list li.fresh .dot{background:var(--money);box-shadow:0 0 0 2px color-mix(in srgb,var(--money) 30%,transparent)}
.haul li.ingr.fresh .v{color:var(--money);font-weight:600}
.mealstats b{font-weight:600}
.field.hire{align-items:center;cursor:pointer;margin-bottom:4px}
/* .field>span uppercases the label, which was also shouting the fee's "z".
   Matched at the same specificity to actually win it back. */
.hire-right{display:flex;align-items:center;gap:10px}
.field>span.hire-right{text-transform:none}
.hire-right .money{color:var(--money);font-variant-numeric:tabular-nums}
.hire-right input{width:16px;height:16px;accent-color:var(--money);cursor:pointer}
.field.hire.cant .money{color:var(--danger)}

/* Status: what is ticking down, in one place. */
.buffs{display:flex;flex-wrap:wrap;gap:5px;align-items:center;min-height:20px}
.buffs .none{font-size:11.5px;opacity:.5}
.buff{
  font-size:11px;font-weight:600;letter-spacing:.3px;white-space:nowrap;
  background:var(--card);border:1px solid var(--line);border-radius:5px;padding:1px 7px;
  font-variant-numeric:tabular-nums;
}
.buffs .exposed{
  font-style:normal;font-size:11px;font-weight:600;letter-spacing:.3px;
  color:var(--money);border:1px solid var(--money);border-radius:5px;padding:1px 7px;
}

/* ── The pond ─────────────────────────────────────────────────────────────
   Bird's-eye water. Everything inside is positioned by percentage, so the
   surface can be any size and the fishing maths stays in pond-fractions. */
.pond{
  /* --water is set per locale from JS; the fallback is only ever used if the
     pond is somehow shown outside a trip. Shallows and depths are both mixed
     from that one colour, so a locale is tuned by changing a single hex. */
  --water:#2E7D6B;
  position:relative;width:100%;aspect-ratio:16/10;max-height:52vh;
  border:1px solid var(--line);border-radius:10px;overflow:hidden;
  background:
    radial-gradient(120% 90% at 50% 12%, color-mix(in srgb,var(--water) 46%,transparent), transparent 72%),
    linear-gradient(180deg,
      color-mix(in srgb,var(--water) 62%,#000 18%),
      color-mix(in srgb,var(--water) 34%,#000 46%));
  /* Nothing in the water is clickable — the pond is arrows and Space — so the
     pointer is only ever something sat on top of the fish you are watching. */
  cursor:none;
}
/* Two moving layers, so the water reads as a body rather than a painted plate.
   Both are cosmetic and neither is hit-tested — the fish are the only thing in
   here you can interact with.

   BENEATH the fish: a slow swell. Two soft lobes of the locale's own colour,
   one lifted toward white and one sunk toward black, drifting and breathing on
   a long alternating cycle. This is what gives the surface depth.

   ABOVE the fish: the flecks, now drifting. Three tiles at different sizes
   moving different distances in different directions, which is enough parallax
   to read as current without ever showing a repeat. Keeping them over the fish
   is deliberate — the fish are under the water, so the surface passes across
   them.

   The timings are deliberately long. Water that hurries looks like static. */
.pond::before{
  content:"";position:absolute;inset:-25%;pointer-events:none;opacity:.34;
  background:
    radial-gradient(38% 52% at 32% 38%, color-mix(in srgb,var(--water) 62%,#fff) 0%, transparent 62%),
    radial-gradient(44% 42% at 71% 66%, color-mix(in srgb,var(--water) 60%,#000) 0%, transparent 66%);
  animation:pond-swell 23s ease-in-out infinite alternate;
}
.pond::after{
  content:"";position:absolute;inset:0;pointer-events:none;opacity:.22;
  background-image:
    radial-gradient(circle at 22% 30%, color-mix(in srgb,var(--water) 60%,#fff) 0 1px, transparent 2px),
    radial-gradient(circle at 71% 66%, color-mix(in srgb,var(--water) 60%,#fff) 0 1px, transparent 2px),
    radial-gradient(circle at 43% 82%, color-mix(in srgb,var(--water) 60%,#fff) 0 1px, transparent 2px);
  background-size:180px 180px,220px 220px,260px 260px;
  animation:pond-drift 48s linear infinite;
}
@keyframes pond-swell{
  from{transform:translate3d(-3%,-2%,0) scale(1.00)}
  to  {transform:translate3d(4%,3%,0)   scale(1.09)}
}
@keyframes pond-drift{
  from{background-position:0 0, 0 0, 0 0}
  to  {background-position:180px 180px, -220px 220px, 260px -260px}
}
/* Anyone who has asked the OS for less movement gets the still pond back. */
@media (prefers-reduced-motion:reduce){
  .pond::before,.pond::after,.bubbles i,.wake,.bobber::before,.bobber::after{animation:none}
  .bubbles i,.bobber::before,.bobber::after{opacity:0}
}

.swimmer{
  position:absolute;transform:translate(-50%,-50%);
  transition:opacity .25s;pointer-events:none;line-height:0;
}
.swimmer img{image-rendering:pixelated;display:block;filter:drop-shadow(0 2px 3px rgba(0,0,0,.45))}
.swimmer.flip img{transform:scaleX(-1)}
.swimmer.nibble{animation:nibble .22s ease-in-out}
@keyframes nibble{50%{transform:translate(-50%,-50%) scale(1.28)}}
.swimmer.gone{opacity:0}
.swimmer.biting img{filter:drop-shadow(0 0 6px var(--money))}
.swimmer.monster img{width:64px;height:64px}

/* The bobber is the theme's own money colour, and goes grey when it dips. */
/* Bubbles. Built in JS so each gets its own size, lane, pace and phase — the
   shared bits live here. They rise and fade rather than travelling far, which is
   what a bubble looks like from directly above. */
.bubbles{position:absolute;inset:0;pointer-events:none;overflow:hidden}
.bubbles i{
  position:absolute;border-radius:50%;opacity:0;
  background:radial-gradient(circle at 35% 32%,
    color-mix(in srgb,var(--water) 30%,#fff) 0%,
    color-mix(in srgb,var(--water) 70%,#fff) 55%,
    transparent 72%);
  animation-name:bubble;animation-timing-function:ease-in-out;
  animation-iteration-count:infinite;
}
@keyframes bubble{
  0%  {opacity:0;   transform:translate3d(0,6px,0)   scale(.55)}
  22% {opacity:.55}
  70% {opacity:.42}
  100%{opacity:0;   transform:translate3d(3px,-16px,0) scale(1.25)}
}

/* The wake: one mark dropped per throttled frame while the bobber is travelling,
   left where it WAS and fading in place. Removes itself on animationend. */
.wake{
  position:absolute;width:9px;height:9px;border-radius:50%;
  transform:translate(-50%,-50%);pointer-events:none;
  background:color-mix(in srgb,var(--money) 45%,transparent);
  animation:wake .55s ease-out forwards;
}
@keyframes wake{
  from{opacity:.5;  transform:translate(-50%,-50%) scale(.7)}
  to  {opacity:0;   transform:translate(-50%,-50%) scale(1.9)}
}

.bobber{
  position:absolute;width:16px;height:16px;border-radius:50%;
  transform:translate(-50%,-50%);background:var(--money);
  border:2px solid rgba(0,0,0,.45);
  box-shadow:0 0 0 3px color-mix(in srgb,var(--money) 25%,transparent);
  transition:background .12s, box-shadow .12s, width .1s, height .1s;
}
/* Sitting still, it works rings out across the water. Two of them, half a cycle
   apart, so one is always opening. They stop while it travels — a float being
   dragged does not ring, it leaves a wake, and running both at once reads as
   noise. */
.bobber::before,.bobber::after{
  content:"";position:absolute;left:50%;top:50%;width:100%;height:100%;
  border-radius:50%;pointer-events:none;
  border:1.5px solid color-mix(in srgb,var(--money) 55%,transparent);
  animation:bobber-ring 2.6s ease-out infinite;
}
.bobber::after{animation-delay:1.3s}
.bobber.travelling::before,.bobber.travelling::after{animation-play-state:paused;opacity:0}
@keyframes bobber-ring{
  from{opacity:.6; transform:translate(-50%,-50%) scale(1)}
  to  {opacity:0;  transform:translate(-50%,-50%) scale(3.6)}
}
.bobber.nudged{width:20px;height:20px}
.bobber.under{
  background:#8b9299;
  box-shadow:0 0 0 6px rgba(139,146,153,.22);
  animation:dip .3s ease-in-out infinite alternate;
}
@keyframes dip{to{width:11px;height:11px}}
.bobber.pulling{width:22px;height:22px}

/* Fixed rows for the same reason: strike bar, track, progress, escape. Every one
   is always there and always the same height, so nothing below or above it moves
   when the escape bar starts filling. */
.tension{width:100%;max-width:520px;display:grid;gap:8px;
         grid-template-rows:14px auto 12px 7px}

/* The strike window: one bar that empties. Only on screen while the fish is on
   the hook and you have not pulled yet. */
.strike-bar{height:14px;background:var(--bg1);border:1px solid var(--money);border-radius:999px;overflow:hidden}
.strike-bar i{display:block;height:100%;width:0;background:var(--money)}
/* Reserved, not removed. The strike bar coming and going changed the height of
   the whole tension block, which moved the pond above it. */
.tension:not(.strike) .strike-bar{visibility:hidden}
.tension.strike .tension-track,.tension.strike .progress{display:none}

/* The line: a pill that starts half full and falls on its own. Both ends lose, but
   they lose in opposite ways, so they are not the same colour: drift left and the
   line goes SLACK (t <= 0), drift right and it SNAPS (t >= 1). Blue for too little
   tension, red for too much — the colour tells you which way to correct without
   reading anything. Clear through the middle, where the marker is what you steer. */
.tension-track{
  position:relative;height:22px;border:1px solid var(--line);border-radius:999px;overflow:hidden;
  background:linear-gradient(90deg,
    var(--slack) 0%, color-mix(in srgb,var(--slack) 35%,var(--bg1)) 14%,
    var(--bg1) 34%, var(--bg1) 66%,
    color-mix(in srgb,var(--danger) 35%,var(--bg1)) 86%, var(--danger) 100%);
}
/* The stretch where the line actually holds. */
.tension-track .band{
  position:absolute;top:0;bottom:0;left:34%;width:32%;
  background:color-mix(in srgb,var(--gauge-hp) 26%,transparent);
  border-left:1px solid color-mix(in srgb,var(--gauge-hp) 70%,transparent);
  border-right:1px solid color-mix(in srgb,var(--gauge-hp) 70%,transparent);
}
.tension-track .pill{
  position:absolute;top:2px;bottom:2px;left:50%;width:13px;margin-left:-6.5px;
  border-radius:999px;background:var(--money);border:1px solid rgba(0,0,0,.5);
  box-shadow:0 0 0 2px color-mix(in srgb,var(--money) 30%,transparent);
}
/* Outside the stretch you are gaining nothing and heading for a loss. Which loss
   depends on the side, and the marker says so: blue below the band, red above it.
   A red pill while the line is going slack would point you the wrong way. */
.tension-track.out .pill{background:var(--danger);box-shadow:0 0 0 3px color-mix(in srgb,var(--danger) 35%,transparent)}
.tension-track.out{border-color:var(--danger)}
.tension-track.out.low .pill{background:var(--slack);box-shadow:0 0 0 3px color-mix(in srgb,var(--slack) 35%,transparent)}
.tension-track.out.low{border-color:var(--slack)}

/* Bracing. The fight is frozen, so the track goes still and dim and the hint
   line does the talking; the point is that nothing you do to the bar matters
   for a moment, only the key. */
.tension.bracing .tension-track{opacity:.45;filter:saturate(.4)}
/* reelHint is a SIBLING of the wrap, not a child. */
.tension.bracing~.reel-hint,.tension.braced~.reel-hint,.tension.hit~.reel-hint{font-weight:700}
.tension.bracing~.reel-hint{color:var(--warn,#d08a3a)}
.tension.braced~.reel-hint{color:var(--good,#4fbf8b)}
.tension.hit~.reel-hint{color:var(--danger)}
.tension.bracing .tension-track{border-color:var(--warn,#d08a3a)}
.tension.braced .tension-track{border-color:var(--good,#4fbf8b);opacity:.8;filter:none}
.tension.hit .tension-track{border-color:var(--danger);opacity:.8;filter:none;animation:braceHit .28s ease-out}
@keyframes braceHit{
  0%{transform:translateX(0)}25%{transform:translateX(-5px)}
  55%{transform:translateX(4px)}100%{transform:translateX(0)}
}
@media (prefers-reduced-motion: reduce){
  .tension.hit .tension-track{animation:none}
}

.progress{height:12px;background:var(--bg1);border:1px solid var(--line);border-radius:6px;overflow:hidden}
.progress i{display:block;height:100%;width:0;background:var(--gauge-hp)}
/* The fish's half of the race: fills whenever the line is outside the band, and
   takes the fish if it gets there first. Thinner than yours — it is the threat,
   not the thing you are steering. */
.progress.escape{height:7px;margin-top:-4px}
.progress.escape i{background:var(--danger)}
.reel-name{font-size:15px;min-height:1.2em}
.reel-hint{font-size:12px;opacity:.75;margin:0;text-align:center;min-height:1.2em}

.catch-list{list-style:none;margin:0;padding:0;display:grid;
  grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:2px}
.catch-list li{display:flex;justify-content:space-between;gap:8px;align-items:baseline;
  font-size:12.5px;padding:3px 7px;background:var(--card);border-radius:5px}
.catch-list .n{font-variant-numeric:tabular-nums;opacity:.75;font-size:11.5px}
.missing{font-size:11.5px;opacity:.7;margin:7px 0 0;line-height:1.4}
.missing b{opacity:.85}
.missing.done{color:var(--money);opacity:.9}

/* A locale's pool widens with rank, so Collectables splits it per rank rather
   than merging the three tables into one list. */
.rank-catch{margin:0 0 12px}
.rank-catch:last-child{margin-bottom:0}
.rank-head{display:flex;align-items:baseline;gap:8px;margin:0 0 6px;
  font-size:12px;font-weight:600;letter-spacing:.04em;text-transform:uppercase;
  opacity:.8;padding-bottom:4px;border-bottom:1px solid var(--line)}
.rank-head .cnt{margin-left:auto;font-size:11.5px;font-weight:400;
  opacity:.85;font-variant-numeric:tabular-nums;text-transform:none;letter-spacing:0}
.rank-catch .chips{display:flex;flex-wrap:wrap;gap:4px}
.rank-catch .chip{font-size:11.5px;padding:2px 8px;border-radius:999px;
  background:var(--card);border:1px solid transparent;opacity:.5}
.rank-catch .chip.has{opacity:1;border-color:var(--line);color:var(--money)}
.rank-catch + .rank-catch{margin-top:12px}
.panel-body .catch-list + .rank-catch{margin-top:14px}

.haul ul{list-style:none;margin:0;padding:0;display:grid;gap:4px;max-height:32vh;overflow-y:auto}
.haul li{display:grid;grid-template-columns:24px minmax(0,1fr) auto;align-items:center;gap:8px;
  font-size:12.5px;padding:4px 6px;background:var(--card);border-radius:5px}
.haul li img{width:24px;height:24px;object-fit:contain;image-rendering:pixelated}
.haul-value{color:var(--money)}
.haul-meta{display:flex;align-items:baseline;gap:10px;font-size:13px;font-weight:400}
#haulGoal{font-variant-numeric:tabular-nums;opacity:.75}
#haulGoal.met{color:var(--sta);opacity:1;font-weight:600}

/* The quest-side lists. These had NO rules of their own, so every row rendered
   at the icon's native size and the two pouches pushed the Haul off the bottom
   of the screen. Tighter than the camp pouch on purpose: out on the water these
   are a reference you glance at between casts, not something you are editing. */
.qpouch{list-style:none;margin:0;padding:0;display:grid;gap:3px}
.qpouch li{
  display:grid;grid-template-columns:20px minmax(0,1fr) auto auto;
  align-items:center;gap:7px;padding:3px 6px;font-size:12px;
  background:var(--card);border:1px solid var(--line);border-radius:5px;
}
.qpouch li.empty{display:block;opacity:.6;padding:5px 6px}
.qpouch img{width:20px;height:20px;object-fit:contain;image-rendering:pixelated;display:block}
.qpouch b{font-weight:600}
.qpouch .eff{font-size:10px;opacity:.65;display:block;line-height:1.25}
.qpouch .qty{font-variant-numeric:tabular-nums;opacity:.85;min-width:26px;text-align:right}
.qpouch li.on{border-color:var(--money)}
.qpouch .btn.tiny{padding:2px 8px;font-size:11px}

/* ── Guide ─────────────────────────────────────────────────────────────── */
.subtabs{display:flex;gap:4px;margin-bottom:14px}
.subtab{background:transparent;border:1px solid transparent;color:var(--text);opacity:.65;
  padding:4px 14px;border-radius:7px;cursor:pointer;font-size:13px}
.subtab:hover{opacity:1;background:#ffffff14}
.subtab.active{opacity:1;background:var(--bg2);border-color:var(--line);font-weight:600}
.subview{display:none}
.subview.active{display:block}

.guide-head{display:flex;align-items:center;gap:16px;flex-wrap:wrap;margin-bottom:12px}
.guide-progress{display:flex;align-items:center;gap:8px;font-size:13px}
.guide-progress .bar i{background:var(--accent-hover)}
.reach{font-size:11.5px;opacity:.65}
.ingr-list{list-style:none;margin:0;padding:0;display:grid;gap:2px}
.ingr-list li{display:grid;grid-template-columns:10px minmax(0,1fr) auto;align-items:center;gap:10px;
  padding:4px 8px;border-radius:5px;font-size:12.5px;opacity:.4}
.ingr-list li:nth-child(odd){background:var(--card)}
.ingr-list li.have{opacity:1}
.ingr-list .dot{width:8px;height:8px;border-radius:50%;background:var(--line)}
.ingr-list li.have .dot{background:var(--sta)}
.ingr-list .rk{font-size:10.5px;opacity:.7;white-space:nowrap}
.haul li.ingr{grid-template-columns:24px minmax(0,1fr) auto}
.haul li.ingr .dot{width:8px;height:8px;border-radius:50%;background:var(--sta);margin-left:8px}
.haul li.ingr .v{color:var(--sta);font-size:11px;opacity:.8}
.chk{display:flex;align-items:center;gap:8px;font-size:13px;cursor:pointer}
.chk input{accent-color:var(--accent);width:16px;height:16px;cursor:pointer}
.chk:hover{color:var(--accent-hover)}

.guide-grid{display:grid;gap:10px}
/* Collectables sections are rows in a list, not panels in their own right — at
   21px the heading overpowers the texture band it sits on. One rule for both
   grids: the fish rows had an override and the ingredient ones did not, so they
   were the only headings in the app still at full panel size. */
.guide-grid .panel-head{
  margin:0;display:flex;align-items:baseline;gap:8px;
  font-size:15px;font-weight:600;padding:5px 12px;
}
.guide-grid .panel-head .r{font-size:11px;opacity:.75;font-weight:400}
.guide-grid .panel-head .cnt{margin-left:auto;font-size:12px;opacity:.8;font-variant-numeric:tabular-nums}
.guide-grid .panel-body{padding:8px 10px}
.guide-row.exclusive .panel-head .r{color:var(--danger);opacity:1}
.variants{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:4px}
@media (max-width:900px){.variants{grid-template-columns:repeat(6,minmax(0,1fr))}}
.variant{display:grid;place-items:center;gap:1px;padding:5px 2px;background:var(--card);
  border:1px solid transparent;border-radius:6px;text-align:center}
.variant.caught{border-color:var(--line)}
.variant:not(.caught){opacity:.25}
.variant:not(.caught) img{filter:grayscale(1)}
.variant img{image-rendering:pixelated}
.variant .vn{font-size:9.5px;opacity:.7;line-height:1.2}
.variant.caught .vn{opacity:1}

/* ── Shop ──────────────────────────────────────────────────────────────── */
/* Wide content scrolls inside its own box rather than pushing the page sideways. */
.table-wrap{overflow-x:auto}
.shop-table{width:100%;border-collapse:collapse;font-size:13px}
.shop-table td,.shop-table th{padding:5px 8px;text-align:left;vertical-align:middle}
.shop-table tr.sect th{
  font-size:12px;text-transform:uppercase;letter-spacing:.8px;font-weight:600;
  padding:14px 8px 5px;border-bottom:1px solid var(--line);
}
.shop-table tr.sect:first-child th{padding-top:2px}
.shop-table tr.sect .sub{text-transform:none;letter-spacing:0;font-weight:400;opacity:.7;font-size:11.5px}
.shop-table tbody tr:not(.sect):hover{background:#ffffff0f}
.shop-table .ic{width:30px;padding-right:0}
.shop-table .ic img{width:26px;height:26px;object-fit:contain;display:block;image-rendering:pixelated}
.shop-table .nm{font-weight:600;white-space:nowrap}
/* The buttons flex INSIDE the cell — flexing the <td> itself drops it out of the
   table layout and collapses every column in the row. */
.shop-table .buys{display:inline-flex;gap:4px}
.shop-table .dt{opacity:.72;font-size:12px}
.shop-table .dt .unlock{opacity:.72;font-size:11px;white-space:nowrap;margin-left:6px}
.shop-table .dt .unlock::before{content:"· "}
.shop-table .pr{color:var(--money);text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap}
.shop-table .hv{text-align:right;opacity:.75;font-variant-numeric:tabular-nums;white-space:nowrap}
.shop-table .bt{text-align:right;width:1%;white-space:nowrap}

/* The Smithy's extra column: what a piece is made of. It gets its own space
   because "which monster do I still have to find?" is the only question this
   screen is really asked, and a column answers it down the page in one look. */
.smithy-table .mt{white-space:nowrap;font-size:12px;width:1%}
.smithy-table .mt .mat{display:inline-flex;align-items:center;gap:5px}
.smithy-table .mt .mat img{width:18px;height:18px;object-fit:contain;image-rendering:pixelated}
.smithy-table .mt .mat b{font-variant-numeric:tabular-nums;opacity:.85}
.smithy-table .mt .mat i{opacity:.7}
.smithy-table .mt .held{opacity:.55;font-size:11px}
/* Being short is the state worth catching from across the table, so it is the
   one that takes a colour. Having enough simply reads normally. */
.smithy-table .mt .short .held{color:#e2907f;opacity:.95}
.smithy-table .mt .ok .held{color:#7fd18d;opacity:.95}
/* What you are wearing is marked by the row itself rather than a word beside the
   name — you should be able to find it without reading. */
.smithy-table tr.worn{background:color-mix(in srgb,var(--accent) 22%,transparent)}
.smithy-table tr.worn:hover{background:color-mix(in srgb,var(--accent) 30%,transparent)}
.smithy-table tr.worn .nm{font-weight:700}
.smithy-table tr.worn .ic{box-shadow:inset 3px 0 0 var(--accent-hover)}
.smithy-table .none{opacity:.35}
/* Skill name and skill description are two columns of STACKED entries, one per
   skill. Both use the same entry height so the two stacks stay in step — without
   it a three-line description would push its own column out of alignment with the
   name beside it. */
.smithy-table .sk,.smithy-table .dt,.smithy-table .st{vertical-align:top;padding-top:7px}
/* HP, Stamina and DEF as a list of their own rather than a run-on line tacked to
   the end of the descriptions — where it made a six-skill piece show six names
   against seven description entries, and broke the pairing the two columns are
   for. Sized to content and never wrapped: three short rows of figures. */
.smithy-table .st{white-space:nowrap;width:1%;font-size:12px}
/* Same 2.6em step as the skill and description entries, so all three columns
   share one rhythm and a row reads as rows rather than three lists that happen
   to sit side by side. */
.smithy-table .st .ent{display:flex;gap:5px;align-items:center;
                       min-height:2.6em;line-height:1.3;opacity:.85}
.smithy-table .st .ent b{font-variant-numeric:tabular-nums;font-weight:600;
                         min-width:3.2em;text-align:right}
/* What one more level buys, beside the figure it would change. */
.smithy-table .st .gain{font-style:normal;font-size:11px;opacity:.6}
@media (max-width:640px){.shop-table .st{display:none}}
.smithy-table .sk{white-space:nowrap;width:1%;font-size:12px}
.smithy-table .dt{white-space:normal;max-width:44ch}
.smithy-table .sk .ent,.smithy-table .dt .ent{
  display:flex;align-items:center;min-height:2.6em;line-height:1.3;
}
/* Armor rows carry no descriptions any more, so nothing has to stay in step with
   anything. Skills stack on their own line and the row is as tall as it needs to
   be. The cap stays for the rod rows, whose one description still lives here. */
.smithy-table .dt .ent{max-height:2.6em;overflow:hidden}
.smithy-table .sk .ent{min-height:0;line-height:1.6}
.smithy-table .sk .ent{font-weight:600;opacity:.95}
.smithy-table .dt .ent{opacity:.72}
/* The numbers a level actually moves. Set apart from the skill blurbs above
   them, because they answer a different question: not what the suit does, but
   what the Upgrade button is buying. */
.smithy-table .dt .ent.stat{opacity:.95;font-variant-numeric:tabular-nums}
.smithy-table .dt .ent.gain{opacity:.9;color:var(--good,#4fbf8b);font-variant-numeric:tabular-nums}

@media (max-width:640px){
  .shop-table .dt{display:none}
  /* The material column outlives the description here — it is the one you cannot
     work the screen without. */
  .smithy-table .mt .held{display:none}
}

.card{background:var(--bg2);border:1px solid var(--line);border-radius:8px;padding:9px 11px;display:grid;gap:5px}
.card .top{display:flex;justify-content:space-between;align-items:center;gap:8px}
.card .nm{font-weight:600;display:flex;align-items:center;gap:7px;min-width:0}
.card .nm img{width:26px;height:26px;object-fit:contain;flex-shrink:0;image-rendering:pixelated}
.card .price{color:var(--money);font-variant-numeric:tabular-nums;white-space:nowrap}
.card .d{font-size:11.5px;opacity:.75;line-height:1.35}
.card .owned{font-size:11.5px;opacity:.75}
.card .row{display:flex;gap:6px;align-items:center}

/* ── Modals ────────────────────────────────────────────────────────────── */
.modal{position:fixed;inset:0;background:rgba(0,0,0,.7);display:grid;place-items:center;z-index:50;padding:16px}
.modal-box{background:var(--bg2);border:1px solid var(--line);border-radius:10px;
  padding:20px;max-width:560px;width:100%;max-height:82vh;overflow-y:auto;display:grid;gap:10px}
.modal-box h2{margin:0;font-size:18px}
.modal-list{list-style:none;margin:0;padding:0;display:grid;gap:3px;max-height:40vh;overflow-y:auto;font-size:13px}
.modal-list li{display:flex;justify-content:space-between;gap:8px;background:var(--card);padding:4px 7px;border-radius:5px}
.modal-box.cart{border-color:var(--danger)}
.modal-box.cart h2{color:var(--danger)}

/* Other MHGU apps modal. The block is shared verbatim across every sibling app
   and expects .modal-card, where this app's own dialogs use .modal-box; the
   layout hooks are scoped to #linksModal so nothing else here shifts. */
.modal-card{
  background:var(--bg2);border:1px solid var(--line);border-radius:8px;
  padding:18px;max-width:470px;width:100%;max-height:82vh;overflow-y:auto;
  background-image:
    linear-gradient(color-mix(in srgb,var(--bg1) 70%,transparent),color-mix(in srgb,var(--bg1) 70%,transparent)),
    url('assets/panel-body-bg.png');
  background-size:contain 100%;background-position:left center;background-repeat:repeat;
}
.modal-card h2{margin:0 0 12px;font-size:18px}
.modal-card a{color:color-mix(in srgb, hsl(from var(--accent) calc(h + 180) 75% 60%) 50%, var(--text))}
.modal-card .muted{color:var(--text-dim);font-size:13px;opacity:.75}
#linksModal .row{display:flex;align-items:center}
#linksModal .row.gap{gap:8px}
#linksModal .row.end{justify-content:flex-end}
.app-links{list-style:none;margin:0 0 14px;padding:0;display:flex;flex-direction:column;gap:10px}
.app-links li{border:1px solid var(--line);border-radius:8px;padding:9px 12px;background:#ffffff08}
.app-links li.current{opacity:.72}
.app-links a,.app-name{font-weight:600;font-size:14px}
.app-here{font-weight:400;font-size:11px;opacity:.7;border:1px solid var(--line);border-radius:6px;padding:1px 7px;margin-left:7px}
.app-desc{display:block;margin-top:3px;font-size:12px;opacity:.8;line-height:1.4}

/* Theme modal — the family swatch grid, copied verbatim from the other apps. */
.swatches{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin-bottom:15px}
.swatch{aspect-ratio:1;border-radius:8px;border:2px solid transparent;cursor:pointer;position:relative;overflow:hidden;padding:0}
.swatch.sel{border-color:#fff}
.swatch-icon{position:absolute;top:7%;left:50%;transform:translateX(-50%);width:60%;pointer-events:none;filter:drop-shadow(0 1px 2px rgba(0,0,0,.45))}
.swatch span{position:absolute;bottom:0;left:0;right:0;text-align:center;font-size:10px;line-height:1.5;color:#fff;background:rgba(0,0,0,.45)}

/* Settings modal: the gear holds more than a palette now. */
.set-head{
  font-size:12px;font-weight:600;letter-spacing:.05em;text-transform:uppercase;
  opacity:.75;margin:0 0 8px;padding-bottom:5px;border-bottom:1px solid var(--line);
}
.set-head + .swatches{margin-bottom:4px}
.setting{display:flex;gap:10px;align-items:flex-start;margin:0 0 18px;cursor:pointer}
.setting input{margin:2px 0 0;flex:none}
.setting b{display:block;font-size:13px;font-weight:600}
.setting i{display:block;font-style:normal;font-size:11.5px;opacity:.7;line-height:1.45;margin-top:2px}

/* A locale panel wears its own water, on a charcoal base in EVERY theme.
   Fixing the base is what makes the tint mean the same thing everywhere — laid
   over --bg2 it was a whisper on Forbidden and a wash on Rathalos.

   Fixing it also makes the panel a dark surface on a light theme, which the
   theme's own tokens are not expecting: on Khezu --text is pure black, --money
   is a mid grey and --card is a black wash, and all three vanish on charcoal.
   So the panel stops borrowing them and carries its own ink. Everything below is
   scoped to .watered and changes nothing anywhere else in the app.

   The one colour it keeps is the locale's: a fish you have caught is lit in the
   water it came out of. */
.panel.watered{
  --base:#1E2025;                                  /* Forbidden, the family default */
  --ink:#f0f0f0;
  --ink-dim:#c9c9c9;
  --lit:color-mix(in srgb,var(--water) 42%,#fff);  /* light on charcoal, whatever the hue */
  color:var(--ink);
  border-color:color-mix(in srgb,var(--water) 34%,#000);
}
.panel.watered .panel-head{
  color:var(--ink);
  background-image:
    linear-gradient(color-mix(in srgb,color-mix(in srgb,var(--water) 26%,var(--base)) 82%,transparent),
                    color-mix(in srgb,color-mix(in srgb,var(--water) 26%,var(--base)) 82%,transparent)),
    url('assets/sidebar-background-texture1.png');
}
.panel.watered .panel-body{
  background-image:
    linear-gradient(color-mix(in srgb,color-mix(in srgb,var(--water) 14%,var(--base)) 92%,transparent),
                    color-mix(in srgb,color-mix(in srgb,var(--water) 14%,var(--base)) 92%,transparent)),
    url('assets/panel-body-bg.png');
}
.panel.watered .catch-list li{background:rgba(255,255,255,.07);color:var(--ink)}
.panel.watered .catch-list .n{color:var(--ink-dim);opacity:1}
.panel.watered .rank-head{color:var(--ink-dim);border-bottom-color:rgba(255,255,255,.16);opacity:1}
.panel.watered .rank-head .cnt{color:var(--ink-dim)}
.panel.watered .chip{background:rgba(255,255,255,.07);color:var(--ink-dim);opacity:.62}
.panel.watered .chip.has{opacity:1;color:var(--lit);border-color:rgba(255,255,255,.20)}
.panel.watered .missing{color:var(--ink-dim);opacity:.9}
.panel.watered .missing.done{color:var(--lit);opacity:1}

/* Combine materials: what it makes, then where it comes from. The sources are
   the long line, so they get their own row rather than fighting the name. */
.mat-list{list-style:none;margin:0;padding:0;display:grid;gap:4px}
.mat-list li{
  display:grid;grid-template-columns:22px minmax(0,1fr);gap:8px;align-items:start;
  padding:5px 8px;background:var(--card);border:1px solid var(--line);border-radius:6px;
  opacity:.62;
}
.mat-list li.have{opacity:1;border-color:color-mix(in srgb,var(--accent-hover) 45%,var(--line))}
.mat-list img{width:22px;height:22px;object-fit:contain;image-rendering:pixelated}
.mat-list .minfo{display:grid;gap:2px;min-width:0}
.mat-list .nm{font-size:12.5px;font-weight:600;display:flex;align-items:baseline;gap:6px}
.mat-list .nm .n{font-size:11.5px;font-weight:400;opacity:.8;font-variant-numeric:tabular-nums}
.mat-list .mk{font-size:11.5px;opacity:.85;line-height:1.35}
/* Where the thing comes from, and how often, one locale per line. */
.mat-list .src{font-size:11px;opacity:.75;line-height:1.4;display:grid;gap:1px;margin-top:2px}
.mat-list .src .at{display:grid;grid-template-columns:minmax(0,1fr) 34px 40px;gap:5px;align-items:baseline}
.mat-list .src .at b{font-weight:400}
.mat-list .src .at i{font-style:normal;opacity:.6}
.mat-list .src .at em{
  font-style:normal;text-align:right;font-variant-numeric:tabular-nums;opacity:.9;
}
.mat-list .src .shop{opacity:.7}
.mat-list .src .more{opacity:.5;font-size:10.5px}
.mat-list li{align-items:start}

/* A recipe you cannot reach yet still shows what it will want. */
.mat-list li.locked{opacity:.42}
.mat-list li.locked .nm .n{color:var(--text);opacity:.7;font-weight:400}

/* A combo list is a table, so the result, the two ingredients and the rate line
   up down the page instead of each row wrapping wherever its names happen to
   end. The rate sits immediately after the ingredients rather than at the far
   edge — its column is only as wide as it needs to be. */
/* Two panels abreast where there is room. Each table is only ~520px wide once it
   shrinks to its content, so on a normal window the Species and Variety lists sit
   side by side instead of leaving half the screen empty and making the page twice
   as long as it needs to be. */
#comboGrid{display:grid;gap:14px;align-items:start;
  grid-template-columns:repeat(auto-fit,minmax(520px,1fr))}
#comboGrid > .panel:first-child{grid-column:1/-1}

/* Shrink to the content. At 100% width the browser hands the slack to the two
   ingredient columns, which put 200px of nothing between a bait and its parts. */
.combo-table{width:auto;border-collapse:collapse;font-size:12.5px}
.combo-table tr{border-bottom:1px solid color-mix(in srgb,var(--line) 55%,transparent)}
.combo-table tr:last-child{border-bottom:0}
.combo-table td{padding:2px 6px;vertical-align:middle;opacity:.62;white-space:nowrap;line-height:1.2}
.combo-table tr.have td{opacity:1}
/* Not dimmed — the row reads normally and only the ingredients are withheld. */
.combo-table tr.locked td{opacity:.62}
.combo-table .unknown{letter-spacing:2px;opacity:.5;font-weight:600}
.combo-table img{width:18px;height:18px;object-fit:contain;image-rendering:pixelated;flex:none}
.combo-table .ing{display:flex;align-items:center;gap:5px;min-width:0}

.combo-table .res b{font-weight:600;font-size:12.5px}
.combo-table .op{text-align:center;opacity:.45;font-weight:600;padding:2px 3px}
.combo-table .n{
  text-align:right;font-weight:600;
  font-variant-numeric:tabular-nums;padding-left:12px;
}
.combo-table .own{font-style:normal;opacity:.65;font-variant-numeric:tabular-nums}

/* The material panels go abreast too — each is a narrow list, and stacking them
   left three quarters of the screen empty. */
#materialGrid{display:grid;gap:12px;align-items:start;
  grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
.mat-list .unknown{letter-spacing:2px;opacity:.55;font-weight:600}
/* Same masking the Combos and Materials pages use, so an unfound thing reads the
   same wherever you meet it. */
.ingr-list .unknown{letter-spacing:2px;opacity:.55;font-weight:600}

/* Whatever just bit you, shown in the line that says so. */
.pest-icon{
  width:20px;height:20px;object-fit:contain;vertical-align:-5px;margin-right:4px;
}

/* ── Monster log ─────────────────────────────────────────────────────────
   A list rather than a card grid: one line per monster, read down the page. */
.mon-table .ic{width:32px;padding-right:0}
.mon-table .ic img{width:28px;height:28px;object-fit:contain;display:block;image-rendering:pixelated}
.mon-table .nm{white-space:nowrap;font-weight:600}
.mon-table .dt{opacity:.68;font-size:12px;max-width:38ch}
.mon-table .rk{white-space:nowrap;font-size:11.5px;opacity:.75;width:1%}
.mon-table .tally{white-space:nowrap;font-size:12px;width:1%}
.mon-table .tally b{font-weight:700}
.mon-table .tally i{font-style:normal;opacity:.35;margin:0 2px}
.mon-table .tally .rate{opacity:.5;margin-left:6px;font-size:11px}
.mon-table .pr{color:var(--money);text-align:right;font-variant-numeric:tabular-nums;
  white-space:nowrap;width:1%}
.mon-table .wh{font-size:11px;opacity:.5;white-space:nowrap}
/* One you have not met gives away nothing — not even its silhouette. */
.mon-table tr.unmet{opacity:.4}
.mon-table tr.unmet img{filter:grayscale(1)}
.mon-table tr.unmet .nm{font-weight:400;letter-spacing:1px}


/* ── What you are wearing ─────────────────────────────────────────────────
   Two places, one builder: the Equipment tab in camp beside Locale, Canteen and
   Items, and a panel down the left of the smithy's ARMOR tab — rods and the cart
   have nothing to say about a set, and scoping it to that one view is what keeps
   their tables at full width. */

/* The armor view is the only smithy view with a side panel, so the grid lives on
   the view rather than on #smithy. */
/* `.active` matters: `#viewArmor.subview` alone outranks the rule that hides an
   inactive subview, so the armor view — and the panel in it — stayed on screen
   under the Rods and Trade Cart tabs as well. */
/* Three columns, left to right: what you have on, which set you are looking at,
   and the pieces of it. Each answers the next one's question. */
#viewArmor.subview.active{display:grid;gap:12px;
                          grid-template-columns:230px 190px minmax(0,1fr);
                          grid-template-rows:auto 1fr;align-items:start}
#viewArmor > .worn-panel{grid-column:1;grid-row:1 / span 2;margin:0;
                         position:sticky;top:12px}
#viewArmor > .line-list{grid-column:2;grid-row:1 / span 2;position:sticky;top:12px}
#viewArmor > .subtabs{grid-column:3;grid-row:1}
#viewArmor > .panel:not(.worn-panel){grid-column:3;grid-row:2}

/* Fifty-six monsters is a list you scroll, not a strip you hunt along. Its own
   scroller so the table beside it keeps its full height. */
.line-list{display:flex;flex-direction:column;gap:2px;
           max-height:calc(100vh - 150px);overflow-y:auto;padding-right:4px}
/* flex:0 0 auto is the whole fix for the list packing itself: a column flex
   SHRINKS its children by default, so fifty-six entries in a height-capped column
   compressed until the text sat on top of itself. They keep their height and the
   column scrolls instead. */
.line-list .subtab{flex:0 0 auto;text-align:left;width:100%;border:0;border-radius:6px;
                   padding:6px 10px;font-size:13px;white-space:nowrap;
                   overflow:hidden;text-overflow:ellipsis;line-height:1.4}
.line-list .subtab.active{font-weight:600}
@media (max-width:1000px){
  #viewArmor.subview.active{display:block}
  #viewArmor > .worn-panel{position:static;margin-bottom:12px}
  /* Back to a strip on a narrow screen, where a column costs more than it buys. */
  #viewArmor > .line-list{position:static;flex-direction:row;flex-wrap:wrap;
                          max-height:none;overflow:visible;margin-bottom:10px}
  .line-list .subtab{width:auto}
}
.worn-set,.worn-stat,.worn-skills{list-style:none;margin:0;padding:0}
.worn-set li{display:grid;grid-template-columns:44px minmax(0,1fr) auto;gap:6px;
             align-items:baseline;padding:3px 0;font-size:12px}
.worn-set .slot{opacity:.55;text-transform:uppercase;letter-spacing:.06em;font-size:10px}
.worn-set .pn{font-weight:600;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.worn-set .lv{opacity:.6;font-variant-numeric:tabular-nums;font-size:11px}
.worn-set li.empty .pn{font-weight:400;opacity:.45;font-style:italic}

.worn-stat{display:flex;gap:10px;flex-wrap:wrap;margin:8px 0 0;
           padding-top:8px;border-top:1px solid var(--line,#ffffff1f)}
.worn-stat li{font-size:12px;opacity:.8}
.worn-stat b{font-variant-numeric:tabular-nums}

.worn-set-bonus{margin:8px 0 0;font-size:11px;text-transform:uppercase;
                letter-spacing:.07em;opacity:.5}
.worn-set-bonus.on{opacity:1;color:var(--good,#4fbf8b)}

.worn-skills{margin-top:8px;padding-top:8px;border-top:1px solid var(--line,#ffffff1f)}
.worn-skills li{padding:4px 0}
.worn-skills .sn{display:block;font-size:12px;font-weight:600}
.worn-skills .sd{display:block;font-size:11px;opacity:.6;line-height:1.35}

/* ── Marketplace ──────────────────────────────────────────────────────────
   One picker for what you hand over, then every part of that same rarity you
   could take back. Rarity is the only gate, so the picker holding nothing is
   itself the answer to "why can I not trade yet". */
.trade-pick{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.trade-pick label{font-size:12px;opacity:.7;text-transform:uppercase;letter-spacing:.06em}
.trade-pick select{background:#12141a;color:inherit;border:1px solid #ffffff2e;
                   border-radius:6px;padding:6px 10px;font:inherit;font-size:13px;
                   min-width:200px;max-width:100%}
.trade-pick .count{font-size:12px;opacity:.7;font-variant-numeric:tabular-nums}

/* ── The pool between casts ───────────────────────────────────────────────
   Fish drift in and out of the water whether or not a line is in it, so a bomb
   is aimed at what is really there rather than at a roll. */
/* The pond is a sibling of #reel now rather than a child, so it keeps its own
   size whether or not a cast is running. */
#pond{position:relative}
/* `entering` is the same fade the pool's own fish use, lent to a cast's school so
   the swap in either direction is the water resettling rather than a cut. It is
   taken off again once it has played: `gone`, at the end of a fight, wants to be
   instant. */
.swimmer.pooled,.swimmer.entering{transition:opacity .6s ease,transform .6s ease}
.swimmer.pooled.arriving,.swimmer.pooled.leaving,
.swimmer.entering.arriving,.swimmer.entering.leaving{opacity:0;transform:translate(-50%,-50%) scale(.6)}

/* ── A bomb ───────────────────────────────────────────────────────────────
   Aimed like the bobber, then dropped: it sinks, holds, and goes up. The ring is
   the real blast radius, stretched by the pond's aspect so a circle in the water
   is a circle on the screen. */
.bomb-marker{position:absolute;transform:translate(-50%,-50%);z-index:6;
             pointer-events:none;transition:left .12s ease,top .12s ease}
.bomb-marker img{width:28px;height:28px;display:block;position:relative;z-index:2;
                 /* tinted toward the water it is going into */
                 filter:drop-shadow(0 2px 3px #0007);
                 transition:transform .5s cubic-bezier(.4,0,.7,1),opacity .2s}
.bomb-marker .ring{position:absolute;left:50%;top:50%;width:var(--d);height:var(--d);
                   transform:translate(-50%,-50%);border-radius:50%;
                   border:2px dashed color-mix(in srgb,var(--water,#2E7D6B) 60%,#fff);
                   background:color-mix(in srgb,var(--water,#2E7D6B) 30%,transparent);
                   opacity:.55;transition:opacity .25s ease,transform .35s ease}
/* Sinks in and shrinks... */
.bomb-marker.dropping img{transform:scale(.62)}
.bomb-marker.dropping .ring{opacity:.35}
/* ...then goes up. */
.bomb-marker.blast img{transform:scale(1.9);opacity:0}
.bomb-marker.blast .ring{opacity:.9;transform:translate(-50%,-50%) scale(1.06);
                         border-style:solid;
                         background:color-mix(in srgb,#fff 35%,transparent)}
.swimmer.blasted{transition:opacity .4s ease,transform .4s ease;
                 opacity:0;transform:translate(-50%,-50%) scale(1.5) rotate(20deg)}
@media (prefers-reduced-motion:reduce){
  .bomb-marker,.bomb-marker img,.bomb-marker .ring,
  .swimmer.pooled,.swimmer.entering,.swimmer.blasted{transition:none}
}

/* ── A trap ───────────────────────────────────────────────────────────────
   Placed like a bomb but it STAYS, and it goes on taking fish while you cast.
   The ring is the reach it actually has, sized off the pond in pixels for the
   same reason the blast is. */
.trap-marker{position:absolute;transform:translate(-50%,-50%);z-index:5;
             pointer-events:none;transition:left .12s ease,top .12s ease}
.trap-marker img{width:24px;height:24px;display:block;position:relative;z-index:2;
                 filter:drop-shadow(0 2px 3px #0007)}
.trap-marker .ring{position:absolute;left:50%;top:50%;width:var(--d);height:var(--d);
                   transform:translate(-50%,-50%);border-radius:50%;
                   border:2px dashed color-mix(in srgb,var(--water,#2E7D6B) 55%,#fff);
                   background:color-mix(in srgb,var(--water,#2E7D6B) 22%,transparent);
                   opacity:.5;transition:opacity .25s ease}
/* While you are still choosing where, it is brighter than once it is down. */
.trap-marker.placing .ring{opacity:.75;border-style:dashed}
.trap-marker.set .ring{opacity:.34;border-style:solid}
.trap-marker.catching .ring{opacity:.85;background:color-mix(in srgb,#fff 28%,transparent)}
/* Full: still there so you can see where it was, and that it is done. */
.trap-marker.full{opacity:.4}
.trap-marker.full .ring{border-style:dotted}
.swimmer.trapped{transition:opacity .4s ease,transform .4s ease;
                 opacity:0;transform:translate(-50%,-50%) scale(.5)}
@media (prefers-reduced-motion:reduce){
  .trap-marker,.trap-marker .ring,.swimmer.trapped{transition:none}
}

/* ── Screen Saver ─────────────────────────────────────────────────────────
   The water with the fishing taken out. The stage is fixed to the viewport
   rather than laid out in a view, because it answers to the window and not to
   wherever you were standing when you opened it. */
.saver-stage{
  position:fixed;inset:0;z-index:60;background:var(--bg);
  display:grid;place-items:stretch;
}
/* #saverWater carries .pond too, so every gradient, drifting layer and bubble
   is the game's own. All this has to undo is the pond's SIZE — it is boxed to
   16/10 and half the viewport height, and here it is the whole window. */
.saver-water.saver-water{
  aspect-ratio:auto;max-height:none;width:100%;height:100%;
  border:0;border-radius:0;
}
/* Out of the way until wanted. The point of the thing is an unbroken window, so
   the controls sit in a corner and fade back once the pointer leaves them. */
.saver-bar{
  position:absolute;right:14px;bottom:14px;display:flex;gap:8px;
  opacity:.25;transition:opacity .25s ease;
}
.saver-bar:hover,.saver-bar:focus-within{opacity:1}
@media (prefers-reduced-motion:reduce){
  .saver-bar{opacity:1;transition:none}
}

.saver-box{display:grid;gap:14px;justify-items:stretch;text-align:left;max-width:420px}
.saver-note{margin:0;opacity:.75;font-size:13px}
.saver-field{display:grid;grid-template-columns:110px 1fr;align-items:center;gap:12px}
.saver-field > span:first-child{font-weight:600}
.saver-range{display:grid;grid-template-columns:1fr 2.5em;align-items:center;gap:10px}
.saver-range input[type=range]{width:100%}
/* Tabular so the number does not shuffle the slider sideways as it counts. */
.saver-range b{text-align:right;font-variant-numeric:tabular-nums}
.saver-actions{display:flex;gap:8px;justify-content:flex-end;margin-top:4px}
