/* GrowlMaster styles (served raw so they apply to JS-injected DOM). Scoped under .lb.
   Printed-sheet look: square corners, no drop shadow, min 16px type, centered.
   Chrome is owned by this page (SiteLayout app + footer={false}) — never hide
   .site-header/.site-footer here; that flashes JimGenerate on first paint. */
@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@700&family=Frank+Ruhl+Libre:wght@500;700;900&family=Grandstander:wght@500;700;800&display=swap');
/* ---- Palette: the ONE colour source. Declared on every GrowlMaster surface —
   the sheet (.lb) AND the body-level layers that render OUTSIDE it (.lb-ovl
   overlays, .lb-menu context menus, .gm-toast, the TV's bar and button) — rather than bare :root, so it
   never collides with SiteLayout's own :root tokens (e.g. --accent). .gm-btn and
   everything inside these surfaces inherit the same values. ---- */
.lb, .lb-ovl, .lb-menu, .gm-toast, .ednum-pop, .gm-lbx, .lb-gate, .retro, .gm-guestmenu, .gm-tvbar {
  --paper:#efe7d3; --sheet:#faf5e8; --sheet2:#f2ead6; --sheet3:#e7dcc0; --edge:#cbb98f;
  --ink:#3a2c17; --ink2:#6f5c3c; --ink3:#8f7c56;
  --gold:#865309; --gold-dk:#6e4307; --gold2:#a8935f; --accent:#ff7038; --red:#B32D1F; --line:#b7a473;
  /* --green had no home at all: three rules asked for it and all three have been living
     on their own fallback, var(--green,#2C6E4A), since they were written. Same value, so
     nothing moves on screen; it just has one place to be changed now.
     The two tints are MIXED FROM the side colours rather than typed out, so a side and
     its wash can never drift apart. 10% because they are a hint of whose square this is,
     not a block of colour: the faintest thing the board already draws is the path at 24%,
     and these have to stay well under it or they start arguing with it.
     MIXED INTO --sheet, NOT INTO transparent, and the difference is the whole thing. A
     cell's background is normally the opaque sheet, so a translucent wash REPLACES that
     rather than sitting on it, and the tint ends up compositing over the board beneath.
     At 10% that looked nothing like 10%: the foe squares came out tan and the party
     squares olive. Mixing into the sheet keeps the square opaque and the colour exactly
     as faint as it says. Found by taking a screenshot; the computed value read 0.1 and
     looked perfectly correct all the way through. */
  --green:#2C6E4A;
  /* The third side's colour, waiting. Allies cannot appear on a board yet (see the note
     in normalizeFight), so nothing paints with this today and that is on purpose.
     CHOSEN BY MEASUREMENT, not by taste: white text reads 6.34 on --red and 6.11 on
     --green, and this blue reads 6.46. A darker blue like #1F4E79 clears any bar going
     (8.7) and would make the ally bar visibly heavier than the other two, so one side
     would look more important than the others for no reason anybody chose. Matching the
     weight matters more here than maximising one number. */
  --blue:#35618E;
  /* An OBJECT is not a side, it is a fourth appearance: a tree, a barrel, a door. Dark
     grey so it reads as scenery rather than as somebody, and measured like the others so
     it is not heavier than them. White on it reads 6.62, against 6.34 on red, 6.11 on
     green and 6.46 on blue. Warm rather than neutral, because a true grey on parchment
     looks like a hole in the paper. */
  --grey:#625C52;
  --tint-foe:color-mix(in srgb, var(--red) 10%, var(--sheet));
  --tint-party:color-mix(in srgb, var(--green) 10%, var(--sheet));
  --tint-ally:color-mix(in srgb, var(--blue) 10%, var(--sheet));
  --tint-object:color-mix(in srgb, var(--grey) 10%, var(--sheet));
  /* The edge of an occupied square: the same colour at half strength. Mixed into
     TRANSPARENT rather than into the sheet, unlike the tints, and that is deliberate
     rather than inconsistent. A border is painted over the cell's own background, so
     half-strength here means half over whatever wash is already there, which is what
     "50% of that colour" has to mean on an edge. The tints could not do that because a
     translucent BACKGROUND replaces the sheet instead of sitting on it. */
  --edge-foe:color-mix(in srgb, var(--red) 50%, transparent);
  --edge-party:color-mix(in srgb, var(--green) 50%, transparent);
  --edge-ally:color-mix(in srgb, var(--blue) 50%, transparent);
  --edge-object:color-mix(in srgb, var(--grey) 50%, transparent);
  /* Bestiary page language (SoT: /growlmaster/sot) */
  --burgundy:#78261F; --meta:#F5EFE0; --stripe:#e5ede7; --stripe2:#cfdad2;
  --stripe-line:#c9d6ce; --stripe2-line:#b5c4bb;
}


/* THIS RULE DOES NOTHING, AND WHY IS WORTH MORE THAN THE RULE.
   WHAT IT ASKS FOR: app mode's 44px tap floor is built for sparse chrome, and this sheet
   is dense, so it tries to hand the sizing back to the controls themselves.
   WHAT ACTUALLY PAINTS: 44px. Measured on the running app rather than read off this
   stylesheet, which is the only reason anybody noticed. Every button in the battle
   toolbar computes min-width 44px and min-height 44px, and .gm-btn--icon.gm-btn--sm
   asking for 30 by 30 loses to it. The comment that used to sit here said the floor was
   off, so everyone who read it believed a thing the browser has never done.
   WHY IT LOSES: AppStyles carries
     body.jim-app :is(button, [role="button"], a.app-btn, .app-hit) { min-height: var(--app-tap) }
   and :is() takes the specificity of its MOST SPECIFIC branch, which is a.app-btn. That
   makes it (0,2,2). The reset below is (0,2,0). It cannot win, and no amount of
   rewriting it in these two lines will change that.
   Beating it means out-specifying AppStyles on purpose, which resizes every control in
   GrowlMaster and not just the dense ones. That is a product decision about how the app
   looks, not a tidy-up, so ask rather than fix it in passing.
   Kept rather than deleted so the next person finds this instead of rediscovering it.
   Third time this exact shape has cost this codebase a day. */
.lb :is(button, [role="button"]), .gm-btn { min-height:0; min-width:0; }
/* contenteditable fields must remain selectable under body.jim-app's user-select:none */
.lb .ed, .lb [contenteditable] { -webkit-user-select:text; user-select:text; }

/* ---- GrowlMaster's palette is at the top of this file; the Jim assistant has its own
   (public/assistant/assistant.css). ---- */
.lb {
  height:100dvh; display:flex; flex-direction:column; overflow:hidden;
  /* Aged paper under the whole table. The panes and rails are transparent, so
     this is the surface everything sits on; --paper stays as the colour beneath
     in case the image is still loading or blocked. fixed keeps the grain still
     while a pane scrolls, which reads as a desk rather than a moving backdrop. */
  background:var(--paper);
  color:var(--ink);
  font-family:'Grandstander', system-ui, sans-serif; font-size:16px;
}
/* A piece of the app on a public page (the character sheet's sheet, the monster maker's picture): the app's scope,
   flowing like a page instead of filling the screen. */
.lb.lb--page { height:auto; overflow:visible; display:block; background:transparent; }
/* In overlays, reuse .lb sheet markup/styles without the app-shell layout */
.lb.lb--inset {
  height:auto; min-height:0; display:block; overflow:visible;
  background:transparent;
}
.lb h1,.lb h2,.lb h3,.lb .serif { font-family:'Frank Ruhl Libre', Georgia, serif; margin:0; }
.lb .spacer { flex:1; }

/* character file tabs — horizontal scroll, never wrap */
.lb-tabsbar { flex:0 0 auto; display:flex; flex-wrap:nowrap; gap:8px; align-items:flex-end;
  padding:12px 16px 0; border-bottom:2px solid var(--line); background:var(--paper);
  overflow-x:auto; overflow-y:hidden; -webkit-overflow-scrolling:touch; scrollbar-width:thin;
  overscroll-behavior-x:contain; }
.lb-tabsbar::-webkit-scrollbar { height:4px; }
.lb-tabsbar::-webkit-scrollbar-thumb { background:var(--edge); }
.lb .lb-tab { display:flex; flex-direction:column; gap:5px; cursor:pointer; background:var(--sheet2);
  border:2px solid var(--edge); border-bottom:none; padding:9px 16px 10px; color:var(--ink2);
  font-weight:700; font-size:16px; white-space:nowrap; flex:0 0 auto; }
.lb .lb-tab .tname { display:flex; align-items:center; gap:6px; }
.lb .lb-tab .tbar { height:5px; background:var(--paper); border:1.5px solid var(--edge); overflow:hidden; }
.lb .lb-tab .tbar i { display:block; height:100%; background:linear-gradient(90deg,var(--gold2),var(--gold)); }
.lb .lb-tab.on { background:var(--sheet); color:var(--ink); border-color:var(--line); position:relative; top:2px; }
.lb .lb-tab .dot { width:9px;height:9px;border-radius:50%;background:var(--red); }
.lb .lb-tab.add { color:var(--ink3); align-self:stretch; justify-content:center; }

/* Two linked panes over one SoT, with tab RAILS pinned to the outer edges and a
   draggable DIVIDER in the middle. Row: [left rail][left pane][divider][right
   pane][right rail]. Rails keep their width; the divider redistributes the rest. */
.lb-split { flex:1; min-height:0; display:flex; overflow:hidden; }
/* Each pane is a QUERY CONTAINER: the sheet's responsive rules react to the
   PANE's width (which the divider changes), not the viewport — so a half-width
   pane on a big screen gets the same stacked layout as a phone. */
.lb-pane { flex:1 1 0; min-width:0; min-height:0; display:flex; flex-direction:column; overflow:hidden;
  container-type:inline-size; container-name:pane; }
.lb-pane.collapsed { flex-basis:0 !important; }
/* bouncy width easing — only when set programmatically (tab tap), never on drag */
.lb-split.springy .lb-pane { transition:flex-grow .46s cubic-bezier(0.34,1.56,0.64,1); }
@media (prefers-reduced-motion:reduce){ .lb-split.springy .lb-pane { transition:flex-grow .18s ease; } }

/* vertical tab rail (outer edge). Text runs along the edge; active tab is filled. */
.lb-rail { flex:0 0 auto; display:flex; flex-direction:column; background:var(--sheet2); }
.lb-rail[data-side="left"] { border-inline-end:2px solid var(--line); }
.lb-rail[data-side="right"] { border-inline-start:2px solid var(--line); }
.lb-rail button { flex:0 0 auto; background:none; border:0;
  padding:14px 14px; font-family:'Grandstander',system-ui,sans-serif; font-weight:700; font-size:16px;
  color:var(--ink2); cursor:pointer; white-space:nowrap; text-align:center; border-block-end:2px solid var(--edge); }
.lb-rail button:last-child { border-block-end:0; }
.lb-rail button { position:relative; } /* anchor the AI-change dot */
.lb-rail button.has-alert::after { content:""; position:absolute; top:8px; inset-inline-end:8px;
  width:9px; height:9px; border-radius:50%; background:var(--red); box-shadow:0 0 0 2px var(--sheet2); }
.lb-rail button:hover { color:var(--ink); }
/* The selected tab is marked by the lighter panel and the darker ink alone. The
   accent stripe was the one loud colour in a page of aged paper. */
.lb-rail[data-side="left"]  button.on { background:var(--sheet); color:var(--ink); }
.lb-rail[data-side="right"] button.on { background:var(--sheet); color:var(--ink); }

/* drag handle between the panes */
/* The panes carry the edge, not the divider: once a pane is narrow enough to be closed
   over, its content is cut off mid-word, and without a hard rule there the text simply
   stops in mid-air. Putting it on the pane keeps the line in place even when the divider
   is dragged right to the end. */
.lb-pane[data-side="left"] { border-inline-end:2px solid var(--line); }
.lb-pane[data-side="right"] { border-inline-start:2px solid var(--line); }
.lb-divider { flex:0 0 10px; align-self:stretch; cursor:col-resize; background:var(--sheet2);
  position:relative; touch-action:none; }
.lb-divider::after { content:""; position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:2px; height:34px; background:var(--edge); box-shadow:-3px 0 0 var(--edge),3px 0 0 var(--edge); }
.lb-divider:hover, .lb-divider.dragging { background:var(--sheet3); }

/* mobile bottom nav: every tab visible, wrapping to a second row (desktop hides it) */
.lb-mnav { display:none; }

/* ---- Mobile / small tablet: ONE panel, all tabs in a scrolling row below it,
   and the AI drawer OVERLAYS the content instead of pushing. ---- */
@media (max-width:820px){
  .lb-body { flex-direction:column; } /* the page, then the bottom nav under it (the panel's overlay is assistant.css's) */
  .lb-rail, .lb-divider, .lb-pane[data-side="left"] { display:none; }  /* no split chrome */
  /* one pane, nothing to separate it from */
  .lb-pane[data-side="right"] { border-inline-start:0; }
  /* single pane fills the width — !important beats applySplit's inline flex-grow */
  .lb-pane[data-side="right"] { flex:0 0 100% !important; min-width:0; max-width:100%; }
  .lb-split { flex:1; min-height:0; }

  /* EVERY TAB IS ON THE SCREEN. This used to be one horizontal-scroll row, and at
     390px that put 646px of tabs in a 390px rail: four visible, Locations showing
     13px of itself, Shop and Library showing nothing at all. It scrolled, so
     nothing was unreachable, but there was no fade, no arrow, no scrollbar and
     nothing else in the product scrolls sideways, so a DM was never told the
     gesture existed. Half the product was missing on a phone.

     A second row costs 46px, which is 5.5% of an 844px screen. That is the right
     price for Shop and Library existing.

     WRAP RATHER THAN A FIXED COLUMN COUNT, because flex distributes each LINE
     separately: seven tabs land as four then three, and both rows fill the width,
     so the last row stretches instead of leaving a ragged hole. An eighth tab
     would become four and four on its own. Nothing here counts the tabs.

     It also removes a question rather than answering it twice: a sideways
     scroller hides its overflow off the LEFT in Hebrew, so any fade or arrow
     would have had to be correct in both directions. Nothing is off any edge now. */
  .lb-mnav { display:flex; flex:0 0 auto; flex-wrap:wrap; border-top:2px solid var(--line); background:var(--sheet2);
    padding-bottom:env(safe-area-inset-bottom,0); }
  /* `.lb` IS LOAD-BEARING HERE, and it is not tidiness. App mode sets
     `.jim-app :is(input, textarea, select, button) { font-size: max(16px,1rem) }`
     so that focusing a small INPUT does not make iOS zoom the page. That selector
     computes to the same specificity as `.lb-mnav button` and comes later, so it
     silently won: the rule below said 14px and the browser reported 16px until I
     read the cascade instead of the file. A nav button is outside that rule's
     reason, because iOS zooms on text entry and not on a button.

     14/6 rather than the old 16/16: at 390px a quarter of the rail is 97.5px and
     "Characters" needs 85px of text at 16px, leaving under a pixel of room.
     Measured with the webfont loaded; the fallback measures differently and says
     it fits. */
  .lb .lb-mnav button { flex:1 1 22%; min-width:0; background:none; border:0; border-inline-start:2px solid var(--edge);
    padding:14px 6px; font-family:'Grandstander',system-ui,sans-serif; font-weight:700; font-size:14px;
    color:var(--ink2); cursor:pointer; white-space:nowrap; position:relative;
    overflow:hidden; text-overflow:ellipsis; }
  /* First of EACH row, not just the first of all seven. */
  .lb-mnav button:nth-child(4n+1) { border-inline-start:0; }
  /* The seam between the rows reads as a divider rather than a gap. */
  .lb-mnav button:nth-child(n+5) { border-top:2px solid var(--edge); }
  /* CONTROLS ARE FOR FINGERS ON A PHONE, AND ONLY THERE.
     `.sel` is one shared rule behind 21 selects in this product and it renders
     33px tall: 16px of type, 12px of padding, 4px of border, and no min-height.
     `.item-fx__input` is 32px for the same reason. Both are 3 or 4px under the
     36px hit area app mode sets, everywhere they appear, which is the sort of
     miss that is invisible one control at a time.
     Scoped to the phone breakpoint on purpose. A pointer does not need 36px and
     several of these sit in dense tables where the extra height would change the
     row rhythm on a desktop for no gain. The place a tap target matters is the
     place people tap. */
  .lb .sel, .lb .item-fx__input { min-height:36px; }
  /* AN EDITABLE FIELD IS A TAP TARGET TOO, and the narrow ones are the worst in
     the product: `.lb .ed` sets `min-width:16px` and nothing else, so a hero's
     LEVEL is a 16px-wide target and HP and XP are 17. Twenty of twenty editable
     fields on a hero sheet are under the minimum, from one rule behind 69 of
     them.
     Width only, and that is a measurement rather than a compromise. Raising it
     to 36 changed nothing else at all: no page overflow, no container spilling,
     and the sheet's height identical to the pixel before and after, because the
     `.fld` boxes these sit in are 109 to 167 wide and had the room.
     THE HEIGHT IS STILL 26 AND IS DELIBERATELY LEFT. Every way of raising it
     either moves every field or grows every row, and that is a change to how the
     sheet looks rather than to whether it can be used.
     Not the wrapper: nothing focuses a field when its `.fld` is tapped, so the
     field itself is the only target. Checked, because three other flags tonight
     turned out to be inputs inside labels that were already big enough. */
  .lb .ed { min-width:36px; }
  /* AND THE ONE THAT ESCAPED IT. `.lb .coinfld .ed { min-width:1.7em }` is more
     specific than the line above, so the gold, silver and copper fields stayed
     at 34px while everything else moved to 36. Found by carrying on measuring
     after the fix rather than by trusting that one shared rule was the whole
     story: a fix to a shared rule is only as wide as the rules that do not
     override it. */
  .lb .coinfld .ed { min-width:36px; }

  /* THE SMALLEST PHONES, where a quarter of the rail is 80px and 68px of that is
     text. Measured in the real font: at 14px "Characters" needs 74.1 and
     "Adventure" 68.7, so both are cut; at 12px the longest is 63.5 and every
     label fits with room. Hebrew never needed this, the longest label there is
     הרפתקה at 56.5, but the rule is not language-dependent and should not be. */
  @media (max-width:360px){
    .lb .lb-mnav button { font-size:12px; padding:14px 4px; }
  }
  .lb-mnav button.on { color:var(--ink); background:var(--sheet); box-shadow:inset 0 4px 0 var(--accent); }
  .lb-mnav button.has-alert::after { content:""; position:absolute; top:9px; inset-inline-end:12px;
    width:9px; height:9px; border-radius:50%; background:var(--red); box-shadow:0 0 0 2px var(--sheet2); }

  /* content fills the single panel (no 600px cap) */
  .lb .page { width:100%; max-width:100%; }

}

/* scrolling stage; pages centered when they fit */
.lb-stage { flex:1; min-height:0; overflow-y:auto; overflow-x:hidden; display:flex;
  padding:22px 18px 22px; -webkit-overflow-scrolling:touch; }
/* A floor under the reflow, on the STAGE so nothing inside can undercut it. The sheet
   answers the divider all the way down to this width; past it the content keeps its size
   and the shrinking pane simply closes over it, which is far kinder than squeezing a
   column until every word breaks a letter at a time. The pane clips what no longer fits.
   Phones keep reflowing: there is no second pane there to hide anything behind. */
.lb-stage { min-width:var(--pane-floor,330px); }
@media (max-width:820px){ .lb-stage { min-width:0; } }
.lb .spread { display:flex; gap:18px; justify-content:center; align-items:flex-start; flex-wrap:wrap; margin:auto; width:100%; }
/* Every sheet is a piece of aged paper, and no two are the same piece: the
   variant (one of four rotations of the same scan), the crop and the zoom come
   from paintPageTexture() in app.js as custom properties. A white veil keeps the
   text crisp over the grain.
   Two background layers, no pseudo-element, so nothing here can disturb the
   absolutely-positioned bits inside a card. The size is given in BOTH axes so a
   zoom above 100% always covers, however tall the sheet turns out. */
.lb .page { flex:0 1 560px; width:560px; max-width:100%;
  background-color:var(--sheet);
  background-image:linear-gradient(rgba(255,252,244,.52), rgba(255,252,244,.52)),
                   var(--tex, url("/growlmaster/page-texture-0.webp"));
  background-size:cover, var(--tex-z,190%) var(--tex-z,190%);
  background-position:center, var(--tex-x,50%) var(--tex-y,50%);
  background-repeat:no-repeat, no-repeat;
  border:2px solid var(--line); padding:16px 18px 18px; position:relative; box-sizing:border-box; }

.lb .charhead, .lb .moncard__head {
  display:grid; grid-template-columns:minmax(0,1.6fr) minmax(0,1fr) auto; gap:8px;
  margin:0 0 12px; align-items:stretch;
}
.lb .charhead--solo { grid-template-columns:minmax(0,1fr) auto; }
.lb .moncard__head { grid-template-columns:minmax(0,1.4fr) minmax(min-content,1fr) minmax(min-content,1fr) auto; }
.lb .moncard__head--noprop { grid-template-columns:minmax(0,1.6fr) minmax(min-content,1fr) auto; }
/* No ⋮ menu (the Library's edit, a public maker page): no column kept for it, so the row ends where the rows under it end. */
.lb .charhead:not(:has(> .sheetmenu)) { grid-template-columns:minmax(0,1.6fr) minmax(0,1fr); }
.lb .charhead.charhead--solo:not(:has(> .sheetmenu)) { grid-template-columns:minmax(0,1fr); }
.lb .moncard__head:not(.moncard__head--noprop):not(:has(> .sheetmenu)) { grid-template-columns:minmax(0,1.4fr) minmax(min-content,1fr) minmax(min-content,1fr); }
.lb .moncard__head.moncard__head--noprop:not(:has(> .sheetmenu)) { grid-template-columns:minmax(0,1.6fr) minmax(min-content,1fr) auto; }
.lb .sheetmenu.gm-btn--icon {
  width:42px; min-width:42px; height:auto !important; min-height:100%;
  align-self:stretch; padding:0; margin:0;
  font-size:28px; line-height:1; font-weight:800;
  letter-spacing:0; box-sizing:border-box;
}

/* editable ink */
.lb .ed { outline:0; border-bottom:2px dotted var(--edge); min-width:16px; display:inline-block; padding:0 2px; }
.lb .ed:hover { border-bottom-color:var(--ink3); }
.lb .ed:focus { border-bottom-color:var(--accent); background:rgba(255,112,56,.08); }
.lb .calc { color:var(--gold); font-weight:800; }

/* field grids */
.lb .grid3 { display:grid; grid-template-columns:1fr 1fr 1fr; gap:8px; margin:0 0 8px; }
.lb .fld { background:var(--sheet2); border:2px solid var(--edge); padding:8px 10px;
  display:flex; flex-direction:column; align-items:center; gap:3px; text-align:center; min-width:0; }
.lb .fld .l { font-size:16px; color:var(--ink3); font-weight:700; }
/* A word kept whole (Troll, 2026-10-25: "Kreatur" broke letter by letter at 390): broken only when it alone is wider
   than its box. word-break:break-word also told the grid a word is one letter wide, so a squeezed column took it apart. */
.lb .fld .v { font-family:'Frank Ruhl Libre',serif; font-size:26px; font-weight:700; line-height:1.1; word-break:normal; overflow-wrap:break-word; }
/* Japanese and Chinese break between any two letters: a field's short value only at a space or a mark ("クリーチャー" whole) */
.lb .fld .v:lang(ja), .lb .fld .v:lang(zh) { word-break:keep-all; }
.lb .fld.name .v { font-size:30px; }
.lb .fld.hp .slash { font-size:24px; color:var(--ink3); margin:0 3px; }
.lb .xpbar { width:100%; height:8px; background:var(--paper); border:1.5px solid var(--edge); overflow:hidden; margin-top:5px; }
.lb .xpbar i { display:block; height:100%; background:linear-gradient(90deg,var(--gold2),var(--gold)); }
.lb .lvlup { display:inline-flex; align-items:center; gap:5px; color:var(--gold); font-weight:900; }
.lb .lvl-banner {
  display:flex; align-items:center; gap:16px; flex-wrap:wrap;
  margin:0 0 12px; padding:12px 14px;
  /* Match reference: bright yellow, slightly darker top/bottom */
  background:linear-gradient(180deg, #f0c820 0%, #ffe94a 42%, #ffe94a 58%, #f0c820 100%);
  border:2px solid #e89a20;
  color:#111;
  box-sizing:border-box;
  direction:ltr; /* CTA left, copy right — same as the game mock in both langs */
}
.lb .lvl-banner__copy {
  flex:1; min-width:0; display:flex; flex-direction:column; gap:2px;
  text-align:end; /* sits on the right of the banner, like the mock */
}
.lb .lvl-banner__tag {
  font-weight:800; font-size:15px; letter-spacing:.02em; color:#111;
}
.lb .lvl-banner__lvl {
  font-family:'Frank Ruhl Libre',serif; font-weight:900; font-size:28px; line-height:1.1; color:#111;
}
/* Green game CTA — white label, fat up-arrow after text */
.lb .lvl-banner .lvl-banner__cta,
.lb .lvl-banner .lvl-banner__cta:hover {
  flex:0 0 auto;
  flex-direction:row-reverse; /* arrow after label */
  gap:10px;
  padding:10px 18px;
  font-size:18px; font-weight:800; letter-spacing:.01em;
  --bg:transparent; --b:transparent; --fg:#fff;
  color:#fff;
  background:linear-gradient(180deg, #2f9b3a 0%, #3cb54a 40%, #3cb54a 60%, #2a8f34 100%);
  border:2px solid #1a6b24;
  box-shadow:none;
}
.lb .lvl-banner .lvl-banner__cta:hover {
  background:linear-gradient(180deg, #36a942 0%, #48c458 40%, #48c458 60%, #2f9b3a 100%);
}
.lb .lvl-banner .lvl-banner__cta:active { transform:translateY(1px); }
.lb .lvl-banner__arrow {
  display:block; flex:0 0 auto; width:20px; height:20px; color:#fff;
}
.lb .arrow-up { animation:lbbump .8s ease-in-out 3; }
@keyframes lbbump { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-4px)} }

/* abilities + image */
.lb .abilgrid { display:grid; grid-template-columns:1fr 1fr; gap:8px; align-content:start; }
.lb .abil { border:2px solid var(--edge); background:var(--sheet2); padding:8px 8px 10px; text-align:center; position:relative; }
.lb .abil .nm { font-size:16px; color:var(--ink3); font-weight:800; margin-bottom:2px; line-height:1.15; }
html[dir="rtl"] .lb .abil .nm { font-size:14px; }
.lb .abil .vl { font-family:'Frank Ruhl Libre',serif; font-size:38px; font-weight:900; line-height:1; }
.lb .abil .mod { font-size:16px; color:var(--gold); font-weight:800; margin-top:2px; }
.lb .abil .plus { display:none; position:absolute; top:6px; inset-inline-end:6px; font-size:16px;
  border:2px solid var(--gold); color:var(--gold); padding:0 7px; cursor:pointer; font-weight:800; }
.lb .abil .plus.on { display:inline-block; }
.lb .portrait { border:2px dashed var(--edge); background:var(--sheet2); min-height:230px;
  display:flex; flex-direction:column; align-items:center; justify-content:center; color:var(--ink3);
  text-align:center; gap:8px; cursor:pointer; overflow:hidden; font-size:16px; position:relative; }
.lb .portrait .up, .lb .shop-banner .up {
  display:flex; flex-direction:column; align-items:center; justify-content:center; gap:4px;
  line-height:1.25; font-weight:700;
}
.lb .portrait .up__t, .lb .shop-banner .up__t { font-size:16px; color:var(--ink2); }
.lb .portrait .up__h, .lb .shop-banner .up__h { font-size:14px; color:var(--ink3); font-weight:600; }
/* The picture fills the slot from OUTSIDE the flow. In the flow, height:100% on
   a flex item inside an auto-height column has nothing to resolve against, so the
   browser falls back to the image's natural height and a tall picture stretches
   the whole card. Item and spell cards were still doing that; character and foe
   cards were fixed the same way (see .charpic img below) and keep their own rule. */
.lb .portrait img { position:absolute; inset:0; width:100%; height:100%; object-fit:contain; }
.lb .portrait.is-gen, .lb .shop-banner.is-gen { border-style:solid; border-color:var(--gold2); cursor:default; }
.lb .portrait.is-gen .up, .lb .shop-banner.is-gen .up { color:var(--gold); }
.lb .portrait.has-img, .lb .shop-banner.has-img { cursor:zoom-in; border-style:solid; }
/* A player's empty picture (app.js imgSlotInner): the eye (the bottom bar's Home icon), faint, in the middle. */
.lb .gm-nopic { display:block; width:36%; max-width:110px; aspect-ratio:1; background:var(--ink3); opacity:.22;
  -webkit-mask:url(/growlmaster/icons/pin-open.svg) center/contain no-repeat; mask:url(/growlmaster/icons/pin-open.svg) center/contain no-repeat; }
.lb .portrait:has(> .gm-nopic), .lb .shop-banner:has(> .gm-nopic) { cursor:default; }
/* Corner pencil to change an existing picture (tap the image itself still enlarges). */
.lb .shop-banner { position:relative; }
.lb .img-edit { position:absolute; top:6px; inset-inline-end:6px; z-index:3;
  width:32px; height:32px; display:inline-flex; align-items:center; justify-content:center;
  padding:0; cursor:pointer; border:2px solid var(--edge); border-radius:6px;
  background:var(--sheet); color:var(--ink); box-shadow:0 1px 3px rgba(58,44,23,.35); }
.lb .img-edit:hover { background:var(--sheet2); border-color:var(--gold); color:var(--gold-dk); }
.lb .img-edit:active { transform:scale(.94); }
.lb .img-spin {
  width:28px; height:28px; box-sizing:border-box; flex-shrink:0;
  border:3px solid rgba(179,45,31,.2); border-top-color:var(--gold);
  border-radius:50%; animation:gmSpin .75s linear infinite;
}
.lb .img-spin--sm { width:16px; height:16px; border-width:2px; }
@keyframes gmSpin { to { transform:rotate(360deg); } }

/* cards */
.lb .card { border:2px solid var(--edge); background:var(--sheet2); padding:12px 14px; margin:0 0 10px; }
.lb .card.big { min-height:230px; }
.lb .sect-t { font-size:16px; color:var(--gold); font-weight:800; text-transform:uppercase; letter-spacing:.04em; margin:0 0 8px; text-align:center; }
.lb .ab { display:flex; align-items:center; gap:8px; padding:6px 2px; border-bottom:1.5px dotted var(--edge); font-size:16px; }

/* =====================================================================
   Shared table (SoT — /growlmaster/sot). Borderless, stripe zebra, dense
   sans, charcoal ink. Use for battle roster, shop, attacks, equipment…
   Markup:
     <div class="gm-table">
       <table class="gm-table__t">…</table>
     </div>
   Optional cells: .gm-table__pic · .gm-table__name · .gm-table__price · .gm-table__menu
   Clickable rows: .gm-table__row  |  Thumbs: .gm-table__img[.gm-table__img--empty|.gm-table__img--item]
   ===================================================================== */
.lb .gm-table {
  width:100%; max-width:100%; box-sizing:border-box;
  background:transparent; border:0;
  overflow-x:auto; overflow-y:visible; -webkit-overflow-scrolling:touch;
}
.lb .gm-table__t {
  width:max-content; min-width:100%; max-width:none; border-collapse:collapse; table-layout:auto;
  font-family:'Grandstander', system-ui, sans-serif; font-size:15px; color:var(--ink);
}
.lb .gm-table__t th {
  text-align:start; font-size:14px; color:var(--ink); font-weight:700;
  text-transform:none; letter-spacing:0; padding:4px 12px 10px;
  border:0; border-bottom:2px solid var(--edge); background:transparent; white-space:nowrap;
  font-family:'Grandstander', system-ui, sans-serif;
}
.lb .gm-table__t th.gm-table__pic,
.lb .gm-table__t td.gm-table__pic { width:72px; }
.lb .gm-table__t th.gm-table__menu,
.lb .gm-table__t td.gm-table__menu { width:48px; text-align:end; padding-inline-end:16px; }
.lb .gm-table__row { cursor:pointer; transition:background .12s; }
/* App shell scales [role=button]:active — don't shrink table rows when tapping a cell value. */
.lb .gm-table__t tr[role="button"]:active,
.lb .gm-table__row:active { transform:none; }
.lb .gm-table__t tbody tr:nth-child(odd) { background:var(--stripe); }
.lb .gm-table__t tbody tr:nth-child(even) { background:var(--stripe2); }
.lb .gm-table__t tbody tr:nth-child(odd) td { border-bottom:1px solid var(--stripe-line); }
.lb .gm-table__t tbody tr:nth-child(even) td { border-bottom:1px solid var(--stripe2-line); }
/* THE EMPTY ROW BY ITS CLASS, NOT :has() (2026-10-25: typing in a sheet cost ~60ms a letter; these :has rules, with the
   two below, put Chrome into re-styling the whole page on every keystroke; ~17ms without them). Keep :has out of
   rules that match many elements in the app. */
.lb .gm-table__t tbody tr.gm-table__emptyrow,
.lb .gm-table__t tbody tr.gm-table__emptyrow:nth-child(odd),
.lb .gm-table__t tbody tr.gm-table__emptyrow:nth-child(even) { background:transparent; }
.lb .gm-table__t tbody tr.gm-table__emptyrow td { border-bottom:0; }
.lb .gm-table__row:hover, .lb .gm-table__row:focus-visible,
.lb .gm-table__t tbody tr:hover:not(.gm-table__emptyrow) { background:#fff; outline:none; }
.lb .gm-table__t tbody tr.gm-table__emptyrow { cursor:default; }
.lb .gm-table__t td {
  padding:7px 12px; border:0; vertical-align:middle;
  text-align:start; white-space:nowrap; line-height:1.3;
}
.lb .gm-table__t td.gm-table__pic { padding:8px 10px; }
.lb .gm-table__t .gm-table__menu .gm-btn { margin:0; }
.lb .gm-table__t .gm-table__menu .gm-btn {
  border-color:var(--edge); background:var(--sheet); color:var(--ink); opacity:1;
  min-width:36px; min-height:36px; padding:6px 8px; font-size:20px; line-height:1;
}
.lb .gm-table__t tr:hover .gm-table__menu .gm-btn,
.lb .gm-table__t tr:focus-within .gm-table__menu .gm-btn {
  border-color:var(--line); background:var(--sheet2); color:var(--ink);
}
.lb .gm-table__img {
  width:64px; height:64px; max-width:64px; max-height:64px;
  object-fit:cover; display:block; border:0; background:transparent;
}
.lb .gm-table__img--item {
  width:48px; height:36px; max-width:48px; max-height:36px;
  border:2px solid var(--edge); background:var(--sheet2);
}
.lb .gm-table__img--empty { box-sizing:border-box; background:rgba(203,185,143,.35); }
/* Thumbnail while its picture is generating: centre the little spinner. */
.lb .gm-table__img--gen { display:inline-flex; align-items:center; justify-content:center; }
.lb .gm-table__img--item.gm-table__img--empty { background:var(--sheet3); }
.lb .gm-table__img--gen {
  display:flex; align-items:center; justify-content:center;
  background:var(--sheet3); border-color:var(--gold2);
}
.lb .gm-table__name { font-weight:700; font-size:16px; color:var(--ink); }
.lb .gm-table__price { color:var(--gold); font-weight:800; white-space:nowrap; }
.lb .gm-table__t td.hit { font-weight:700; color:var(--ink); }
.lb .gm-table__t td.gm-table__empty {
  padding:14px 12px; color:var(--ink2); font-size:14px; white-space:normal;
  text-align:center;
}

/* Full-bleed list tables when the pane is tight */
.lb .shop-scroll > .gm-table { padding:16px 18px 0; box-sizing:border-box; }
@container pane (max-width:900px){
  .lb .monbody--table { padding-inline:0; }
  .lb .shop-scroll > .gm-table { padding-inline:0; }
}

/* THE button (.gm-btn, built by B() in app.js) is defined once, in public/assistant/assistant.css:
   the Jim assistant's panel draws its buttons with the same one. Loaded before this file. */
/* contextual spacing for stand-alone action buttons (layout, not button look) */
.lb .card > .gm-btn, .lb .page.col > .gm-btn, .lb-box > .gm-btn { margin-top:10px; }
.lb .toolbar .gm-btn, .lb .btoolbar .gm-btn { margin-top:0; }

/* transient status note (save failures etc.) — body-level, above overlays */
.gm-toast { position:fixed; left:50%; bottom:20px; transform:translateX(-50%) translateY(12px);
  background:var(--ink); color:var(--paper); border:2px solid var(--ink); padding:11px 16px;
  font-family:'Grandstander',system-ui,sans-serif; font-weight:700; font-size:15px; line-height:1.35;
  max-width:min(92vw,420px); text-align:center; opacity:0; pointer-events:none; z-index:90;
  transition:opacity .18s ease, transform .18s ease; }
.gm-toast.show { opacity:1; transform:translateX(-50%) translateY(0); }

/* THE chevron on a button that opens a list (lib/ui/icons.js "down", app.js chev()). */
.gm-chev { display:inline-flex; vertical-align:middle; margin-inline-start:6px; }
.gm-chev svg { width:1em; height:1em; }
/* THE NUMBER STEPPER (app.js numStep, every sheet's number field): −1 and +1 first in the field's own row under it
   (.gm-edbar, Goblin's), never over its digits (Maayan, 2026-10-25); the row's ✓ and ✕ only once the number changed */
.gm-edbar__btn.gm-step { background:var(--sheet); border:2px solid var(--edge); color:var(--ink); }
.gm-edbar__btn.gm-step .gm-chev { margin:0; }
/* ✓ AND ✕ UNDER A SHEET FIELD BEING EDITED (app.js edBar, Maayan 2026-10-25: nothing saves by itself): in the flow under
   the field, so it never covers the next one; circles at a real touch size, in the icons' stroke; ✓ GrowlMaster's gold.
   A field waiting on them has a dashed underline. RTL and LTR both: the row centred under the field (clear of the seat circles at the screen's edge). */
.gm-edbar { display:flex; align-items:center; gap:8px; margin:6px 0 4px; justify-content:center; width:100%; flex-basis:100%; }
.gm-edbar .gm-edbar__btn, .lb .gm-edbar .gm-edbar__btn, body.jim-app .gm-edbar button.gm-edbar__btn { width:44px; height:44px; min-width:44px; min-height:44px; border-radius:50%;
  display:grid; place-items:center; padding:0; cursor:pointer; border:2px solid var(--edge, #cbb98f); background:var(--sheet, #faf5e8); color:var(--ink, #3a2c17); box-shadow:0 2px 6px rgba(0,0,0,.15); }
.gm-edbar .gm-edbar__btn svg { width:22px; height:22px; }
.gm-edbar .gm-edbar__btn.gm-edbar__ok, .lb .gm-edbar .gm-edbar__btn.gm-edbar__ok, body.jim-app .gm-edbar button.gm-edbar__btn.gm-edbar__ok { background:var(--gold, #b8860b); border-color:var(--gold, #b8860b); color:#fff; }
.gm-edbar .gm-edbar__btn:hover, .gm-edbar .gm-edbar__btn:focus-visible { filter:brightness(1.06); }
.gm-edbar .gm-edbar__btn:active { transform:translateY(1px); }
.ed.is-draft, [data-ed].is-draft, input.is-draft { text-decoration:underline dashed var(--gold, #b8860b) 2px; text-underline-offset:4px; }
/* the number stepper's row before a change: −1 and +1 only (its ✓ and ✕ once the number is a draft; app.js numStep) */
/* ✓ and ✕ only on a row whose field has changed (app.js draftable marks the row: after a pair it follows the pair) */
.gm-edbar:not(.gm-edbar--draft) > .gm-edbar__btn[data-edbar] { display:none; }
/* a narrow cell (a phone's two-column sheet): the row goes on two lines inside it, −1 +1 over ✓ ✕, never out over the
   cell beside it (Maayan, 2026-10-25: nothing covers the sheet); the buttons keep their size */
.gm-edbar { flex-wrap:wrap; max-width:100%; box-sizing:border-box; }
@media (prefers-reduced-motion:reduce){ .gm-toast { transition:opacity .18s ease; transform:translateX(-50%); } }

/* context menu — above .lb-ovl (70) so ⋮ menus work inside modals */
.lb-menu { position:fixed; z-index:75; background:var(--sheet); border:2px solid var(--ink);
  padding:5px; min-width:180px; font-family:'Grandstander',system-ui,sans-serif; }
.lb-menu button { display:block; width:100%; text-align:start; background:none; border:0; padding:8px 12px;
  font:inherit; font-size:16px; font-weight:600; cursor:pointer; color:var(--ink); }
.lb-menu button:hover { background:var(--sheet2); }
.lb-menu button.dan { color:var(--red); }
.lb-menu hr { border:0; border-top:1.5px solid var(--edge); margin:4px 2px; }

/* Guest account popover (header): shows the free daily credits + a sign-up CTA.
   Its own box (not .lb-menu) so the .gm-btn inside keeps its real button look. */
.gm-guestmenu { position:fixed; z-index:75; background:var(--sheet); border:2px solid var(--ink);
  box-shadow:0 8px 24px rgba(0,0,0,.22); padding:12px; min-width:216px;
  font-family:'Grandstander',system-ui,sans-serif; color:var(--ink); }
.gm-guestmenu__row { display:flex; align-items:baseline; justify-content:space-between; gap:12px; }
.gm-guestmenu__k { font-weight:800; font-size:15px; }
.gm-guestmenu__v { font-weight:800; font-size:15px; color:var(--ink2); }
.gm-guestmenu__hint { font-size:12.5px; line-height:1.4; color:var(--ink3); margin:6px 0 12px; }

.lb .stub { margin:auto; text-align:center; color:var(--ink3); font-size:18px; }
.lb .stub .big { font-size:56px; }

/* ---------------- shared bits ---------------- */
.lb .mini { font-size:16px; color:var(--ink3); }
.lb .sel { font:inherit; font-size:16px; padding:6px 8px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); max-width:100%; }
.lb .page.col, .lb .chat { display:flex; flex-direction:column; }
.lb .chatin { display:flex; gap:8px; margin-top:10px; }
.lb .chatin input { flex:1; min-width:0; font:inherit; font-size:16px; padding:8px 10px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); }

/* ---------------- adventure: paginated script + suggestions ---------------- */
/* The report after a file's cards come into the game. */
.loadreport { margin:0; padding-inline-start:20px; display:flex; flex-direction:column; gap:6px; }
/* The bare card, only while snippet-card.js is missing. */
.lb .gm-snipshell { display:flex; align-items:center; gap:10px; min-height:7.5em; box-sizing:border-box; padding:10px 12px;
  background:var(--sheet3); border:1px solid var(--gold2); border-inline-start:6px solid var(--accent); }
.lb .gm-snipshell__kind { font-size:.75em; font-weight:700; text-transform:uppercase; letter-spacing:.06em; }
.lb .gm-snipshell__name { flex:1; }
.lb .gm-snipshell__open { margin-inline-start:auto; width:36px; height:36px; border:0; border-radius:50%; background:var(--accent); color:#fff; font-size:18px; cursor:pointer; }
/* suggestions column — bottom inset matches editorwrap so last card isn't clipped */
.lb .adv-sugs {
  flex:0 0 320px; max-width:320px; align-self:stretch;
  display:flex; flex-direction:column; min-height:0; overflow:hidden;
  padding:28px 4px 16px 0; box-sizing:border-box;
}
.lb .adv-sugs__head { display:flex; align-items:center; gap:8px; margin-bottom:10px; flex:0 0 auto; }
.lb .adv-sugs__head h3 { font-size:20px; flex:1; min-width:0; }
.lb .adv-sugs__head .gm-btn { margin-top:0; }
.lb .adv-sugs__list {
  flex:1 1 auto; min-height:0; overflow:auto; overscroll-behavior:contain;
  display:flex; flex-direction:column; gap:12px;
  padding:0 2px 8px; /* room past last card so border + buttons clear the clip edge */
  -webkit-overflow-scrolling:touch;
}
.lb .adv-sugs__empty { padding:4px 2px; }
.lb .adv-sug {
  flex:0 0 auto; /* never shrink — scroll the list instead of clipping the card */
  border:2px solid var(--edge); background:#faf5e8; padding:14px 16px; box-sizing:border-box;
}
.lb .adv-sug__text { font-size:16px; color:var(--ink); line-height:1.55; margin-bottom:12px; }
.lb .adv-sug__a { display:flex; gap:8px; }
.lb .adv-sug__a .gm-btn { margin-top:0; flex:1; min-width:0; justify-content:center; }
/* THE ADVENTURE TAB IS JIM DOCS (app.js "ADVENTURES ARE JIM DOCS"): one dock,
   made once and laid over whichever pane shows the tab; the pane's own stage
   steps aside while it is there. GrowlMaster's bar over a doc, the frame, and
   the Suggestions panel beside it. */
.lb-stage.lb-stage--under { display:none; }
.lb .advdock { flex:1 1 auto; min-height:0; display:flex; flex-direction:column; background:var(--paper); }
/* Its frame unseen over the host colour until the frame says it is drawn (frameDock said), then seen in one step. */
.lb .advdock--wait .advdock__frame { visibility:hidden; }
.lb .advdock[hidden] { display:none; }
/* Not its tab (frameDock sync): over its pane, laid out at the size it is seen at, unseen. */
.lb-pane--dockaway { position:relative; }
.lb .advdock--away { position:absolute; inset:0; visibility:hidden; pointer-events:none; z-index:-1; overflow:hidden; }   /* away, its bar never widens the page (a phone's sideways scroll, 2026-10-26) */
.lb .advdock__bar { flex:0 0 auto; }
.lb .advdock__bar[hidden] { display:none; }
/* The item's name, first in the bar: tap to edit (frameDock). Always whole (Maayan saw "Untitled blackbo…"): as wide as
   its words, and the buttons go to the next row when they don't fit beside it. */
.lb .advdock__name { flex:0 1 auto; min-width:6em; max-width:100%; field-sizing:content; margin:0; padding:4px 8px; border:2px solid transparent; background:transparent;
  font:700 18px/1.2 'Frank Ruhl Libre', Georgia, serif; color:var(--ink); text-overflow:ellipsis; }
.lb .advdock__name:hover { border-color:var(--edge); }
.lb .advdock__name:focus { outline:none; border-color:var(--gold); background:var(--sheet); }
.lb .advdock__pin:empty { display:none; }
/* The Adventure's chevron: points down while the editing tools are folded away, turns to point up as they slide down. */
.lb .advdock__chev .gm-btn__i svg { display:block; width:20px; height:20px; transition:transform .28s ease; }
.lb .advdock__chev[aria-pressed="true"] { background:var(--gold); border-color:var(--gold); color:#fff; }
@media (prefers-reduced-motion:reduce){ .lb .advdock__chev .gm-btn__i svg { transition:none; } }
.lb .advdock__acts { display:flex; align-items:center; gap:8px 10px; margin-inline-start:auto; flex:0 0 auto; }
/* On a phone the name has its own row, whole; the Pin and the buttons under it. */
@media (max-width:520px){
  .lb .advdock__bar { flex-wrap:wrap; }
  .lb .advdock__name { flex:1 1 100%; }
  .lb .advdock__acts { gap:6px; margin-inline-start:0; }
  .lb .maptoolbar.advdock__bar { gap:8px 6px; padding-inline:10px; }   /* the Pin and two buttons in one row under the name */
}
.lb .advdock__cols { flex:1 1 auto; min-height:0; display:flex; gap:0; }
.lb .advdock__frame { flex:1 1 auto; min-width:0; width:100%; height:100%; border:0; display:block; background:var(--paper); }
.lb .advdock .adv-sugs { flex:0 0 300px; max-width:42%; border-inline-start:2px solid var(--edge); padding:12px; overflow:auto; }
.lb .advdock .adv-sugs[hidden] { display:none; }
/* A narrow pane: the panel goes BELOW the doc, and the doc stays on screen and
   editable (the panel works on its selection and cursor). */
@container pane (max-width:640px){
  .lb .advdock__cols { flex-direction:column; }
  .lb .advdock__frame { flex:1 1 55%; min-height:240px; height:auto; }
  .lb .advdock .adv-sugs { flex:0 1 45%; min-height:0; max-width:none; border-inline-start:0; border-top:2px solid var(--edge); overflow:hidden; padding:8px 12px 0; }
  .lb .adv-sugs__list { overflow:auto; flex:1; min-height:0; padding-bottom:4px; }
}

/* ---------------- battle (foes) ----------------
   Sticky header flush to stage top; roster table with real horizontal scroll;
   review-before-add; archive gallery. Old grid/dice CSS kept below. */
/* THE LIBRARY BELONGS IN THIS LIST AND WAS NOT IN IT. Base .lb-stage is
   display:flex with the default row direction, so a screen that does not join
   this list is a row flex container and its content shrink-wraps: one Goblin
   in an 875px pane drew a 420px row with empty parchment beside it. It reads
   as a width cap on the row; the row is innocent, the pane hugs the list. */
.lb-stage.lb-stage--adv, .lb-stage.lb-stage--foes, .lb-stage.lb-stage--shop, .lb-stage.lb-stage--map, .lb-stage.lb-stage--locs, .lb-stage.lb-stage--detail, .lb-stage.lb-stage--lib { padding:0; align-items:stretch; flex-direction:column; }
/* Full bleed is right for the map and the foe list, but the adventure view is a
   page of writing: without side room the header, the toolbar and the suggestions
   column all ran flush into the pane border. Inline only, so the sticky toolbar
   inside still reaches the top. */
.lb-stage.lb-stage--adv { padding-inline:14px; }
.lb .monstack { width:100%; max-width:none; display:flex; flex-direction:column; gap:0; padding:0; margin:0; align-self:stretch; box-sizing:border-box; min-width:0; flex:1; min-height:0; }
.lb .monstack--review { height:100%; }
.lb .montoolbar.btoolbar {
  position:sticky; top:0; z-index:6;
  background:var(--paper); border-bottom:2px solid var(--edge);
  padding:12px 18px; margin:0; flex-wrap:nowrap; gap:10px; align-items:center;
  box-sizing:border-box; width:100%; flex:0 0 auto;
}
.lb .montoolbar__title {
  font-size:28px; font-weight:700; margin:0; line-height:1.1; white-space:nowrap;
  color:var(--burgundy); text-transform:uppercase; letter-spacing:.02em;
}
.lb .monbody { padding:18px 18px 28px; box-sizing:border-box; width:100%; min-width:0; }
/* Battle's two top-level pages. Unlike .adv-tabs (which only appears when the pane is too
   narrow for both columns) this switch is the tab's navigation, so it shows at every
   width. Logical borders keep the segment order correct in Hebrew. */
/* A title and up to four controls do not fit one line in a narrow pane. */
.lb .montoolbar.btoolbar--wrap { flex-wrap:wrap; row-gap:8px; }
.lb .bt-board { --bt-bar-h:272px;
  position:relative; flex:1; min-height:0; display:flex; flex-direction:column; gap:0;
  padding:0; overflow:hidden; }
/* The board's own section. It clips here, below the card, so a zoomed or dragged board
   can never draw over the details above it. No transform of its own: the clip has to
   happen in plain screen coordinates. */
.lb .bt-view { position:relative; flex:1; min-width:0; min-height:0; overflow:hidden;
  display:flex; cursor:grab; touch-action:none; }
.lb .bt-view.is-panning { cursor:grabbing; }
.lb .bt-view.is-panning .bt-cell, .lb .bt-view.is-panning .bt-pc { cursor:grabbing; }
/* What the board is waiting for, said once and said large. */
.lb .bt-hint { position:absolute; left:50%; top:calc(var(--bt-bar-h) + 52px); transform:translateX(-50%);
  z-index:5; pointer-events:none; max-width:calc(100% - 36px); text-align:center;
  padding:8px 20px; box-sizing:border-box;
  background:var(--paper); border:2px solid var(--edge);
  font-family:'Frank Ruhl Libre', Georgia, serif; font-weight:700; font-size:26px; color:var(--ink); }
.lb .bt-hint[hidden] { display:none; }
/* Rides with the cursor, so the count is where the eye already is. */
.lb .bt-steps { position:fixed; z-index:60; pointer-events:none;
  transform:translate(-50%, 22px); padding:3px 10px;
  background:var(--ink); color:var(--paper); font-weight:700; font-size:15px;
  direction:ltr; unicode-bidi:isolate; }
.lb .bt-steps.is-no { background:var(--red); }
.lb .bt-steps[hidden] { display:none; }
/* Who is selected. Empty when nothing is. */
/* The selected figure's card. Fixed height, always: an empty card, a short name and a
   long one all leave the board exactly the same room, so nothing below it moves or has
   to be refitted. One card shape for heroes and foes, because at the table they are the
   same kind of thing. */
/* The top section: its own inset, and a rule under it so the board below reads as a
   separate surface rather than more of the same page. */
.lb .bt-selbar { flex:0 0 auto; height:var(--bt-bar-h); box-sizing:content-box;
  padding:14px 18px; border-bottom:2px solid var(--line);
  /* CENTRED, both states. One card alone sits in the middle, and in the attack view the
     two cards and the die column between them sit as a group in the middle. Without this
     the row started at the left edge and the cards grew to fill it, which pushed them
     hard to either side and stretched the middle column between them. */
  display:flex; align-items:stretch; justify-content:center; gap:12px; }
/* A card, not a banner: it keeps its own width whatever the pane does. THE RULE NOW SAYS
   THAT. It read flex:1 1 auto, which grows to fill, so the comment and the rule disagreed
   and the rule won: a card stretched to whatever room the pane had, up to its cap. 0 1
   auto is the sentence above written as CSS. It may still shrink, because a narrow phone
   has to put it somewhere, and it no longer reaches for space it does not need. */
.lb .bt-card { height:100%; flex:0 1 auto; max-width:470px; box-sizing:border-box;
  display:flex; flex-direction:column; gap:10px;
  padding:10px; border:3px solid var(--gold2); border-radius:8px; background:var(--sheet2); }
/* Nobody chosen: the line sits in the middle of the whole strip, on one line. A pane too
   narrow for it trims the end rather than stacking words. */
.lb .bt-selbar.is-empty { align-items:center; justify-content:center; }
.lb .bt-selbar__hint { max-width:100%; padding:0 12px; color:var(--ink3); font-size:19px;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
/* EVERY CARD IS OUTLINED IN ITS OWN SIDE, not just the one being attacked. This used to
   be .bt-card--foe alone, which is why a party card had the plain gold edge and a foe
   card looked marked even when nothing was happening to it.
   Keyed off data-bt-side, the same attribute the action buttons use, so a side has one
   colour across the whole board. The kind class is still emitted and still means where
   the stats live; a JS selector finds the foe's card by it.
   4px ON ALL THREE rather than only the foe. A thinner edge on the party card would
   read as the party mattering less, which is nobody's decision. */
.lb .bt-card[data-bt-side] { border-width:4px; }
.lb .bt-card[data-bt-side="foe"]   { border-color:var(--red); }
.lb .bt-card[data-bt-side="party"] { border-color:var(--green); }
.lb .bt-card[data-bt-side="ally"]  { border-color:var(--blue); }
.lb .bt-card[data-bt-side="object"] { border-color:var(--grey); }
/* The round arrow that pops a fighter out to the other panel. Its own rule paints it red
   for every use in the product, snippets included, so this overrides it INSIDE a card
   rather than changing the shared button. */
.lb .bt-card[data-bt-side="foe"]   .gm-openpanel { background:var(--red); }
.lb .bt-card[data-bt-side="party"] .gm-openpanel { background:var(--green); }
.lb .bt-card[data-bt-side="ally"]  .gm-openpanel { background:var(--blue); }
.lb .bt-card[data-bt-side="object"] .gm-openpanel { background:var(--grey); }
/* The word, beside the name. Small and quiet: it is a label, not a heading, and the name
   is still the thing you read first. Measured on the card's own ground: red 5.29,
   green 5.10, blue 5.39, all clear of 4.5 and within a third of a point of each other so
   no side reads louder than another. */
.lb .bt-card__side { flex:0 0 auto; font-size:12px; font-weight:800; letter-spacing:.04em;
  text-transform:uppercase; white-space:nowrap; }
.lb .bt-card[data-bt-side="foe"]   .bt-card__side { color:var(--red); }
.lb .bt-card[data-bt-side="party"] .bt-card__side { color:var(--green); }
.lb .bt-card[data-bt-side="ally"]  .bt-card__side { color:var(--blue); }
.lb .bt-card[data-bt-side="object"] .bt-card__side { color:var(--grey); }
.lb .bt-card__top { flex:1; min-height:0; display:flex; gap:0;
  border:2px solid var(--edge); background:var(--paper); }
.lb .bt-card__pic { flex:0 0 132px; border-inline-end:2px solid var(--edge);
  display:grid; place-items:center; overflow:hidden; }
.lb .bt-card__pic img { width:100%; height:100%; object-fit:contain; display:block; }
.lb .bt-card__glyph { font-family:'Frank Ruhl Libre', Georgia, serif; font-weight:900;
  font-size:26px; color:var(--ink3); }
.lb .bt-card__id { flex:1; min-width:0; min-height:0; overflow:hidden;
  display:flex; flex-direction:column; padding:10px 16px 12px; box-sizing:border-box; }
.lb .bt-card__line { flex:0 0 auto; display:flex; align-items:baseline; gap:10px; min-width:0; }
.lb .bt-card__name { flex:1 1 auto; min-width:0; font-size:29px; line-height:1.15; color:var(--ink);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.lb .bt-card__lvl { margin-inline-start:auto; flex:0 0 auto; white-space:nowrap;
  font-weight:700; font-size:23px; color:var(--ink); }
.lb .bt-card__lvl i { font-style:normal; font-size:15px; color:var(--ink3); }
.lb .bt-card__sub { flex:0 0 auto; margin-top:1px; font-size:18px; color:var(--ink3);
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.lb .bt-card__stats { margin-top:auto; padding-top:8px; display:flex; align-items:flex-end; gap:26px; }
.lb .bt-card__stat { display:flex; flex-direction:column; align-items:center; gap:3px; }
.lb .bt-card__box { min-width:124px; padding:4px 14px; box-sizing:border-box; text-align:center;
  border:2px solid var(--ink); background:var(--paper);
  font-size:29px; font-weight:700; line-height:1.15; cursor:text; }
.lb .bt-card__box:focus { outline:none; border-color:var(--gold2); background:var(--sheet); }
/* The armour number lives inside the shield outline. */
.lb .bt-card__shield { position:relative; display:grid; place-items:center;
  width:52px; height:59px; color:var(--ink); }
.lb .bt-card__shieldsvg { position:absolute; inset:0; width:100%; height:100%; }
.lb .bt-card__shield b { position:relative; font-size:26px; font-weight:700; line-height:1;
  padding-bottom:4px; }
.lb .bt-card__lbl { font-size:15px; line-height:1.2; color:var(--ink3); font-weight:700; white-space:nowrap; }
.lb .bt-card__bot { flex:0 0 82px; display:flex; align-items:stretch; gap:0;
  border:2px solid var(--edge); background:var(--paper); }
.lb .bt-card__wpic { flex:0 0 82px; border-inline-end:2px solid var(--edge);
  display:grid; place-items:center; overflow:hidden; }
.lb .bt-card__wpic img { width:100%; height:100%; object-fit:contain; display:block; }
.lb .bt-card__atk { flex:1; min-width:0; display:flex; flex-direction:column; justify-content:center;
  gap:1px; padding:0 16px; }
.lb .bt-card__dmg { font-size:29px; line-height:1.15;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.lb .bt-card__bot .gm-btn { align-self:center; margin-inline-end:10px; flex:0 0 auto; }
/* Choosing a target swaps the buttons for the swing itself: two cards facing each
   other with the roll between them. */
/* Two cards, one card component. The only thing duel mode changes is that they share
   the row: same type, same spacing, same shield, same everything. */
.lb .bt-selbar.is-duel .bt-card { min-width:0; }
.lb .bt-duel { flex:0 0 200px; min-width:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:8px; }
/* .bt-back is gone from the attack panel with the button it dressed. The class is left
   defined because the strip menu's own back control still uses it. */
.lb .bt-back { align-self:flex-start; background:none; border:0; padding:2px 4px;
  font:inherit; font-weight:700; font-size:17px; color:var(--ink2); cursor:pointer; }
.lb .bt-back:hover { color:var(--ink); }
/* WHAT TO DO, at the top of the column and the biggest thing in it. It is not a button
   and must not look like one: nothing in this app rolls a die, a person picks one up, and
   the panel is telling them to. Which die it asks for lives on BT_DIE in app.js. */
.lb .bt-roll { display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:6px; max-width:100%; font-weight:800; font-size:26px; line-height:1.1; color:var(--ink); }
/* THE DIE SITS ABOVE THE WORDS AND IS THE BIGGEST THING IN THE COLUMN, because it is the
   object you are being told to pick up rather than a glyph decorating a label.
   THE SIZE IS THE COLUMN'S BUDGET RATHER THAN A NUMBER I LIKED. The panel is 272px tall
   and the rest of this column costs about 125: the title at 29, the needs line at 28, the
   arrow at 52, and the gaps. 80px leaves the arrow where it was with room to spare, and
   the next size up starts eating into it. */
.lb .bt-roll .bt-act__i { flex:0 0 auto; width:80px; height:80px; }
/* Their own d20 in the same 80px (dice.js dock): the panel keeps its size whether the die is there or in the hand. */
.lb .bt-d20 { flex:0 0 auto; width:80px; height:80px; display:grid; place-items:center; --dock-scale:1.22; }   /* the die a little larger than its place (Maayan), the place the same */
.lb .bt-d20 > .bt-act__i { width:80px; height:80px; }
.lb .bt-roll__t { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
/* The arrow points at whoever is about to be hit, and turns around in Hebrew. */
.lb .bt-go { display:flex; align-items:center; justify-content:center; gap:10px;
  width:100%; min-height:52px; padding:0 26px 0 16px; box-sizing:border-box;
  border:0; background:var(--gold-deep,#7A4A17); color:#fff;
  font:inherit; font-weight:700; font-size:20px; cursor:pointer;
  clip-path:polygon(0 0, calc(100% - 16px) 0, 100% 50%, calc(100% - 16px) 100%, 0 100%); }
html[dir="rtl"] .lb .bt-go { padding:0 16px 0 26px;
  clip-path:polygon(16px 0, 100% 0, 100% 100%, 16px 100%, 0 50%); }
.lb .bt-go:hover { filter:brightness(1.08); }
.lb .bt-go .bt-act__i { width:26px; height:26px; }
/* The line is a button: tapping it shows where the number came from, the same box the
   sheet opens on any of its numbers. */
.lb .bt-need { font:inherit; font-size:20px; color:var(--ink); text-align:center;
  background:none; border:0; padding:2px 6px; cursor:pointer;
  border-bottom:2px dotted var(--edge); }
.lb .bt-need:hover { border-bottom-color:var(--ink3); }
.lb .bt-need b { font-size:24px; border-bottom:2px solid var(--ink); }
/* Panels arrive from the side they came from, so Back visibly walks it back. */
@keyframes btInFwd { from { opacity:0; transform:translateX(18px); } to { opacity:1; transform:none; } }
@keyframes btInBack { from { opacity:0; transform:translateX(-18px); } to { opacity:1; transform:none; } }
.lb .bt-in-fwd { animation:btInFwd .22s ease-out both; }
.lb .bt-in-back { animation:btInBack .22s ease-out both; }
@media (prefers-reduced-motion: reduce){
  .lb .bt-in-fwd, .lb .bt-in-back { animation:none; }
}
/* One button per thing a turn can be. They do nothing yet: each lands with its own
   rules. */
/* Five buttons at the app's 44px minimum tap target, plus these gaps, IS the bar's
   height. Shrinking the targets to fit would be the wrong way round. */
.lb .bt-actions { flex:0 0 176px; display:flex; flex-direction:column; gap:5px; }
.lb .bt-act { flex:1; min-height:0; display:flex; align-items:center; gap:10px;
  padding:0 12px; box-sizing:border-box; font:inherit; font-weight:700; font-size:17px;
  border:2px solid var(--ink); background:var(--paper); color:var(--ink);
  cursor:pointer; text-align:start; -webkit-tap-highlight-color:transparent; }
/* WHOSE TURN IT IS, BEFORE YOU READ A WORD. The action buttons take the colour of the
   SIDE of whoever is selected, from the same palette entries the cell tints are mixed
   from, so a side has one colour in this product rather than two that drift apart.
   It used to be a tone on each action, and every action said "red". That was the wrong
   home for it: the colour is not a property of Move or Attack, it is a property of who
   is about to move or attack. It is set once on the container now, so the four buttons
   cannot disagree with one another.
   THE ALLY BRANCH IS WRITTEN AND UNREACHABLE. btSideOf cannot return "ally" until a
   piece can carry a side of its own, and nothing fakes it with a flag. It is here so
   that the day the side lands, this file needs no change at all.
   Measured on the painted result, white on each: red 6.34, green 6.11, blue 6.46.
   AND IT IS data-bt-side, NOT data-side. This stylesheet already uses data-side on
   .lb-rail and .lb-pane to mean WHICH PANE, left or right. Reusing it here would make
   one attribute name mean two unrelated things in one product, which is the collision
   that has cost this codebase a day more than once. The bt- prefix is what everything
   else on this board already does. */
.lb .bt-actions[data-bt-side="foe"]   .bt-act { background:var(--red);   border-color:var(--red);   color:#fff; }
.lb .bt-actions[data-bt-side="party"] .bt-act { background:var(--green); border-color:var(--green); color:#fff; }
.lb .bt-actions[data-bt-side="ally"]  .bt-act { background:var(--blue);  border-color:var(--blue);  color:#fff; }
.lb .bt-actions[data-bt-side="object"] .bt-act { background:var(--grey);  border-color:var(--grey);  color:#fff; }
/* The icon PNGs ship white, so on any of the three they are already right. */
.lb .bt-actions[data-bt-side] .bt-act__i--png { filter:none; }
/* Hover is the rule below, a brightness filter on whatever colour the button is, so it
   follows the side without knowing about it. These two do the same for the states that
   had nothing. */
.lb .bt-actions[data-bt-side] .bt-act:active { filter:brightness(.94); }
.lb .bt-actions[data-bt-side] .bt-act:focus-visible { outline:3px solid var(--gold); outline-offset:-3px; }
.lb .bt-act:hover { filter:brightness(1.06); }
.lb .bt-act.is-on { box-shadow:inset 0 0 0 3px var(--gold); }
.lb .bt-act__i { flex:0 0 auto; width:26px; height:26px; object-fit:contain; }
/* The icon PNGs ship white: on the pale button they need turning dark, the same trick
   the equipment slots use. */
.lb .bt-act__t { min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
@media (max-width:820px){
  /* Same height as anywhere else: the stack inside is what sets it, not the pane. */
  .lb .bt-card { max-width:none; }
  .lb .bt-card__pic { flex-basis:88px; }
  .lb .bt-card__name { font-size:21px; }
  .lb .bt-card__sub { font-size:14px; }
  .lb .bt-card__box, .lb .bt-card__dmg { font-size:21px; }
  .lb .bt-actions { flex-basis:124px; }
  .lb .bt-act { font-size:14px; gap:7px; padding:0 8px; }
  .lb .bt-act__i { width:20px; height:20px; }
}
/* 2D only, and OUTSIDE .bt-stage. See the comment on btGridHtml.
   Both wrappers are transparent to the pointer: their LAYOUT boxes are the plane's
   untransformed size, which is far bigger than the room they sit in, so they reached up
   over the buttons and ate clicks on the lowest one. The plane itself is transformed, so
   it is hit-tested where it actually appears. */
.lb .bt-fit, .lb .bt-stage { pointer-events:none; }
.lb .bt-grid { pointer-events:auto; }
.lb .bt-fit { flex:1; min-width:0; min-height:0; display:flex; align-items:center; justify-content:center;
  /* Pan sits in front of the fit so dragging the board can never disturb the projection
     or the centring behind it. */
  transform:translate(var(--px,0px), var(--py,0px)) translate(var(--dx,0px), var(--dy,0px)) scale(var(--fit,1));
  /* --stretch and --pw are set here, not on the plane: a value declared ON the plane
     would beat the inherited one that fitBtBoard() computes. */
  --stretch:1.45; }
/* ONLY THE JOURNEY HOME ANIMATES. The transition lives on a class the centre button adds
   for the length of one trip, never on .bt-fit itself: a transition on that transform
   would make every drag lag behind the finger. */
.lb .bt-fit.is-homing { transition:transform 300ms cubic-bezier(.2,.7,.3,1); }
@media (prefers-reduced-motion: reduce){ .lb .bt-fit.is-homing { transition:none; } }
.lb .bt-stage { perspective:calc(var(--pw,760px) * 1.95); perspective-origin:50% 44%; }
/* The plane. The gap IS the grid line: one uniform rule at every size, and no doubled
   stroke where the last column meets the frame. */
.lb .bt-grid {
  --tilt:48deg; direction:ltr;
  width:var(--pw,760px);
  height:calc(var(--pw,760px) / var(--cols,8) * var(--rows,6) * var(--stretch));
  transform:rotateX(var(--tilt)); transform-style:preserve-3d;
  display:grid; gap:2px;
  grid-template-columns:repeat(var(--cols,8), minmax(0,1fr));
  grid-template-rows:repeat(var(--rows,6), minmax(0,1fr));
  background:var(--edge); border:2px solid var(--line); box-sizing:border-box;
}
/* Flat on purpose: a cell with its own 3D context breaks cell picking. */
/* THE TRANSPARENT BORDER IS ON EVERY CELL ON PURPOSE. An occupied square gets a
   coloured edge, and if only occupied squares had a border at all, every square would
   change size the moment somebody stepped onto it. Reserved here, coloured below, so
   the grid never moves. box-sizing stated rather than assumed for the same reason. */
.lb .bt-cell { background:var(--sheet); min-width:0; min-height:0;
  box-sizing:border-box; border:2px solid transparent; }
/* Somebody is standing here. The floor says so, which frees the sprite from needing a
   frame of its own. */
/* WHOSE SQUARE IS THIS. A held cell carries the faintest wash of its side, so a glance
   reads the shape of a fight without reading a single figure. Asked for by Maayan, and
   it REVERSES what this comment used to say: unselected cells were deliberately left
   uncoloured for a clean grid. That was a real decision and it is being changed on
   purpose, so it is recorded rather than quietly overwritten.
   THE ORDER OF THESE RULES IS THE WHOLE TRICK. They sit ABOVE every state below them
   and they never fight for specificity, so the moment a cell is selected, hovered,
   marked as a target, or part of a route, that rule comes later and simply wins. The
   wash is what a cell looks like when nothing else is happening to it, which is why a
   selected foe still goes strong red and a legal move still goes green.
   AND WHY GREEN IS ONLY 10%: green already means "you can move here" on this board, at
   24% for a path and 42% for its destination. A party wash any stronger would make one
   colour say two things at once. Red is clean, it only ever means foe or forbidden.
   The figure standing on the square is the real cue and this is the supplement: heroes
   show a character portrait and foes a monster one. Worth knowing before a third side
   arrives, because a friendly wolf and a hostile wolf are the SAME artwork and a wash
   cannot carry that difference on its own. */
.lb .bt-cell.is-taken-foe  { background:var(--tint-foe); }
/* All three are set from btSideOf now, each one named. is-taken-hero used to be set as
   "anything that is not a foe", which was correct while there were two sides and would
   have painted a friendly wolf as one of the party on the day there were three. */
.lb .bt-cell.is-taken-hero { background:var(--tint-party); }
.lb .bt-cell.is-taken-ally { background:var(--tint-ally); }
.lb .bt-cell.is-taken-object { background:var(--tint-object); }
/* And the edge that says whose square it is. The tint is the whisper, this is the word.
   Asked for by Maayan at 2px and half strength.
   PER SQUARE, NOT PER REGION. .bt-grid has gap:2px, so two occupied squares side by side
   are two outlined squares with a gap between them and never one edged block. Correcting
   myself in place: I first wrote that adjacent same-side squares MERGE into territory,
   having judged it from a screenshot of a whole board at small scale rather than from the
   one case the claim was about. Dwarf laid three adjacent foe cells against this
   stylesheet and looked at them. They do not merge, and the rule above says why they
   cannot. If territory is ever wanted it is a real change, killing the gap and
   suppressing the internal edges, not a description of what this does. */
.lb .bt-cell.is-taken-foe  { border-color:var(--edge-foe); }
.lb .bt-cell.is-taken-hero { border-color:var(--edge-party); }
.lb .bt-cell.is-taken-ally { border-color:var(--edge-ally); }
.lb .bt-cell.is-taken-object { border-color:var(--edge-object); }
/* EVERYTHING THAT HAPPENS TO A SQUARE BEATS WHOSE IT IS. Ordered above the state rules
   and matched at the same weight, so selection, hover, the route, a drop and the attack
   mark all win by coming later, exactly as they do over the tints. The edge is dropped
   rather than drawn under them: a 2px half-strength line outside a 3px inset outline is
   two rings round one square, which reads as a mistake rather than as emphasis. */
.lb .bt-cell.is-sel, .lb .bt-cell.is-hot, .lb .bt-cell:hover,
.lb .bt-cell.is-path, .lb .bt-cell.is-dest, .lb .bt-cell.is-nogo,
.lb .bt-cell.is-drop-ok, .lb .bt-cell.is-drop-no,
.lb .bt-cell.is-mark-foe, .lb .bt-cell.is-mark-ally { border-color:transparent; }
/* Pointing at the figure standing here. Inset outline, so the square reads as pickable
   without the board's own lines moving. */
.lb .bt-cell.is-hot { background:rgba(122,88,46,.3); outline:2px solid var(--gold2); outline-offset:-2px; }
.lb .bt-cell:hover { background:rgba(255,214,90,.55); }
/* Whoever is chosen reads stronger: a selected FOE turns strong red, a selected
   PARTY member turns yellow. Side rules are more specific, so they win over is-sel. */
.lb .bt-cell.is-sel { background:rgba(255,196,40,.55); outline:3px solid var(--ink); outline-offset:-3px; }
.lb .bt-cell.is-taken-foe.is-sel  { background:rgba(179,45,31,.58); outline:3px solid var(--red); outline-offset:-3px; }
.lb .bt-cell.is-taken-hero.is-sel { background:rgba(255,196,40,.68); outline:3px solid var(--gold-dk); outline-offset:-3px; }
/* A selected ALLY reads as itself rather than falling through to the generic gold, which
   would make the one square you are looking at the only one that forgets whose it is. */
.lb .bt-cell.is-taken-ally.is-sel { background:color-mix(in srgb, var(--blue) 45%, var(--sheet)); outline:3px solid var(--blue); outline-offset:-3px; }
.lb .bt-cell.is-taken-object.is-sel { background:color-mix(in srgb, var(--grey) 45%, var(--sheet)); outline:3px solid var(--grey); outline-offset:-3px; }
.lb .bt-cell.is-drop-ok { background:rgba(44,110,74,.32); }
.lb .bt-cell.is-drop-no { background:rgba(179,45,31,.32); }
/* The route, and where it ends. */
.lb .bt-cell.is-path { background:rgba(44,110,74,.24); }
.lb .bt-cell.is-dest { background:rgba(44,110,74,.42); outline:3px solid var(--green,#2C6E4A); outline-offset:-3px; }
.lb .bt-cell.is-nogo { background:rgba(179,45,31,.3); outline:3px solid var(--red); outline-offset:-3px; }
/* Who the aim lands on: the attack DESTINATION gets a dashed border, red for the
   other side, green for your own. */
.lb .bt-cell.is-foe { background:rgba(179,45,31,.42); outline:3px dashed var(--red); outline-offset:-3px; }
.lb .bt-cell.is-ally { background:rgba(44,110,74,.42); outline:3px dashed var(--green,#2C6E4A); outline-offset:-3px; }
/* The one being attacked stays marked after the choice is made. */
.lb .bt-cell.is-mark-foe { background:rgba(179,45,31,.42); outline:3px dashed var(--red); outline-offset:-3px; }
.lb .bt-cell.is-mark-ally { background:rgba(44,110,74,.42); outline:3px dashed var(--green,#2C6E4A); outline-offset:-3px; }
/* The hand: empty ground drags the camera. */
.lb .bt-zoom { display:inline-flex; align-items:center; gap:4px; }
.lb .bt-zoom__lbl { min-width:3.4em; text-align:center; font-weight:700; font-size:13px;
  color:var(--ink2); direction:ltr; unicode-bidi:isolate; }
.lb .bt-grid.is-move, .lb .bt-grid.is-move .bt-cell { cursor:move; }
.lb .bt-grid.is-aim, .lb .bt-grid.is-aim .bt-cell { cursor:crosshair; }
/* A piece is a grid item like a cell, never a child of one, and it stands up out of the
   floor by undoing the plane's tilt. The perspective then scales it by depth for free. */
/* Stands in the MIDDLE of its square, not on the front edge: the box is centred in the
   cell and then dropped by half its own height, so its feet land on the cell's centre.
   The pivot stays at the feet. Pivoting from the middle would sink the lower half of the
   figure below the floor, where it is both hidden and unclickable. */
.lb .bt-pc {
  position:relative;
  align-self:center; justify-self:center; width:var(--pcw,118%);
  /* --mx/--my slide the figure across the board while it walks. Outermost, so they act
     in the plane's own flat space. */
  transform:translate(var(--mx,0px), var(--my,0px)) translateY(-50%) rotateX(calc(-1 * var(--tilt, 48deg)));
  transform-origin:50% 100%;
  display:flex; align-items:flex-end; justify-content:center;
  cursor:grab; touch-action:none; -webkit-tap-highlight-color:transparent;
}
/* Cut-out art: no frame, no fill. The piece is a figure standing on the board, not a
   card lying on it. Outlines (never borders) carry hover and selection, so nothing
   reflows when either turns on. */
/* -webkit-user-drag pairs with draggable="false" in the markup: between them the
   browser's own image drag can never steal a board gesture. */
.lb .bt-pc__art { width:100%; display:block; -webkit-user-drag:none; user-select:none; }
/* Above the head, and OUT of the flow: in it, the bar would grow the billboard and the
   figure's feet would drift off the middle of its square. */
.lb .bt-pc__hp { position:absolute; bottom:100%; left:50%; transform:translateX(-50%);
  width:64%; height:9px; margin-bottom:3px; box-sizing:border-box;
  background:#141210; border-radius:99px; overflow:hidden; pointer-events:none; }
.lb .bt-pc__hp i { display:block; height:100%; border-radius:99px; background:#46b83f; }
.lb .bt-pc__hp.is-warn i { background:#e8912a; }
.lb .bt-pc__hp.is-bad i { background:#d0331f; }
/* THE NUMBER ON THE CARD WEARS THE SAME THREE COLOURS AS THE BAR, from the same three
   values, so a card saying one thing while the bar beside it says another is not a bug
   that can happen. At zero the number is red, which is the point: a piece that is down
   says so on its card as loudly as it does on the board. */
.lb .bt-card__box.is-ok  { color:#2f8a2a; }
.lb .bt-card__box.is-warn{ color:#b96f13; }
.lb .bt-card__box.is-bad { color:#b3271a; }
/* The maximum beside it, in the hero sheet's shape. Quieter than the number, because the
   number is the thing that changes and the maximum is only there to give it scale. */
/* THE VALUE AND ITS MAXIMUM ARE ONE LINE, which they were not for free. The stat is a
   column, so a slash and a maximum added after the box simply flowed underneath it: at
   390px the number sat on one line and "/ 45" on the next, which reads as two facts
   rather than one. They share a row of their own now, and the box may shrink below its
   124px minimum inside it, because on a phone the maximum has to come from somewhere. */
.lb .bt-card__hpline { display:flex; align-items:baseline; justify-content:center;
  gap:2px; max-width:100%; min-width:0; }
.lb .bt-card__hpline .bt-card__box { min-width:0; flex:0 1 auto; }
.lb .bt-card__slash { margin:0 2px; color:var(--ink3); }
.lb .bt-card__max { color:var(--ink3); font-weight:700; }
/* THE LETTER STANDS ON THE RING, where a portrait's feet would. The chip is portrait
   shaped (about a cell and a half tall) so it holds the same box a picture does, and
   centring the letter in that box floated it most of a cell above its own base. */
.lb .bt-pc__chip {
  width:100%; aspect-ratio:.78; display:grid; place-items:end center; padding-bottom:10%; box-sizing:border-box;
  font-family:'Frank Ruhl Libre', Georgia, serif; font-weight:900; font-size:clamp(12px,4vw,26px);
  color:var(--ink); text-shadow:0 1px 0 var(--paper), 0 -1px 0 var(--paper), 1px 0 0 var(--paper), -1px 0 0 var(--paper);
}
/* THE RING: A BASE UNDER THE FIGURE, WHICH IS WHERE A TABLETOP PUTS IT.
   The cell tint cannot carry a side on its own. Measured on the painted squares, an ally
   against a party square is dE 4.2 where a foe against a party square is 7.5, because
   blue at 10% over warm parchment does not read as blue, it reads as grey. The card
   shouts and the tint can only whisper, and a DM in a fight is looking at the board.
   So the mark goes on the FIGURE, at full strength, the same colour the card border uses.
   AN ELLIPSE AT THE FEET rather than a box around the art. This sprite is cut-out art of
   somebody standing on the board, not a card lying on it, and a rectangle around its
   bounding box would turn it back into a card. A base is also what a miniature has, so
   it needs no explaining to anybody who has seen a table.
   ::after, so nothing reflows and the art is untouched.
   UNDER THE FEET, NOT OVER THEM. A positioned ::after paints above the unpositioned art,
   so the ring was drawn across the sprite's boots. z-index:-1 drops it beneath the art,
   and it cannot fall any further than the piece itself: .bt-pc always carries a
   transform, which makes it the stacking context, so the ring stays above the square. */
.lb .bt-pc[data-bt-side]::after{
  content:""; position:absolute; left:50%; bottom:-1px; z-index:-1;
  width:66%; height:20%; transform:translateX(-50%);
  border:3px solid var(--ring, transparent); border-radius:50%;
  pointer-events:none; }
.lb .bt-pc[data-bt-side="foe"]   { --ring:var(--red); }
.lb .bt-pc[data-bt-side="party"] { --ring:var(--green); }
.lb .bt-pc[data-bt-side="ally"]  { --ring:var(--blue); }
.lb .bt-pc[data-bt-side="object"] { --ring:var(--grey); }
/* AT ZERO. A hero, a foe or an object with no hit points left stays on its square, is
   painted at half, is walked over, and can be brought back up. ONE state across all three
   kinds rather than destruction for objects and unconsciousness for people, because it is
   one fact about hit points and a seven year old should learn it once.
   THERE WAS A HEAVY X HERE for about an hour, for destroyed objects only. Then objects
   turned out to be repairable, and a mark that says destroyed on something that can be
   mended is a mark it has to shed. It also said more than the stylesheet knows: a big
   cross over an unconscious hero reads as "dead and gone", which is a thing a DM says out
   loud at a table and should never be said by a CSS rule. Half opacity says out of the
   fight and claims nothing further.
   The square is NOT tinted and NOT edged while only the fallen are on it, which is
   handled in app.js by leaving them out of the occupancy map: that is what "no longer
   holds its square" means, and it is why somebody can stand there. */
.lb .bt-pc.is-down { opacity:.5; }
/* The letter a piece shows when it has no portrait yet. By SIDE, not by kind: this was
   .bt-pc--foe, so a wolf fighting for the party would have worn a red initial. */
.lb .bt-pc[data-bt-side="foe"]   .bt-pc__chip { color:var(--red); }
.lb .bt-pc[data-bt-side="ally"]  .bt-pc__chip { color:var(--blue); }
.lb .bt-pc[data-bt-side="object"] .bt-pc__chip { color:var(--grey); }
/* Gone, not faded. While a figure is in hand there is exactly ONE of it, the one
   following the pointer; a ghost of it left standing on the old square reads as a
   copy being made rather than a piece being carried. The square it came from is
   still marked, by the cell. */
.lb .bt-pc.is-dragging { opacity:0; }
.lb .bt-pc.is-walking { will-change:transform; }
/* The one class that makes cell picking exact: while placing, sprites stop intercepting. */
.lb .bt-grid.is-placing .bt-pc { pointer-events:none; }
/* Held, and hanging. The pointer pinches the top of the figure and the rest of it
   dangles below, so on a phone the finger never covers the thing being carried.
   Only the pointer decides which square is targeted, so the offset is free to be
   whatever reads best. */
.lb .bt-ghost { position:fixed; left:0; top:0; z-index:60; pointer-events:none; opacity:.92;
  transform:translate(-50%,-6%); filter:drop-shadow(0 10px 12px rgba(38,28,14,.34)); }
/* The ghost's own transform centres it on the pointer, so the lift animates this
   inner element instead of fighting over the same property. Feet as the origin,
   because a figure is picked up from where it stands. */
.lb .bt-ghost__in { transform-origin:50% 100%; }
.lb .bt-ghost img, .lb .bt-ghost span { width:100%; display:block; }
/* Squashed from the feet, so a landing figure keeps its footing on the square. */
.lb .bt-pc__art, .lb .bt-pc__chip { transform-origin:50% 100%; }
.lb .bt-gridwrap { position:relative; display:inline-flex; }
/* "8 × 6" reads left to right in Hebrew too. */
.lb .bt-gridtrigger { direction:ltr; }
/* The trigger sits at the END of the toolbar, so a popover anchored to its start edge
   runs off the pane and gets clipped by the stage. It opens inward instead. */
.lb .mapgrid-pop.bt-gridpop { inset-inline-start:auto; inset-inline-end:0; }
.lb .bt-gridpop input[type="range"] { flex:1; min-width:110px; }
.lb .monbody--center { display:flex; justify-content:center; align-items:flex-start; }
.lb .monbody--center .moncard { margin:0 auto; }
.lb .monbody--scroll { flex:1; min-height:0; overflow:auto; -webkit-overflow-scrolling:touch; }
.lb .monapprove {
  flex:0 0 auto; position:sticky; bottom:0; z-index:6;
  background:var(--paper); border-top:2px solid var(--edge);
  padding:12px 18px calc(12px + env(safe-area-inset-bottom,0px));
  box-sizing:border-box; width:100%;
}
.lb .monapprove__hint { margin:0 0 8px; }
.lb .chartop, .lb .moncard__top {
  display:grid; grid-template-columns:minmax(120px,180px) 1fr; gap:12px; margin:0 0 12px; align-items:stretch;
}
.lb .charpic, .lb .moncard__pic { min-height:160px; height:100%; }
/* The picture fills the slot from OUTSIDE the flow. In the flow its natural shape set
   the slot's height, so swapping a square portrait for a tall one stretched the whole
   row to match it. The slot now keeps its own height and crops whatever it is given. */
.lb .charpic img, .lb .moncard__pic img { position:absolute; inset:0;
  width:100%; height:100%; object-fit:contain; max-height:none; }
.lb .charid, .lb .moncard__id { min-width:0; display:flex; flex-direction:column; gap:8px; }
.lb .charrow, .lb .moncard__row { display:grid; grid-template-columns:1.5fr 0.85fr; gap:8px; }
.lb .charstats, .lb .moncard__stats { display:grid; grid-template-columns:repeat(3,1fr); gap:8px; margin:0; }
.lb .moncard__stats--1 { grid-template-columns:1fr; }
.lb .moncard__stats--2 { grid-template-columns:1fr 1fr; }
.lb .moncard__stats--3 { grid-template-columns:1fr 1fr 1fr; }
.lb .char-hpbar, .lb .moncard__hpbar { margin-top:8px; height:10px; }
.lb .char-hpbar > i, .lb .moncard__hpbar > i { background:#2C6E4A; }
.lb .charabils { margin:0 0 12px; }
.lb .moncard { position:relative; width:100%; flex:none; max-width:560px; height:auto; align-self:flex-start; }
.lb .monreview__hint { margin:0 0 14px; }
.lb .monreview { display:flex; flex-direction:column; gap:10px; }
.lb .monreview__card {
  display:flex; align-items:center; gap:14px; width:100%; text-align:start;
  padding:12px 14px; border:2px solid var(--edge); background:var(--sheet);
  cursor:pointer; font:inherit; color:var(--ink); box-sizing:border-box;
}
.lb .monreview__card:hover { border-color:var(--gold); background:var(--sheet2); }
.lb .monreview__meta { flex:1; min-width:0; }
.lb .monreview__kind { font-family:'Frank Ruhl Libre',serif; font-weight:900; font-size:20px; }
/* A real CTA pill, not a text link: it reads as "do this to proceed". */
.lb .monreview__go { flex:0 0 auto; background:var(--gold); color:#fff; font-weight:800; white-space:nowrap;
  padding:9px 16px; border:2px solid var(--gold-dk); }
.lb .monreview__card:hover .monreview__go { background:var(--gold-dk); }
/* Approve bar: reject sits beside approve, approve carries the weight. */
.lb .monapprove__btns { display:flex; gap:10px; }
.lb .monapprove__btns .gm-btn { flex:1; }
.lb .monapprove__btns .gm-btn--primary { flex:1.6; }
.lb .monreview__card .gm-table__img,
.lb .monarchive__tile .gm-table__img {
  width:64px; height:64px; max-width:64px; max-height:64px;
  object-fit:cover; display:block; border:0; background:var(--meta);
}
/* Centre the spinner in a generating thumb — needs to out-specify the display:block above. */
.lb .monreview__card .gm-table__img--gen,
.lb .monarchive__tile .gm-table__img--gen {
  display:inline-flex; align-items:center; justify-content:center;
}
.lb .monarchive { display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:12px; }
.lb .monarchive__tile {
  position:relative; display:flex; flex-direction:column; align-items:center; gap:8px;
  padding:14px 12px 12px; border:2px solid var(--edge); background:var(--sheet);
  cursor:pointer; text-align:center; box-sizing:border-box;
}
.lb .monarchive__tile:hover { border-color:var(--gold); }
.lb .monarchive__open { display:flex; flex-direction:column; align-items:center; gap:8px; width:100%; }
.lb .monarchive__menu { position:absolute; top:6px; inset-inline-end:6px; z-index:2; }
.lb .monarchive__name { font-family:'Frank Ruhl Libre',serif; font-weight:800; font-size:16px; line-height:1.2; }
.lb .monarchive__kind { color:var(--ink3); }
.lb .monarchive__plus { margin-top:4px; }

/* library modal — account-wide spell / item / ability / monster templates */
.lb-box .libhead { gap:8px; }
.lb-box .libhead__back { flex:0 0 auto; }
.lb-box .libhead__title { font-size:22px; min-width:0; }
.lb-box .libcats { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:10px; }
.lb-box .libcat {
  display:flex; flex-direction:column; align-items:center; gap:6px; padding:16px 10px;
  border:2px solid var(--edge); border-radius:12px; background:var(--paper); cursor:pointer;
  text-align:center; transition:border-color .15s, background .15s; }
.lb-box .libcat:hover { border-color:var(--gold); background:var(--sheet2); }
.lb-box .libcat__icon { font-size:28px; line-height:1; }
.lb-box .libcat__name { font-family:'Frank Ruhl Libre',serif; font-weight:800; font-size:16px; }
.lb-box .libcat__n { color:var(--ink3); }
.lb-box .liblist { display:flex; flex-direction:column; gap:8px; max-height:min(60vh,520px); overflow:auto; }
.lb-box .librow {
  display:flex; align-items:center; gap:10px; padding:8px 10px;
  border:2px solid var(--edge); border-radius:10px; background:var(--paper); }
.lb-box .librow__pic { flex:0 0 auto; }
/* A Library row's thumbnail in the Library window (outside .lb, so the table's sizes never reached it): a real
   picture (a saved drop's) at the thumbnail's size, whole. */
.lb-box .librow__pic img.gm-table__img { width:64px; height:64px; max-width:64px; max-height:64px; object-fit:contain; display:block; }
.lb-box .librow__main { flex:1 1 auto; min-width:0; }
.lb-box .librow__name { display:block; font-family:'Frank Ruhl Libre',serif; font-size:16px; line-height:1.2; }
.lb-box .librow__sub { margin-top:2px; color:var(--ink3); }
.lb-box .librow__add { flex:0 0 auto; }
.lb-box .libempty { padding:20px 0; }
/* Library as an in-panel tab (mirrors the modal library, narrower grid). */
/* THE LIBRARY WEARS THE SAME CHROME AS EVERY OTHER PAGE. It used to carry its
   own bare .libtab__head: no padding, no background, no rule under it, and a
   24px/700 title where the rest of the app uses .maptoolbar at 22px/900. The
   markup was already in the right order (back, title, spacer, actions), so the
   headers only had to stop being a private class. The toolbar is full bleed and
   fixed, which is why the page padding moved off .libtab and onto the body. */
.lb .libtab { padding:0; height:100%; min-height:0; overflow:hidden;
  display:flex; flex-direction:column; box-sizing:border-box; }
.lb .libtab__body { flex:1 1 auto; min-height:0; overflow:auto;
  padding:14px 16px 20px; box-sizing:border-box; }
.lb .libtab .libcats { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:10px; }
.lb .libtab .libcat {
  display:flex; flex-direction:column; align-items:center; gap:6px; padding:18px 10px;
  border:2px solid var(--edge); border-radius:12px; background:var(--paper); cursor:pointer;
  text-align:center; transition:border-color .15s, background .15s; }
.lb .libtab .libcat:hover { border-color:var(--gold); background:var(--sheet2); }
.lb .libtab .libcat__icon { font-size:30px; line-height:1; }
.lb .libtab .libcat__name { font-family:'Frank Ruhl Libre',serif; font-weight:800; font-size:16px; }
.lb .libtab .libcat__n { color:var(--ink3); }
.lb .libtab .liblist { display:flex; flex-direction:column; gap:8px; }
.lb .libtab .librow {
  display:flex; align-items:center; gap:10px; padding:8px 10px;
  border:2px solid var(--edge); border-radius:10px; background:var(--paper); }
.lb .libtab .librow__pic { flex:0 0 auto; }
.lb .libtab .librow__main { flex:1 1 auto; min-width:0; }
.lb .libtab .librow__name { display:block; font-family:'Frank Ruhl Libre',serif; font-size:16px; line-height:1.2; }
.lb .libtab .librow__sub { margin-top:2px; color:var(--ink3); }
.lb .libtab .librow__add { flex:0 0 auto; }
/* A BUTTON WITH TWO LABELS, CHOSEN BY THE PANE AND NOT BY THE PHONE.
   "Add to adventure" is 160px. In a 358px row that leaves .librow__main, which
   is flex:1 1 auto with min-width:0, holding 36px: "Rumble" wants 57 and a four
   word Hebrew name wrapped to four lines.
   THIS WAS FIRST WRITTEN AS @media (max-width:430px) AND THAT WAS THE WRONG
   INSTRUMENT. The row does not live in the viewport, it lives in a pane, and a
   pane is narrow at any window size: with the split open on a 900px window the
   row is 300px and the name column measured ZERO, which the phone rule did not
   touch because the viewport was 900. Same mistake as measuring a child against
   the window instead of its scroller, one level up.
   `pane` is the existing container on .lb-pane, so this now answers the only
   question that matters: how much room does THIS row actually have.
   Swapped in CSS rather than picked in JS so a resize or a rotate cannot leave
   the wrong label on screen. The long one stays the accessible name. */
.lb .gm-btn--dual .gm-btn__t--short { display:none; }
@container pane (max-width:520px){
  .lb .gm-btn--dual .gm-btn__t--long { display:none; }
  .lb .gm-btn--dual .gm-btn__t--short { display:inline; }
}
/* The row opens what it is about, so a clipped name is not also a dead one. */
.lb .libtab .librow__main { cursor:pointer; }
.lb .libtab .libempty { padding:24px 0; }
/* The popped-out detail fills the whole panel (no 560px page cap, no centering).
   The .page class pins flex-basis:560px, so width:100% alone loses — override flex. */
.lb .libtab--detail { padding:0; }  /* the toolbar brings its own padding now */
.lb .libtab--detail .monbody--center { justify-content:stretch; }
.lb .libtab--detail .monbody--center .moncard { margin:0; width:100%; max-width:none; flex:1 1 auto; }
/* "Open in the other panel" round arrow button (reused on cards + snippets). */
.lb .gm-openpanel {
  flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center;
  width:30px; height:30px; border-radius:50%;
  border:none; background:var(--red); color:#fff;
  line-height:1; cursor:pointer; padding:0;
  box-shadow:0 1px 3px rgba(0,0,0,.25);
  transition:filter .15s, transform .1s; }
.lb .gm-openpanel:hover { filter:brightness(1.08); }
.lb .gm-openpanel:active { transform:scale(.92); }
.lb .gm-openpanel__i { display:block; }
.lb .gm-openpanel--rtl .gm-openpanel__i { transform:scaleX(-1); }
.lb .bt-card__open { align-self:flex-start; margin-inline-start:auto; }
.lb-box .libdests, .lb-box .libpicks { display:flex; flex-direction:column; gap:8px; }
.lb-box .libdest, .lb-box .libpick {
  display:flex; align-items:center; gap:10px; width:100%; padding:12px 14px;
  border:2px solid var(--edge); border-radius:10px; background:var(--paper); cursor:pointer;
  text-align:start; font:inherit; color:inherit; transition:border-color .15s; }
.lb-box .libdest:hover, .lb-box .libpick:hover { border-color:var(--gold); }
.lb-box .libdest__label, .lb-box .libpick__name { flex:1 1 auto; font-weight:700; }
.lb-box .libdest__arr { color:var(--ink3); font-size:20px; }
.lb-box .libpick { flex-direction:column; align-items:flex-start; gap:2px; }
.lb-box .libpick__sub { color:var(--ink3); }
.lb-box .libaddwho { margin-bottom:4px; }

/* library entry editor — wide modal, sticky save/cancel header */
.lb-box.lb-box--edit { max-width:min(96vw,820px); padding:0; display:flex; flex-direction:column; max-height:min(92vh,900px); overflow:hidden; }
.lb-box.lb-box--edit .libedit__head {
  position:sticky; top:0; z-index:2; margin:0; padding:12px 16px;
  background:var(--sheet); border-bottom:2px solid var(--edge); }
.lb-box.lb-box--edit .libedit__title { font-size:20px; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.lb-box.lb-box--edit .libedit__body { flex:1 1 auto; overflow:auto; padding:0; }
.lb-box.lb-box--edit .libedit__body .lb--inset { padding:18px 18px 28px; box-sizing:border-box; }
.lb-box.lb-box--edit .libedit__body .monstack { width:100%; }
.lb-box.lb-box--edit .libshop-edit__banner,
.lb-box.lb-box--edit .libloc-edit__banner { display:block; margin-bottom:14px; cursor:pointer; }
.lb-box.lb-box--edit .libshop-edit__meta,
.lb-box.lb-box--edit .libloc-edit__fld { display:flex; flex-direction:column; gap:6px; margin-bottom:14px; }
.lb-box.lb-box--edit .libshop-edit__fld .ed,
.lb-box.lb-box--edit .libloc-edit__fld .ed { display:block; padding:8px 10px; border:2px solid var(--edge); background:var(--paper); min-height:1.4em; }
.lb-box.lb-box--edit .libmap-edit__prev { margin-bottom:12px; border:2px solid var(--edge); background:var(--paper); max-height:220px; overflow:hidden; }
.lb-box.lb-box--edit .libmap-edit__prev img { width:100%; height:auto; display:block; object-fit:contain; }
.lb-box.lb-box--edit .libmap-edit__meta { margin:8px 0; }

.lb .mon-gen__ta { width:100%; min-height:110px; box-sizing:border-box; font:inherit; font-size:16px; padding:10px 12px; border:2px solid var(--edge); background:var(--paper); resize:vertical; color:var(--ink); }
.lb-box .mon-gen__ta { width:100%; min-height:110px; box-sizing:border-box; font:inherit; font-size:16px; padding:10px 12px; border:2px solid var(--edge); background:var(--paper); resize:vertical; color:var(--ink); }
.lb-box .mon-gen__err { color:var(--red); margin-top:8px; }
/* the picture window's look: imageModal.css (one for v1 and GrowlMaster v2) */
@container pane (max-width:560px){
  .lb .charhead { grid-template-columns:minmax(0,1.4fr) minmax(0,1fr) auto; }
  .lb .moncard__head { grid-template-columns:minmax(0,1fr) minmax(min-content,1fr) minmax(min-content,1fr) auto; }
  .lb .moncard__head--noprop { grid-template-columns:minmax(0,1.4fr) minmax(min-content,1fr) auto; }
  /* SIDE ON ITS OWN ROW on a phone (Troll, 2026-10-25): beside Name and Type its select left Type too little room; the
     ⋮ menu stays at the end of the first row */
  .lb .moncard__head > .fld.side { grid-column:1 / -1; }
  .lb .moncard__head > .sheetmenu { grid-row:1; grid-column:-2 / -1; }
  .lb .moncard__head:not(.moncard__head--noprop):not(:has(> .sheetmenu)) { grid-template-columns:minmax(0,1fr) minmax(min-content,1fr) minmax(min-content,1fr); }
  .lb .moncard__head.moncard__head--noprop:not(:has(> .sheetmenu)) { grid-template-columns:minmax(0,1.4fr) minmax(min-content,1fr); }
  .lb .chartop, .lb .moncard__top { grid-template-columns:1fr; }
  .lb .charpic, .lb .moncard__pic { min-height:140px; }
  .lb .charrow, .lb .moncard__row { grid-template-columns:1fr 1fr; }
  .lb .charstats, .lb .moncard__stats { grid-template-columns:1fr 1fr; }
  .lb .montoolbar.btoolbar { padding:10px 12px; }
  .lb .monbody { padding:18px 12px 24px; }
  .lb .monbody--table { padding-inline:0; }
  /* THE BOARD AND THE MAP FILL A PHONE'S WIDTH (Maayan, 2026-10-20): the battle board edge to edge, and the map's
     tool column a row under the map instead of a column beside it. Both pan and pinch (app.js pinchZoom). */
  .lb .monbody.bt-board { padding-inline:0; }
  .lb .mapbody { flex-direction:column; }
  .lb .mapbody > .mapview { flex:none; height:64dvh; }   /* its height was the tool column's; now its own, as the board's */
  .lb .mapside { flex-direction:row; flex-wrap:nowrap; overflow-x:auto; overflow-y:hidden; border-inline-start:0; border-top:2px solid var(--edge); padding:8px 10px; }
  .lb .mapside__brush { flex-direction:row; padding:4px 10px; }
  .lb .mapside__brush input[type=range] { writing-mode:horizontal-tb; direction:ltr; width:120px; height:28px; }
  .lb .mapside__gap { min-height:0; min-width:8px; }
}

/* ---------------- battle (snoozed grid / dice — keep for later) ---------------- */
.lb .battle { display:flex; gap:16px; align-items:stretch; margin:auto; width:100%; max-width:1200px; }
.lb .tok { position:absolute; width:42px; height:42px; display:flex; align-items:center; justify-content:center;
  font-weight:900; font-size:20px; color:#fff; cursor:pointer; border:2px solid var(--ink); background:var(--tc,#666); }
.lb .tok.mon { border-color:var(--red); }
.lb .tok.turn { box-shadow:0 0 0 3px var(--gold); }
.lb .tok.target { outline:3px solid var(--red); outline-offset:1px; }
.lb .tok.dead { opacity:.45; filter:grayscale(1); border-style:dotted; }
.lb .tok .fly { position:absolute; top:-14px; font-size:12px; color:var(--ink2); }
.lb .tok.hit { animation:lbflash .5s ease-out 2; }
@keyframes lbflash { 0%,100%{background:var(--tc)} 50%{background:var(--red)} }
.lb .float { position:absolute; color:var(--red); font-weight:900; font-size:18px; pointer-events:none; animation:lbrise 1s ease-out forwards; }
@keyframes lbrise { 0%{opacity:1;transform:translateY(0)} 100%{opacity:0;transform:translateY(-28px)} }
.lb .need { display:flex; flex-direction:column; align-items:center; line-height:1; }
.lb .need b { font-family:'Frank Ruhl Libre',serif; font-size:34px; }
.lb .need span { font-size:16px; color:var(--ink3); margin-top:2px; }
.lb .die { width:46px; height:46px; border:2px solid var(--ink); background:var(--paper); display:grid; place-items:center; font-weight:800; font-size:16px; }
.lb .die.rolling { animation:lbspin .5s linear 1; }
@keyframes lbspin { to{transform:rotate(360deg)} }
.lb .x { color:var(--ink3); } .lb .one { font-family:'Frank Ruhl Libre',serif; font-size:20px; }
.lb .result { width:70px; padding:7px; text-align:center; font:inherit; font-size:20px; font-weight:800; border:2px solid var(--edge); background:var(--paper); }
.lb .verdict { font-weight:800; font-size:18px; }
.lb .verdict.hit { color:var(--red); } .lb .verdict.miss { color:var(--ink3); }
.lb .side { flex:0 0 300px; max-width:300px; overflow:auto; }
.lb .hpbar { height:9px; background:var(--paper); border:1.5px solid var(--edge); overflow:hidden; margin-top:5px; }
.lb .hpbar i { display:block; height:100%; background:var(--red); }

/* ---------------- map: sized page + zoom (grid scales with page) ---------------- */
.lb .mapstack { width:100%; flex:1; min-height:0; display:flex; flex-direction:column; align-self:stretch; }
.lb .maptoolbar {
  flex:0 0 auto; display:flex; align-items:center; flex-wrap:wrap; gap:8px 10px;
  padding:10px 14px; margin:0; background:var(--paper); border-bottom:2px solid var(--edge);
  box-sizing:border-box; width:100%; z-index:6;
}
.lb .maptoolbar .gm-btn { margin-top:0; flex:0 0 auto; }
.lb .maptoolbar__group {
  display:inline-flex; align-items:center; gap:6px;
  flex:0 0 auto; flex-wrap:nowrap; max-width:100%;
}
.lb .maptoolbar__group--nav {
  flex:1 1 160px; min-width:0; gap:8px;
}
.lb .maptoolbar__name {
  font-size:18px; font-weight:900; min-width:0; flex:1 1 auto;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.lb .maptoolbar__title { font-size:22px; font-weight:900; margin:0; line-height:1.1; }
/* ---- THE MAP TOOLBAR (Maayan's mockup, map-toolbar-mockup.png) ----
   Three rows over the map, and a vertical bar beside it. Its own classes:
   .maptoolbar is shared with the adventure and library screens. Logical sides
   only, so Hebrew mirrors it. */
.lb .mapbar {
  flex:0 0 auto; display:flex; flex-direction:column; gap:10px;
  padding:12px 14px; background:var(--paper); border-bottom:2px solid var(--edge);
  box-sizing:border-box; width:100%; z-index:6;
}
.lb .mapbar .gm-btn { margin-top:0; flex:0 0 auto; }
.lb .mapbar__row { display:flex; align-items:center; flex-wrap:wrap; gap:8px; }
.lb .mapbar__row > .spacer { flex:1 1 0; min-width:0; }
.lb .mapbar__row--head { flex-wrap:nowrap; gap:10px; }
.lb .mapbar__name {
  flex:1 1 auto; min-width:0; font-size:20px; font-weight:900; padding:2px 0 4px;
  border-bottom:2px solid var(--ink); overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.lb .mapbar__drop { position:relative; display:inline-flex; }
.lb .mapbar__drop:last-of-type .mapgrid-pop { inset-inline-start:0; }
/* An icon with its state under it: the grid (ON/OFF), the image (its %). */
.lb .mapbar__ico { flex-direction:column; gap:1px; padding:4px 8px; min-width:46px; line-height:1; }
.lb .mapbar__ico-t { font-size:11px; font-weight:800; letter-spacing:.03em; }
.lb .mapbar__svg { width:22px; height:22px; display:block; }
.lb .mapbar__plus { font-weight:900; font-size:18px; margin-inline-end:-2px; line-height:1; }
.lb .mapbar__loc .gm-btn__i { gap:0; }
.lb .mapbar__hist { display:inline-flex; gap:6px; flex:0 0 auto; }
/* THE TOOL ROW (map-toolbar-mockup-2026-10-08.png): Lines, Fills, Fog, spread across
   the row, each a plain icon then its buttons. */
.lb .mapbar__row--tools { justify-content:flex-start; column-gap:28px; }
.lb .mapbar__grp { display:inline-flex; align-items:center; gap:6px; }
.lb .mapbar__gi { display:inline-flex; width:30px; justify-content:center; color:var(--ink); }
.lb .mapbar__gi .mapbar__svg { width:26px; height:26px; }
/* The tool buttons at the mockup's size: 48px squares, 24px icons. */
.lb .mapbar__grp .gm-btn--icon, .lb .mapbar__loc, .lb .mapbar__split .gm-btn--icon { width:48px; height:48px; }
.lb .mapbar__grp .gm-btn--icon .mapbar__svg, .lb .mapbar__loc .mapbar__svg, .lb .mapbar__split-more .mapbar__svg { width:24px; height:24px; }
/* Fog: off until the map has fills, but keeping its place, so the first fill moves nothing. */
.lb .mapbar__grp[hidden] { display:inline-flex; visibility:hidden; }
/* The door and its ⌄: one split button. */
.lb .mapbar__split { display:inline-flex; }
.lb .mapbar__split .gm-btn + .gm-btn { margin-inline-start:-2px; }
.lb .mapbar__split .mapbar__split-more { width:40px; }
.lb .mapbar__split .mapbar__split-main { width:52px; padding:0; }
.lb .mapbar__split-main .mapbar__svg--door { width:30px; min-width:30px; height:36px; flex:none; }
/* The image's opacity: the picture and its % under it, both big enough to read (as the mockup). */
.lb .mapbar__drop--end .mapbar__ico { min-width:52px; padding:4px 8px; gap:2px; }
.lb .mapbar__drop--end .mapbar__ico .mapbar__svg { width:26px; height:26px; }
.lb .mapbar__drop--end .mapbar__ico-t { font-size:13px; font-weight:900; }
.lb .mapbar__drop.mapbar__drop--end .mapgrid-pop { inset-inline-start:auto; inset-inline-end:0; }
.lb .mapbar__hist { margin-inline-start:10px; }
.lb-menu button[disabled] { opacity:.45; cursor:default; }
.lb-menu .mapbar__menu-hint { display:block; font-size:12px; font-weight:600; opacity:.8; }
.lb .mapgrid-pop__switch { justify-content:space-between; }
/* DOORS: thick segments over the paths; a hidden door is dashed (and DM-only). */
.lb .mappage .mapdoors { position:absolute; inset:0; width:100%; height:100%; z-index:3; pointer-events:none; overflow:visible; }
.lb .mapdoor { stroke:#3a2c17; stroke-linecap:round; }
.lb .mapdoor--hidden { stroke:#5B4A8C; }
.lb .mapdoor--preview { stroke:#3a2c17; opacity:.6; }
.lb .mapdoor.is-sel { stroke:var(--accent); }
.lb .mapdoor--open { opacity:.7; }
.lb .mapdoor--open line { stroke:inherit; stroke-linecap:round; }
.lb .mapdoor--open .mapdoor__gap { stroke-dasharray:2 4; opacity:.6; }
.lb .mapdoor__dot { fill:#3a2c17; stroke:#fff; stroke-width:1.5; }
/* A LOCATION ON THE MAP: its icon in a round badge, on the middle of its square
   (the button is sized to the square in app.js). A link rings it in gold. */
.lb .map-pin--loc { color:#3a2c17; filter:drop-shadow(0 1px 2px rgba(58,44,23,.3)); }
.lb .map-pin--loc .maploc { display:grid; place-items:center; width:100%; height:100%; box-sizing:border-box; padding:16%;
  border-radius:50%; background:#fbf6ea; border:1.5px solid #3a2c17; transform-origin:50% 50%; }
.lb .map-pin--loc.map-pin--link .maploc { border:2.5px solid #865309; }
.lb .map-pin--loc .maploc svg, .lb .map-pin--loc .maploc img { width:100%; height:100%; display:block; border-radius:50%; object-fit:cover; }
.lb .map-pin--loc.is-selected .maploc { border:3px solid var(--accent); }
/* A HIDDEN location, as only the DM sees it: dimmed, with an eye-off mark. */
.lb .map-pin--loc.map-pin--hidden .maploc { opacity:.5; border-style:dashed; }
.lb .map-pin--loc .maploc__eye { position:absolute; right:-14%; bottom:-14%; width:46%; height:46%; padding:8%; box-sizing:border-box;
  border-radius:50%; background:#3a2c17; color:#fbf6ea; display:grid; place-items:center; pointer-events:none; }
.lb .map-pin--loc .maploc__eye svg { width:100%; height:100%; display:block; }
/* ONE HIT RULE: hovering a pin's square is hovering the pin. */
.lb .map-pin--loc:hover .maploc, .lb .map-pin--loc.is-hover .maploc { border-color:var(--gold2); background:#fff; }
.lb .map-pin--party.is-hover { filter:drop-shadow(0 0 0 2px var(--gold)) drop-shadow(0 2px 4px rgba(58,44,23,.35)); }
.lb .mappage #mapcv.mapcv--onpin { cursor:pointer; }
/* THE LOCATION MODALS (location-new-modal-mockup.png, location-open-modal-mockup.png). */
.lb-box.lb-box--loc { width:min(820px, 94vw); }
/* A trap springs (Maayan): the location's card bounces in when the party steps on it; a plain fade under reduced motion. */
.lb-box.lb-box--bounce { animation:locBounce .62s cubic-bezier(.22,1.4,.36,1) both; }
@keyframes locBounce { 0% { opacity:0; transform:scale(.6) translateY(24px); } 55% { opacity:1; transform:scale(1.06) translateY(-6px); } 78% { transform:scale(.97) translateY(2px); } 100% { opacity:1; transform:none; } }
@media (prefers-reduced-motion: reduce){ .lb-box.lb-box--bounce { animation:locFade .25s ease both; } }
@keyframes locFade { from { opacity:0; } to { opacity:1; } }
.lb-box.lb-box--loc .loc-modal__acts { justify-content:flex-start; margin-top:22px; }
.lb-box .locmodal__head { align-items:center; gap:18px; flex-wrap:nowrap; }
.lb-box .locicon-split { display:inline-flex; flex:0 0 auto; }
.lb-box .locicon-split .gm-btn { width:54px; height:54px; padding:0; margin:0; }
.lb-box .locicon-split .gm-btn + .gm-btn { width:40px; margin-inline-start:-2px; }
.lb-box .locicon-split__icon svg, .lb-box .locicon-split__icon img { width:30px; height:30px; display:block; object-fit:cover; border-radius:4px; }
.lb-box .locicon-split .mapbar__svg { width:22px; height:22px; }
.lb-box .locicon-split__icon--ro { display:grid; place-items:center; width:54px; height:54px; border:2px solid var(--edge); flex:0 0 auto; }
.lb-box .locmodal__title { margin:0; }
.lb-box .locmodal__name { flex:1 1 auto; min-width:0; font-size:26px; font-weight:900; padding:2px 0 6px; border-bottom:2px solid var(--ink); outline:none; }
.lb-box .locmodal__body { display:grid; grid-template-columns:minmax(0,1.45fr) minmax(0,1fr); gap:28px; margin-top:18px; }
.lb-box .locmodal__fields { display:flex; flex-direction:column; }
.lb-box .locmodal__pic { align-self:stretch; min-height:170px; margin-top:28px; border:2px dashed var(--edge); background:var(--sheet2); color:var(--ink3);
  font:700 17px 'Grandstander', system-ui, sans-serif; cursor:pointer; display:grid; place-items:center; padding:0; overflow:hidden; }
.lb-box .locmodal__pic img { width:100%; height:100%; object-fit:cover; display:block; }
.lb-box .locmodal__pic:hover { border-color:var(--gold); color:var(--ink); }
.lb-box .locmodal__img { display:block; width:100%; height:auto; max-height:46vh; object-fit:cover; border:1.5px solid var(--edge); margin-top:16px; }
.lb-box .locmodal__sect { margin-top:22px; }
.lb-box .locmodal__k { color:#9a9286; font-size:17px; margin-bottom:6px; }
.lb-box .locmodal__v { font-size:19px; white-space:pre-wrap; line-height:1.45; }
.lb-box .locmodal__name--ro { border-bottom:0; }
.lb-box .locmodal__grid { display:grid; grid-template-columns:repeat(2, minmax(0, 260px)); gap:20px; margin-top:26px; }
.lb-box .locmodal__things { margin:0; padding-inline-start:18px; line-height:1.6; }
.lb-box .locmodal__switches { display:flex; flex-wrap:wrap; gap:12px 28px; margin-top:22px; padding-top:16px; border-top:1.5px solid var(--edge); }
.lb-box .locmodal__switch.is-off { opacity:.5; }
@media (max-width:560px){ .lb-box .locmodal__body { grid-template-columns:1fr; } .lb-box .locmodal__pic { margin-top:0; } }
/* ADDING TO THE MAP (app.js placeOnMap): what it is, then where it comes from; a picker lists rows to tap. */
.lb-box .spotpick { display:flex; flex-wrap:wrap; gap:8px; margin-top:16px; }
.lb-box .spotpick--src { margin-top:10px; padding-bottom:4px; }
.lb-box .spotpick + .locmodal__fields, .lb-box .spotpick + .locmodal__body, .lb-box .spotpick + .gm-search, .lb-box .spotpick + .empty { margin-top:18px; }
.lb-box .spotrows { display:flex; flex-direction:column; gap:8px; max-height:min(46vh, 420px); overflow:auto; margin-top:10px; padding:2px; }
/* THE ICON PICKER: a pop-up under the icon's ⌄, over the modal. */
.lociconpop { position:fixed; z-index:1200; width:min(420px, 92vw); max-height:min(60vh, 520px); overflow:auto; padding:10px 12px; box-sizing:border-box; }
.lociconpop .spoticons__wrap { max-height:none; }
.lociconpop__own { margin-top:10px; }
.lb-box .spoticons__wrap, .lociconpop .spoticons__wrap { max-height:62vh; overflow:auto; }
.lb-box .spoticons__cat, .lociconpop .spoticons__cat { margin:14px 0 6px; font-size:13px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; color:var(--ink2, #6f5c3c); }
.lb-box .spoticons__cat:first-child, .lociconpop .spoticons__cat:first-child { margin-top:4px; }
.lb-box .spoticons, .lociconpop .spoticons { display:grid; grid-template-columns:repeat(auto-fill, minmax(48px, 1fr)); gap:6px; padding-top:4px; }
.lb-box .spoticons__i, .lociconpop .spoticons__i { display:grid; place-items:center; aspect-ratio:1; padding:8px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); cursor:pointer; }
.lb-box .spoticons__i:hover, .lb-box .spoticons__i:focus-visible, .lociconpop .spoticons__i:hover, .lociconpop .spoticons__i:focus-visible { border-color:var(--gold); outline:none; }
.lb-box .spoticons__i.is-on, .lociconpop .spoticons__i.is-on { border-color:var(--ink); background:var(--sheet2); }
.lb-box .spoticons__i svg, .lb-box .spoticons__i img, .lociconpop .spoticons__i svg, .lociconpop .spoticons__i img { width:100%; height:100%; display:block; object-fit:cover; }
.lb-box .spotlink__bad { color:#9b2c1f; margin:0 0 8px; }
.lb-box .spotask__q { margin:4px 0 16px; }
/* The Saved locations list: a location with no picture shows its icon. */
.lb .locthumb__icon { display:grid; place-items:center; width:46px; height:46px; padding:10px; border-radius:50%; background:#fbf6ea; border:1.5px solid #3a2c17; box-sizing:border-box; }
.lb .locthumb__icon svg, .lb .locthumb__icon img { width:100%; height:100%; display:block; object-fit:cover; border-radius:50%; }
.lb .mapdoor__dot--hover { fill:var(--accent); }
.lb .mapview--door #mapcv { cursor:crosshair; }
/* A hint over the map (placing a door, a spot): in a holder with no height that
   sticks to the top of the view, so it floats over the map and never pushes it down. */
.lb .mapdoorhint-hold { position:sticky; top:0; left:0; height:0; z-index:8; display:flex; justify-content:center; pointer-events:none; }
.lb .mapdoorhint {
  width:max-content; max-width:calc(100% - 24px); margin-top:8px; height:max-content;
  padding:6px 12px; background:var(--ink); color:var(--paper); font-weight:700; font-size:14px; border-radius:999px;
}
.lb .mapdoorhint[hidden] { display:none; }
/* The pen's Done: the hint holds a button instead of words, and takes the tap. */
.lb .mapdoorhint--btn { padding:0; background:none; border-radius:0; pointer-events:auto; }
/* FOG, over the paths and doors and under the pins. PRESENT shows or hides the
   existing buttons, never moves them; the paths and hidden doors are not shown. */
.lb .mappage .mapfog { position:absolute; inset:0; width:100%; height:100%; z-index:3; pointer-events:none; }
/* Drawing in Present, snoozed (PRESENT_SKETCH in app.js): its controls hidden, not moved. */
.lb .mapstack--nosketch #t-ink,
.lb .mapstack--nosketch #t-brush,
.lb .mapstack--nosketch .mapbar__hist { visibility:hidden; }
/* Present's Sketch layer, over the fog, for everyone; only in Present. */
.lb .mappage .mapoverlay { position:absolute; inset:0; width:100%; height:100%; z-index:3; pointer-events:none; display:none; }
.lb .mapstack--present .mappage .mapoverlay { display:block; }
.lb .mapstack--present .mappage .mappaths,
.lb .mapstack--present .mapdoor--hidden:not(.mapdoor--open),
.lb .mapstack--present #t-lib,
.lb .mapstack--present #t-mapimg,
.lb .mapstack--present #t-imgvis,
.lb .mapstack--present #t-locpin,
.lb .mapstack--present #t-element { visibility:hidden; }
/* PRESENT'S HEADER (Maayan): only the map's name, centred, plain (not an editable
   field), and "Exit Present Mode" at the end. The tool rows are not there:
   no fog brushes, no drawing; the side bar keeps what the table needs. */
.lb .mapstack--present .mapbar__row:not(.mapbar__row--head) { display:none; }
.lb .mapbar__row--present { display:grid; grid-template-columns:1fr minmax(0,auto) 1fr; align-items:center; }
.lb .mapbar__name--present { border-bottom:0; text-align:center; padding:2px 8px; }
.lb .mapbar__side--end { display:flex; justify-content:flex-end; }
/* The map's menus: a check mark, a heading. */
.lb-menu .mapmenu__check { display:inline-block; width:18px; font-weight:900; }
.lb-menu button[disabled] .mapmenu__head { font-size:12px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; }
/* The map and its side bar */
.lb .mapbody { flex:1; min-height:0; display:flex; position:relative; }
/* THE MAP'S HINTS (app.js syncDoorHint): a toast over the map, outside its scroll box, so it never moves the map. */
.lb .maptoast { position:absolute; top:10px; left:50%; transform:translateX(-50%); z-index:20; display:flex; align-items:center; gap:6px;
  max-width:calc(100% - 24px); box-sizing:border-box; padding-block:6px; padding-inline:14px 6px; background:var(--ink); color:var(--paper);
  font-weight:700; font-size:14px; border-radius:999px; box-shadow:0 4px 14px rgba(0,0,0,.25); opacity:1; transition:opacity .4s ease; }
.lb .maptoast[hidden] { display:none; }
.lb .maptoast.is-out { opacity:0; pointer-events:none; }
.lb .maptoast__x { flex:0 0 auto; width:28px; height:28px; padding:0; border:0; border-radius:50%; background:rgba(255,255,255,.14); color:inherit;
  font-size:18px; line-height:1; cursor:pointer; }
.lb .maptoast__x:hover { background:rgba(255,255,255,.28); }
.lb .maptoast__x:focus-visible { outline:2px solid var(--gold2); outline-offset:2px; }
@media (prefers-reduced-motion:reduce){ .lb .maptoast { transition:none; } }
.lb .mapside {
  flex:0 0 auto; display:flex; flex-direction:column; align-items:center; gap:10px;
  padding:12px 10px; border-inline-start:2px solid var(--edge); background:var(--paper); overflow-y:auto;
}
.lb .mapside .gm-btn { margin-top:0; }
.lb .mapside__ink { width:44px; height:44px; padding:0; }
.lb .mapside__ink[disabled] { opacity:1; cursor:default; }
.lb .mapside__swatch { display:block; width:24px; height:24px; border-radius:6px; background:var(--ink); box-shadow:inset 0 0 0 1px rgba(58,44,23,.25); }
.lb-menu .mapside__swatch--sm { display:inline-block; width:16px; height:16px; border-radius:4px; vertical-align:middle; margin-inline-end:8px; background:var(--ink); box-shadow:inset 0 0 0 1px rgba(58,44,23,.25); }
.lb .mapside__brush { display:flex; flex-direction:column; align-items:center; gap:8px; padding:10px 4px; border:2px solid var(--edge); }
.lb .mapside__brush input[type=range] { writing-mode:vertical-lr; direction:rtl; width:28px; height:120px; accent-color:var(--gold); margin:0; }
.lb .mapside__brush.is-off { opacity:.45; }
.lb .mapside__gap { flex:1 1 auto; min-height:8px; }
/* FULL SCREEN (Present only). The button takes the top of the side column, the
   slot the hidden colour swatch keeps in Present, so nothing else moves. It is
   centred by its box (left, right, auto margins), never by a transform: a
   press scales buttons with a transform, which would throw the centring away
   and make the button jump. */
.lb .mapside { position:relative; }
.lb .mapside__fs { position:absolute; top:var(--mapside-pad, 12px); left:0; right:0; margin-inline:auto; z-index:1; }
.lb .mapside__fs[hidden] { display:none; }
/* In full screen there is ONLY the map (Maayan): no bar, no side column, no
   buttons; the map fits the whole display on black. The fog, the party, spots
   and doors are drawn as in Present, the arrow keys still move the party, and
   the browser's Esc leaves. */
.lb-stage:fullscreen, .lb-stage:-webkit-full-screen { background:#000; width:100vw; height:100vh; max-width:none; overflow:hidden; display:flex; flex-direction:column; }
.lb-stage:fullscreen .mapstack, .lb-stage:-webkit-full-screen .mapstack { height:100%; }
.lb-stage:fullscreen .mapbar, .lb-stage:fullscreen .mapside,
.lb-stage:-webkit-full-screen .mapbar, .lb-stage:-webkit-full-screen .mapside { display:none; }
.lb-stage:fullscreen .mapview, .lb-stage:-webkit-full-screen .mapview { background:#000; }
.lb .mapside__zoom { display:flex; flex-direction:column; align-items:center; gap:0; }
.lb .mapside__zoom .gm-btn + .gm-btn { margin-top:-2px; }
.lb .mapside__pct { font-family:'Grandstander',system-ui,sans-serif; font-weight:700; font-size:14px; color:var(--ink2); margin-top:6px; }
/* The PATHS layer: painted solid, shown red at a fixed opacity, only while painting
   paths; the rest of the map fades back. The pen's preview sits on top of it. */
.lb .mappage .mappaths, .lb .mappage .mappen {
  position:absolute; inset:0; width:100%; height:100%; pointer-events:none;
}
/* The FILLS show red over the map whenever the GM is editing (ONE VECTOR LAYER); Present hides them under the fog. */
.lb .mappage .mappaths { z-index:2; opacity:.45; display:none; }
.lb .mappage .mappen { z-index:2; }
.lb .mappage.mappage--fills .mappaths { display:block; }
@media (max-width:560px){
  .lb .mapbar { padding:10px; gap:8px; }
  .lb .mapbar__name { font-size:17px; }
  .lb .mapbar .gm-btn { padding-inline:10px; }
  .lb .mapbar .gm-btn--icon { width:40px; height:40px; padding:0; }
  .lb .mapbar__ico { min-width:40px; padding:3px 6px; }
  .lb .mapside { padding:8px 6px; gap:8px; }
  .lb .mapside__fs { top:8px; }
  .lb .mapside__brush input[type=range] { height:90px; }
}
.lb .mapview {
  flex:1; min-height:0; overflow:auto; -webkit-overflow-scrolling:touch;
  background:var(--paper); position:relative;
  overscroll-behavior:contain;
  /* A map has no reading direction: its scroll counts from the left in every language, as its zoom (bumpMapZoom) and
     its pan count. Right to left, a Hebrew page's scroll started at the right, so a pinch drifted off the fingers and
     a drag sideways went nowhere. What it shows (names, hints) keeps the page's direction. */
  direction:ltr;
}
html[dir="rtl"] .lb .mapview > * { direction:rtl; }
.lb .mapview--hand #mapcv { cursor:grab; }
.lb .mapview--hand #mapcv:active { cursor:grabbing; }
.lb .mapview--pickpin #mapcv { cursor:cell; }
/* The sketch a painted map was made from: out of sight, still under the pointer,
   so a brush stroke can bring it back (setMapTool / setupCanvas start). */
.lb .mappage #mapcv.mapcv--hidden { opacity:0; }
/* Full zoom-out: page sits inset with visible padding around it */
.lb .mapview--pad {
  display:flex; align-items:center; justify-content:center;
  padding:22px 18px; box-sizing:border-box;
}
.lb .mapview--pad .mapzoom { margin:auto; }
/* Zoomed in enough to fill: no padding, scroll the page */
.lb .mapview--fill { display:block; padding:0; }
.lb .mapzoom { position:relative; flex:0 0 auto; line-height:0; }
.lb .mappage {
  position:absolute; top:0; left:0;
  transform-origin:0 0;
  background:var(--sheet);
  /* An OUTLINE, not a border, and this is the whole reason the grid lines used to
     sit a few pixels off the square they were outlining.

     The page is sized in map units (width:1400px means the map is 1400 wide) and
     a cell is that width over cols. But every layer inside it (the grid lines,
     the highlight tracks, the canvas, the picture) is absolutely positioned with
     inset:0, and absolute positioning resolves against the PADDING box. A 2px
     border made that box 1396 wide, so the lines tiled every 1400/17 while the
     highlight tracks measured 1396/17: a quarter of a pixel per column, which is
     invisible at column 1 and a clear miss by column 10. The canvas had the same
     quarter-pixel error, since its bitmap is 1400 wide and it was displayed at
     1396.

     An outline draws in the same place and takes no space, so the padding box is
     the full 1400x900 and one coordinate space serves the lines, the squares, the
     pins and the drawing. */
  outline:2px solid var(--line);
  outline-offset:-2px;
  box-sizing:border-box;
  overflow:hidden;
  /* Pin x/y and canvas coords are physical left→right; grid-column must match. */
  direction:ltr;
  unicode-bidi:isolate;
}
.lb .mappage .mapbg {
  position:absolute; inset:0; width:100%; height:100%; object-fit:contain;
  pointer-events:none; z-index:0;
}
.lb .mappage .mapbg--off { display:none; }
.lb .mappage .mapbg--fade { opacity:.42; }
.lb .mappage .mapgrid {
  position:absolute; inset:0; z-index:1; pointer-events:none;
  background-color:transparent;
  --map-grid-line: color-mix(in srgb, var(--edge) 50%, transparent);
  background-image:
    linear-gradient(to bottom, var(--map-grid-line) 1px, transparent 1px),
    linear-gradient(to right, var(--map-grid-line) 1px, transparent 1px);
  /* One size per axis, matching the 1fr tracks the highlight layer uses. A single
     square size drew lines that slid away from the squares they were meant to
     outline, by more the further down the map you looked. */
  background-size:var(--map-cell-x,34px) var(--map-cell-y,34px);
}
.lb .mappage .mapgrid[hidden] { display:none; }
.lb .mappage #mapcv {
  display:block; width:100%; height:100%;
  position:absolute; inset:0; z-index:2; background:transparent;
  cursor:crosshair; touch-action:none;
}
.lb .mappage .mapcells {
  position:absolute; inset:0; z-index:3; pointer-events:none;
  display:grid;
  grid-template-columns:repeat(var(--map-cols, 24), 1fr);
  grid-template-rows:repeat(var(--map-rows, 16), 1fr);
}
.lb .map-cell-hl {
  box-sizing:border-box; pointer-events:none; min-width:0; min-height:0;
}
.lb .map-cell-hl--loc { background:rgba(255,255,255,.42); }
.lb .map-cell-hl--selected,
.lb .map-cell-hl--party,
.lb .map-cell-hl--hover { background:rgba(255,214,90,.48); display:grid; place-items:center; position:relative; }
/* The square a dragged pin will land in: the same gold, with an edge so it reads as "here". */
.lb .map-cell-hl--drop { background:rgba(255,214,90,.55); box-shadow:inset 0 0 0 2px rgba(134,83,9,.85); }
/* Both sized as a share of the square, so they read the same at any zoom. */
.lb .map-cell-hl__plus { width:46%; height:46%; color:var(--ink); opacity:.72; }
/* Out of the flow, so the plus keeps the exact middle of the square. The plus is
   46% tall and centred, so its top edge is at 73% from the bottom; the pin sits a
   short gap above that and is free to hang over the square's edge. */
.lb .map-cell-hl__pin { position:absolute; left:50%; bottom:79%; transform:translateX(-50%);
  width:88%; height:88%; color:var(--ink); opacity:.72; }
.lb .mappage .mapmarkers {
  position:absolute; inset:0; z-index:4; pointer-events:none;
}
.lb .mappage .mapgen {
  position:absolute; inset:0; z-index:5;
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:12px; background:color-mix(in srgb, var(--sheet) 78%, transparent);
  color:var(--ink); backdrop-filter:blur(1px);
}
.lb .mappage .mapgen[hidden] { display:none; }
.lb .mappage .mapgen .mapgen__lbl { font-size:13px; letter-spacing:.04em; opacity:.8; }
.lb .map-pin {
  position:absolute;
  width:28px; height:28px; padding:0; border:0; background:none; cursor:pointer;
  pointer-events:auto; color:var(--red); filter:drop-shadow(0 1px 2px rgba(58,44,23,.35));
  -webkit-tap-highlight-color:transparent; touch-action:none;
  /* transform set inline with left/top (% anchor = pin tip) */
}
/* The pin's own transform is rewritten on every pointer move, so pick-up and
   landing animate this instead. Origin at the tip, because a pin is lifted from
   the point it stands on. */
.lb .map-pin__in { display:block; width:100%; height:100%; transform-origin:50% 100%; }
.lb .map-pin.is-dragging { z-index:6; }
.lb .map-pin.is-selected { filter:drop-shadow(0 0 0 2px var(--gold)) drop-shadow(0 1px 2px rgba(58,44,23,.35)); }
.lb .map-pin.is-dragging { cursor:grabbing; filter:drop-shadow(0 4px 10px rgba(58,44,23,.28)); }
.lb .map-pin--party { color:var(--accent); width:38px; height:46px; transform-origin:50% 100%; }
.lb .map-pin--party.is-dragging { cursor:grabbing; }
.lb .map-pin__svg { display:block; width:100%; height:100%; }
.lb .map-pin__svg--party { overflow:visible; filter:drop-shadow(0 1px 1px rgba(58,44,23,.22)); }
/* cloak.png is white-on-black — screen blend keeps the white hood on the orange pin */
.lb .map-pin__cloak {
  position:absolute; left:50%; top:31%; width:54%; height:auto;
  transform:translate(-50%,-50%); pointer-events:none; object-fit:contain;
  mix-blend-mode:screen;
}
.lb .maptoolbar__group--grid { position:relative; gap:8px; }
/* Brush size: only while the pencil or eraser is in hand. [hidden] has to be spelled
   out because the group's inline-flex would otherwise beat it. The well is as wide
   as the largest dot, so the slider does not shuffle sideways as the dot grows. */
.lb .maptoolbar__group--brush[hidden] { display:none; }
.lb .mapbrush__well { width:28px; height:28px; display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto; }
.lb .mapbrush__dot { display:block; border-radius:50%; background:var(--ink); }
.lb .maptoolbar__group--brush input[type=range] { width:110px; accent-color:var(--gold); }
.lb .mappage .mapbrush-cur {
  position:absolute; z-index:5; pointer-events:none; box-sizing:border-box;
  min-width:3px; min-height:3px; border-radius:50%; transform:translate(-50%,-50%);
  border:1.5px solid rgba(58,44,23,.9); box-shadow:0 0 0 1px rgba(255,255,255,.75);
}
.lb .mappage .mapbrush-cur--erase { border-style:dashed; }
.lb .mappage .mapbrush-cur[hidden] { display:none; }
.lb .mapgrid-trigger {
  font:inherit; font-family:'Grandstander',system-ui,sans-serif; font-weight:700; font-size:14px;
  color:var(--ink); padding:6px 10px; border:2px solid var(--edge); background:var(--paper);
  cursor:pointer; border-radius:0; -webkit-tap-highlight-color:transparent;
}
.lb .mapgrid-trigger:hover { border-color:var(--gold2); }
.lb .mapgrid-pop {
  position:absolute; top:calc(100% + 6px); inset-inline-start:0; z-index:30;
  min-width:min(280px, 92vw); padding:12px 14px; box-sizing:border-box;
  background:var(--paper); border:2px solid var(--edge);
  box-shadow:0 10px 28px rgba(58,44,23,.16);
}
.lb .mapgrid-pop__row { display:flex; flex-direction:column; gap:8px; margin-bottom:12px; }
.lb .mapgrid-pop__row:last-child { margin-bottom:0; }
.lb .mapgrid-pop__lbl { display:flex; align-items:center; justify-content:space-between; gap:10px;
  font-size:13px; font-weight:700; color:var(--ink2); }
.lb .mapgrid-pop input[type="range"] { width:100%; accent-color:var(--gold); }
.lb .mapgrid-pop__hint { margin:4px 0 0; line-height:1.35; color:var(--ink3); }
.lb .mapgrid-pop__unit { display:flex; gap:8px; align-items:center; margin-top:6px; }
.lb .mapgrid-pop__num { width:5em; font:inherit; font-family:inherit; padding:6px 8px; border:2px solid var(--edge); background:var(--paper); }
.lb .mapgrid-pop__sel { font:inherit; font-family:inherit; padding:6px 8px; border:2px solid var(--edge); background:var(--paper); min-width:7em; }
.lb .mapgrid-pop__row--unit { margin-top:12px; padding-top:12px; border-top:1px solid var(--edge); }
.lb .mapgrid-swatches { display:flex; flex-wrap:wrap; gap:8px; }
.lb .mapgrid-swatch {
  width:28px; height:28px; padding:0; border:2px solid var(--edge); border-radius:0;
  background:var(--swatch); cursor:pointer; flex:0 0 auto;
  box-shadow:inset 0 0 0 1px rgba(58,44,23,.12);
  -webkit-tap-highlight-color:transparent;
}
.lb .mapgrid-swatch:hover { border-color:var(--gold2); }
.lb .mapgrid-swatch.is-active {
  border-color:var(--gold); box-shadow:0 0 0 2px var(--gold);
}

/* iOS-style switch (Grid on/off) */
/* Scoped to BOTH .lb and .lb-ovl: the switch also appears in the Settings modal,
   which renders at body level inside .lb-ovl, not inside .lb. */
.lb .gm-switch, .lb-ovl .gm-switch {
  display:inline-flex; align-items:center; gap:8px; cursor:pointer; user-select:none;
  font-family:'Grandstander',system-ui,sans-serif; font-weight:700; font-size:15px; color:var(--ink);
}
.lb .gm-switch input, .lb-ovl .gm-switch input {
  position:absolute; opacity:0; width:1px; height:1px; margin:0; pointer-events:none;
}
.lb .gm-switch__ui, .lb-ovl .gm-switch__ui {
  position:relative; flex:0 0 auto; width:44px; height:26px; border-radius:999px;
  background:var(--edge); border:2px solid var(--line); box-sizing:border-box;
  transition:background .18s ease, border-color .18s ease;
}
.lb .gm-switch__ui::after, .lb-ovl .gm-switch__ui::after {
  content:""; position:absolute; top:2px; left:2px; width:18px; height:18px; border-radius:50%;
  background:#fff; box-shadow:0 1px 3px rgba(58,44,23,.35);
  transition:transform .18s ease;
}
/* On = iOS green (not the brand orange/brown). */
.lb .gm-switch input:checked + .gm-switch__ui, .lb-ovl .gm-switch input:checked + .gm-switch__ui {
  background:#34c759; border-color:#2ba149;
}
.lb .gm-switch input:checked + .gm-switch__ui::after, .lb-ovl .gm-switch input:checked + .gm-switch__ui::after { transform:translateX(18px); }
.lb .gm-switch input:focus-visible + .gm-switch__ui, .lb-ovl .gm-switch input:focus-visible + .gm-switch__ui { outline:2px solid var(--gold2); outline-offset:2px; }
html[dir="rtl"] .gm-switch__ui::after { left:auto; right:2px; }
html[dir="rtl"] .gm-switch input:checked + .gm-switch__ui::after { transform:translateX(-18px); }
@media (prefers-reduced-motion:reduce){
  .lb .gm-switch__ui, .lb .gm-switch__ui::after, .lb-ovl .gm-switch__ui, .lb-ovl .gm-switch__ui::after { transition:none; }
}

.lb .maplibbody {
  flex:1; min-height:0; overflow:auto; -webkit-overflow-scrolling:touch;
  padding:18px; box-sizing:border-box;
}
.lb .maplib { display:grid; grid-template-columns:repeat(auto-fill,minmax(150px,1fr)); gap:12px; }
.lb .shopstack .maplib { grid-template-columns:repeat(auto-fill,minmax(240px,1fr)); gap:14px; }
.lb .mapthumb, .lb-ovl .mapthumb { border:2px solid var(--edge); background:var(--sheet2); cursor:pointer; }
.lb .mapthumb.on, .lb-ovl .mapthumb.on { border-color:var(--red); }
.lb .mapthumb .th, .lb-ovl .mapthumb .th {
  height:96px; background:var(--sheet); display:flex; align-items:center; justify-content:center; overflow:hidden;
}
.lb .mapthumb .th img, .lb-ovl .mapthumb .th img { width:100%; height:100%; object-fit:cover; }
.lb .mapthumb .th.plus, .lb-ovl .mapthumb .th.plus { font-size:34px; color:var(--ink3); }
.lb .mapthumb .cap, .lb-ovl .mapthumb .cap { padding:7px 9px; font-size:16px; display:flex; align-items:center; gap:6px; justify-content:space-between; }

.lb .shopthumb .th { background:var(--sheet3); display:flex; align-items:center; justify-content:center; }
.lb .shopthumb__name { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.lb .loclib { grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); }
.lb .locthumb .th { background:var(--sheet3); display:flex; align-items:center; justify-content:center; }
.lb .locthumb__name { flex:1; min-width:0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.lb .loc-mappick__list { display:flex; flex-direction:column; gap:8px; }
.lb .loc-mappick__btn {
  font:inherit; font-family:inherit; text-align:start; width:100%;
  padding:10px 12px; border:2px solid var(--edge); background:var(--paper); cursor:pointer;
}
.lb .loc-mappick__btn:hover { border-color:var(--gold2); background:var(--sheet2); }
.lb .loc-mappick__name { font-weight:700; font-size:16px; }
.lb .loc-things__h { font-size:18px; margin:0 0 10px; }
.lb .loc-things__list { display:flex; flex-direction:column; gap:8px; margin-bottom:12px; }
.lb .loc-thing { display:flex; flex-wrap:wrap; gap:6px 10px; align-items:baseline; padding:10px 12px; background:var(--sheet2); border:2px solid var(--edge); }
.lb .loc-thing__name { flex:1 1 140px; font-size:16px; }
.lb .loc-thing__note { flex:2 1 180px; color:var(--ink3); }
.lb .loc-thing__rm { flex:0 0 auto; background:none; border:0; font-size:22px; line-height:1; cursor:pointer; color:var(--ink3); padding:0 4px; }
.lb .loc-thing__rm:hover { color:var(--red); }
.lb .loc-banner { min-height:140px; }
.lb-ovl .loc-modal__lbl { display:block; font-weight:700; margin:12px 0 6px; font-size:15px; }
.lb-ovl .loc-modal__in, .lb-ovl .loc-modal__ta {
  width:100%; box-sizing:border-box; font:inherit; font-family:inherit;
  padding:8px 10px; border:2px solid var(--edge); background:var(--paper);
}
.lb-ovl .loc-modal__acts { display:flex; flex-wrap:wrap; gap:8px; margin-top:16px; justify-content:flex-end; }
.lb .shoptoolbar { border-bottom:2px solid var(--edge); }
.lb .shop2 { width:100%; flex:1; min-height:0; display:flex; flex-direction:column; }
.lb .shop-scroll { flex:1; min-height:0; overflow:auto; -webkit-overflow-scrolling:touch; display:flex; flex-direction:column; padding-bottom:26px; }
/* Shop cover: a horizontal image thumbnail (like a character portrait, wider).
   Self-contained so it holds its box even when empty. */
.lb .shop-banner-wrap { flex:0 0 auto; padding:14px 18px 0; position:relative; }
.lb .shop-banner, .lb .shop-cover {
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  width:100%; max-width:340px; aspect-ratio:3 / 2; min-height:120px; gap:8px;
  background:var(--sheet2); border:2px dashed var(--edge); border-radius:2px;
  color:var(--ink3); text-align:center; font-size:16px; cursor:pointer; overflow:hidden; position:relative; }
.lb .shop-banner img, .lb .shop-cover img { width:100%; height:100%; object-fit:cover; display:block; }
.lb .shop-banner.is-gen, .lb .shop-cover.is-gen { border-style:solid; border-color:var(--gold2); cursor:default; }
.lb .shop-banner.has-img, .lb .shop-cover.has-img { cursor:default; border-style:solid; }
/* fixed header — name • description + category tabs (stays like the Battle toolbar) */
.lb .shop-head { flex:0 0 auto; background:var(--paper); border-bottom:2px solid var(--edge); padding:12px 18px 0; }
.lb .shop-id { display:flex; align-items:baseline; gap:8px; flex-wrap:wrap; margin-bottom:10px; }
.lb .shop-id b { font-family:'Frank Ruhl Libre',serif; font-weight:900; font-size:22px; color:var(--ink); }
.lb .shop-id .sep { color:var(--ink3); font-size:20px; }
.lb .shop-id .shop-desc { font-size:16px; color:var(--ink2); font-weight:600; }
.lb .shop-id b:empty:before, .lb .shop-id .shop-desc:empty:before { content:attr(data-ph); color:var(--ink3); font-weight:400; }
.lb .shop-cats { display:flex; gap:2px; flex-wrap:wrap; }
.lb .shop-cat { background:none; border:2px solid transparent; border-bottom:0; padding:8px 16px; font:inherit;
  font-weight:700; font-size:16px; color:var(--ink2); cursor:pointer; position:relative; top:2px; }
.lb .shop-cat:hover { color:var(--ink); }
.lb .shop-cat.on { background:var(--sheet); border-color:var(--line); color:var(--ink); }
.lb .shop-cat--add, .lb .shop-cat--edit { padding:8px 12px; color:var(--ink2); font-size:18px; line-height:1; }
.lb .shop-cat--add:hover, .lb .shop-cat--edit:hover { color:var(--ink); }
.lb .shop-scroll > .gm-btn { align-self:flex-start; margin:14px 18px 0; }
.lb .shop-scroll__acts { display:flex; flex-wrap:wrap; gap:10px; align-items:center; margin:14px 18px 0; }
.lb .shop-scroll__acts .gm-btn { margin:0; }
/* header actions (Generate / Archive) sit at the end of the name line */
.lb .shop-id { align-items:center; }
.lb .shop-head .gm-btn { margin-top:0; }
.lb .shop-head .shop-id .gm-btn { margin-inline-start:6px; }
/* item card — name+⋮ → image → description → rule → category */
.lb .itemcard__head { display:flex; align-items:flex-start; gap:8px; margin:0 0 12px; }
.lb .itemcard__name { flex:1; min-width:0; margin:0; font-size:28px; font-weight:900; line-height:1.15;
  border-bottom:2px dotted transparent; }
.lb .itemcard__name.ed { display:block; width:100%; border-bottom-color:var(--edge); }
.lb .itemcard__name:empty:before { content:attr(data-ph); color:var(--ink3); font-weight:700; }
.lb .itemcard__head .sheetmenu { flex:0 0 auto; align-self:stretch; min-height:42px; }
.lb .shopcard .itempic { display:flex; width:100%; max-width:280px; min-height:0; aspect-ratio:4 / 3; margin:0 auto 12px; }
/* Contain, not cover: these pictures are objects on a plain ground, not scenery,
   and a mail shirt or a staff cropped to 4:3 loses the ends that say what it is.
   Character and foe portraits already settled on the same answer. */
.lb .itemcard__desc { display:block; width:100%; box-sizing:border-box; min-height:2.6em; margin:0 0 14px;
  font-size:16px; font-weight:600; line-height:1.35; color:var(--ink2); border-bottom:2px dotted var(--edge); }
.lb .itemcard__desc:empty:before { content:attr(data-ph); color:var(--ink3); font-weight:500; }
.lb .itemcard__rule { border:0; border-top:2px solid var(--edge); margin:4px 0 14px; }
.lb .itemcard__meta { display:flex; flex-wrap:wrap; gap:12px 18px; align-items:flex-end; margin:0 0 10px; }
.lb .itemcard__cat, .lb .itemcard__price, .lb .itemcard__uses { display:flex; flex-direction:column; gap:4px; min-width:0; }
.lb .itemcard__cat { flex:1 1 10rem; }
.lb .itemcard__price .ed { font-size:20px; font-weight:800; }
/* Gold, silver, copper, always in that order. The row is forced LTR so Hebrew
   does not reverse it into copper-first: the coins are a place-value sequence
   like a number, not a list of words. */
.lb .itemcard__price .coinrow { display:flex; align-items:baseline; gap:12px; direction:ltr; }
.lb .coinfld { display:inline-flex; align-items:baseline; gap:3px; }
.lb .coinfld .ed { min-width:1.7em; text-align:center; border-bottom:2px dotted var(--edge); }
.lb .coinfld i { font-style:normal; font-weight:700; color:var(--ink3); }
.lb .itemcard__uses .item-fx__input { max-width:6rem; }
.lb .shopcard .grid3 { margin-bottom:14px; }
.lb .shopcard .grid3:last-child { margin-bottom:0; }

/* equipment paper doll — above inventory grid */
.lb .equip-doll-wrap { margin:0 0 16px; }
.lb .equip-doll { position:relative; display:grid; grid-template-columns:1fr 1fr 1fr; gap:10px;
  max-width:320px; margin:0 auto 10px; padding:12px 8px 8px; min-height:300px; }
.lb .equip-doll__silhouette { position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  pointer-events:none; z-index:0; border:none; background:none; border-radius:0; }
.lb .equip-doll__figure { width:42%; max-width:110px; height:auto; color:var(--ink3); }
.lb .equip-slot { position:relative; z-index:1; display:flex; flex-direction:column; align-items:center; gap:4px;
  margin:0; padding:5px; background:var(--paper); border:2px solid var(--edge); box-shadow:inset 0 0 0 1px var(--paper);
  color:var(--ink2); cursor:pointer; font:inherit; font-size:11px; font-weight:700;
  -webkit-tap-highlight-color:transparent; min-width:0; }
.lb .equip-slot:hover { border-color:var(--gold2); color:var(--ink); }
.lb .equip-slot.has-item { border-color:var(--gold); color:var(--ink); }
.lb .equip-slot__pic { position:relative; display:flex; align-items:center; justify-content:center; width:100%; aspect-ratio:1;
  background:transparent; border:none; overflow:hidden; }
/* two-handed marker: same item shows in both hand cells, tagged 2H so it reads as one weapon */
.lb .equip-slot__2h { position:absolute; bottom:2px; right:2px; font-size:9px; font-weight:800; line-height:1;
  padding:1px 3px; border-radius:4px; background:var(--gold); color:#fff; letter-spacing:.02em; pointer-events:none; }
.lb .equip-slot--twohand { border-color:var(--gold); }
/* Empty slot = a placeholder: the icon BOX gets a faint black 10% fill. The icon
   image and the label text stay transparent (no box of their own). */
.lb .equip-slot:not(.has-item) .equip-slot__pic { background:rgba(0,0,0,.10); }
.lb .equip-slot__pic img { width:100%; height:100%; object-fit:cover; display:block; }
.lb .equip-slot__icon { color:var(--ink3); opacity:.88; }
.lb .equip-slot.has-item .equip-slot__icon { opacity:1; color:var(--gold); }
/* PNG slot icons ship WHITE — brightness(0) turns them solid dark on every browser
   and device (CSS filter is universally supported; no mask/SVG-color quirks). Opacity
   softens them to match the muted ink look. Override the __pic img (cover) rule. */
.lb .equip-slot__pic img.equip-slot__icon--png {
  width:72%; height:72%; object-fit:contain;
  filter:brightness(0); opacity:.6;
}
.lb .equip-slot:hover .equip-slot__icon--png { opacity:.8; }
.lb .equip-slot.has-item .equip-slot__icon--png { opacity:.92; }
.lb .equip-slot__lbl { line-height:1.1; text-align:center; max-width:100%; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; color:var(--ink2); font-size:10px; font-weight:700; }
.lb .equip-slot--head { grid-column:2; grid-row:1; }
.lb .equip-slot--neck { grid-column:2; grid-row:2; }
.lb .equip-slot--back { grid-column:1; grid-row:2; }
.lb .equip-slot--handL { grid-column:1; grid-row:3; }
.lb .equip-slot--body { grid-column:2; grid-row:3; }
.lb .equip-slot--handR { grid-column:3; grid-row:3; }
.lb .equip-slot--ringL { grid-column:1; grid-row:4; }
.lb .equip-slot--ringR { grid-column:3; grid-row:4; }
.lb .equip-slot--feet { grid-column:2; grid-row:5; }
.lb .equip-doll-extra { display:flex; flex-wrap:wrap; gap:10px; justify-content:center; margin:0 0 10px; }
.lb .equip-doll-extra .equip-slot { width:72px; }
.lb .equip-doll__add { display:block; margin:0 auto; }

/* inventory grid — tap a tile to open the shared item card */
.lb .itemgrid { display:grid; grid-template-columns:repeat(auto-fill,minmax(104px,1fr)); gap:10px; margin:4px 0 12px; }
.lb .itemtile { display:flex; flex-direction:column; align-items:stretch; gap:6px; margin:0; padding:8px; text-align:start;
  background:var(--paper); border:2px solid var(--edge); color:var(--ink); cursor:pointer;
  font:inherit; font-weight:700; -webkit-tap-highlight-color:transparent; }
.lb .itemtile:hover { border-color:var(--gold2); }
.lb .itemtile__pic { position:relative; display:block; width:100%; aspect-ratio:1; background:var(--sheet2); border:1.5px solid var(--edge);
  overflow:hidden; }
/* INK ICONS (inkIcons.js): dark line art on paper, for an item with no picture and in the equipment slots. */
.lb .ink-ico { display:block; color:var(--ink); }
.lb .itemtile__ink { width:58%; height:58%; margin:21%; color:var(--ink2); }
/* On the hero: a tiny mark in the corner, the only difference between cards (Maayan). Paper shows none. */
.lb .itemtile__worn { position:absolute; top:4px; inset-inline-end:4px; width:16px; height:16px; padding:2px; box-sizing:border-box;
  border-radius:50%; background:var(--paper); border:1px solid var(--ink2); }
.lb .itemtile__worn .ink-ico { width:100%; height:100%; color:var(--ink); stroke-width:2.4; }
.gmpdf .itemtile__worn { display:none !important; }
.lb .equip-slot__pic svg.equip-slot__icon { width:62%; height:62%; color:var(--ink2); opacity:1; }
.lb .equip-slot.has-item .equip-slot__pic svg.equip-slot__icon { color:var(--ink); }
.lb .itemtile__pic img { width:100%; height:100%; object-fit:cover; display:block; }
.lb .itemtile__glyph { display:flex; align-items:center; justify-content:center; width:100%; height:100%; font-size:32px; color:var(--gold2); }
.lb .itemtile__pic .img-spin--sm { display:block; margin:auto; position:relative; top:50%; transform:translateY(-50%); }
.lb .itemtile__name { font-size:14px; line-height:1.2; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
.lb .itemtile__chip { font-size:12px; font-weight:600; color:var(--gold); line-height:1.2; }
.lb .fld-sub { display:block; margin-top:2px; font-weight:600; color:var(--ink3); }
.lb .abil-gear { display:block; color:var(--gold); font-weight:700; font-size:11px; }
.lb .ab--gear { opacity:.92; }
.lb .ab--gear .mini { color:var(--ink3); font-weight:600; }
.lb button.ab.ab--spell { width:100%; background:none; border:0; border-bottom:1.5px dotted var(--edge); cursor:pointer; text-align:inherit; font:inherit; color:inherit; padding:6px 2px; }
.lb .ab__main { flex:1; min-width:0; text-align:start; }
.lb .ab__sub { display:block; margin-top:2px; color:var(--ink3); font-weight:600; font-size:13px; }
.lb .ab--res { gap:6px; flex-wrap:wrap; }
/* Resistance rollup table: Type | Base(edit) | +gear | Total | menu */
.lb .restab { display:flex; flex-direction:column; gap:4px; margin-bottom:8px; }
.lb .restab__head, .lb .restab__row { display:grid; grid-template-columns: minmax(0,1fr) 3.4rem minmax(0,1fr) 2.6rem 1.6rem; align-items:center; gap:8px; }
.lb .restab__head { padding:0 2px 4px; color:var(--ink3); font-weight:700; }
.lb .restab__row { border-bottom:1.5px dotted var(--edge); padding:3px 2px; }
.lb .restab__type--ro { font-weight:700; }
.lb .restab__base { width:100%; box-sizing:border-box; font:inherit; text-align:center; padding:2px 4px; border:1.5px solid var(--edge); border-radius:6px; background:var(--sheet2); }
.lb .restab__gear { color:var(--ink3); font-weight:600; }
.lb .restab__total { text-align:end; color:var(--gold); font-weight:800; }
.lb .sel--sm { font-size:14px; padding:2px 6px; max-width:9rem; }
.lb .magic-schools { margin:0 0 8px; color:var(--ink3); }
.lb .spellcard__grid { display:grid; grid-template-columns:1fr 1fr; gap:10px 14px; margin:0 0 12px; }
.lb .spellcard__fld { display:flex; flex-direction:column; gap:4px; min-width:0; }
.lb .spellcard__fld .ed { display:block; font-size:18px; font-weight:700; }
.lb .spell-warn, .lb .spell-resist-hint { color:var(--ink3); margin:0 0 10px; }
.lb .spellcard__meta label { display:flex; flex-direction:column; gap:4px; min-width:6rem; flex:1; }
.lb .magic-school-picks { display:flex; flex-direction:column; gap:10px; margin:12px 0 16px; }
.lb .magic-school-pick { display:flex; align-items:center; gap:10px; font-size:16px; cursor:pointer; }
.lb .magic-school-actions { display:flex; gap:10px; justify-content:flex-end; flex-wrap:wrap; }

/* item card — effect rows (type + value); category decides equip vs uses */
.lb .item-equip { display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px 16px; margin:0 0 12px; }
.lb .item-worn { display:flex; align-items:center; gap:8px; margin:0; font-weight:700; cursor:pointer; min-height:38px; }
.lb .item-worn input { width:18px; height:18px; accent-color:var(--gold); }
.lb .item-equip__slot { display:flex; flex-direction:column; gap:4px; min-width:9rem; flex:1 1 9rem; }
.lb .item-equip__slot .sel:disabled { opacity:.55; }
.lb .item-fxlist__hint { margin:0 0 10px; }
.lb .item-fxlist { margin-top:12px; padding-top:8px; border-top:2px solid var(--edge); }
.lb .item-fxlist > .gm-btn { margin:8px 0 4px; }
.lb .item-fx__empty { margin:6px 0 10px; }
.lb .item-fx { display:grid; grid-template-columns:minmax(8rem,1.1fr) minmax(5rem,1.6fr) auto; gap:8px; align-items:center;
  padding:8px 0; border-bottom:1px dotted var(--edge); }
.lb .item-fx:last-child { border-bottom:0; }
.lb .item-fx__fields { display:flex; gap:6px; align-items:center; min-width:0; }
.lb .item-fx__pick { color:var(--ink3); }
.lb .item-fx__input { width:100%; min-width:0; box-sizing:border-box; font:inherit; font-size:16px; font-weight:700;
  padding:6px 8px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); }
.lb .item-fx__input--sm { max-width:4.5rem; }
.lb .item-fx__input:focus { outline:none; border-color:var(--gold2); }
.lb .item-fx .sel { width:100%; min-width:0; }
.lb .itemcard > .gm-btn--block { margin-top:14px; }
@container pane (max-width:520px){
  .lb .item-fx { grid-template-columns:1fr auto; }
  .lb .item-fx__kind { grid-column:1; }
  .lb .item-fx__fields { grid-column:1 / -1; }
}
.lb.party .item-fxlist .gm-btn.dm-only,
.lb.party #itemAddFx { display:none !important; }

/* ---------------- overlay ---------------- */
/* ---- Gate: two dark-brown doors that swing open onto the app ----
   Sits above the app (z 65) but BELOW modals (z 70), so a modal always wins. */
.lb-gate { position:fixed; inset:0; z-index:65; overflow:hidden; }
/* THE TABLE PAGE'S SCREEN STATES (docs/growlmaster-screen-states.md): the root's data-screen-state, the one state, set
   first by the server (AppShell.astro) and then only by app.js goToState. Everything the gate shows is decided here,
   by it; each move is an animation that starts with its state, and its end is the event that moves on (app.js). */
/* the doors' pace, on the root: the gate and the table behind it (#lb) move together by it */
:root { --grate-lift:1.25s; --doors:1.6s; --doors-ease:cubic-bezier(.45,.05,.55,.95); --iron-ease:cubic-bezier(.42,0,.58,1); }
.lb-gate { display:none; }
:root[data-screen-state^="gate_"] .lb-gate { display:block; }
/* gate_closed_gorak_welcomes_first_visit: doors shut, the table's card, Gorak talks: no Enter */
:root[data-screen-state="gate_closed_gorak_welcomes_first_visit"] .lb-gate__enter { display:none; }
/* gate_closed_*: behind the shut doors the table waits a little smaller (0.96); while it is scaled the page never
   scrolls sideways (a transform makes #lb the box its fixed children sit in: 2026-10-24) */
:root[data-screen-state^="gate_closed_"] .lb { transform:scale3d(.96,.96,1); transform-origin:50% 50%; overflow-x:clip; }
/* gate_doors_opening: the iron rises as the doors part, the black lifts and the table grows to 1, one movement: Enter to
   the table is the doors' own time, nothing waited before it */
:root[data-screen-state="gate_doors_opening"] .lb-gate__grate { animation:gmIronUp var(--grate-lift) var(--iron-ease) both; }
:root[data-screen-state="gate_doors_opening"] .lb-gate__half--l { animation:gmDoorLeftOut var(--doors) var(--doors-ease) both; }
:root[data-screen-state="gate_doors_opening"] .lb-gate__half--r { animation:gmDoorRightOut var(--doors) var(--doors-ease) both; }
:root[data-screen-state="gate_doors_opening"] .lb-gate__shade { animation:gmBlackOut var(--doors) var(--doors-ease) both; }
:root[data-screen-state="gate_doors_opening"] .lb-gate__inner { opacity:0; pointer-events:none; }
:root[data-screen-state="gate_doors_opening"] .lb { transform-origin:50% 50%; overflow-x:clip; animation:gmTableIn var(--doors) var(--doors-ease) both; }
/* gate_doors_closing (Exit): the doors shut, the table going back into the black and the iron coming down with them */
:root[data-screen-state="gate_doors_closing"] .lb-gate__inner { display:none; }
:root[data-screen-state="gate_doors_closing"] .lb-gate__half--l { animation:gmDoorLeftOut var(--doors) var(--doors-ease) reverse both; }
:root[data-screen-state="gate_doors_closing"] .lb-gate__half--r { animation:gmDoorRightOut var(--doors) var(--doors-ease) reverse both; }
:root[data-screen-state="gate_doors_closing"] .lb-gate__shade { animation:gmBlackOut var(--doors) var(--doors-ease) reverse both; }
:root[data-screen-state="gate_doors_closing"] .lb-gate__grate { animation:gmIronUp var(--doors) var(--iron-ease) reverse both; }
:root[data-screen-state="gate_doors_closing"] .lb { transform-origin:50% 50%; overflow-x:clip; animation:gmTableIn var(--doors) var(--doors-ease) reverse both; }
@keyframes gmIronUp { from { transform:translate3d(0,0,0); } to { transform:translate3d(0,-104%,0); } }
@keyframes gmDoorLeftOut { from { transform:translate3d(0,0,0); } to { transform:translate3d(-100%,0,0); } }
@keyframes gmDoorRightOut { from { transform:translate3d(0,0,0); } to { transform:translate3d(100%,0,0); } }
@keyframes gmBlackOut { from { opacity:1; } to { opacity:0; } }
@keyframes gmTableIn { from { transform:scale3d(.96,.96,1); } to { transform:scale3d(1,1,1); } }
@media (prefers-reduced-motion:reduce){
  :root { --grate-lift:.35s; --doors:.35s; }
  /* the black only, no scale; Jim stops every animation (AppStyles.astro), the black's fade is the one movement kept: in
     as the doors open, and out before they shut (they wait for it), so leaving is seen too */
  :root[data-screen-state^="gate_"] .lb { transform:none; animation:none; }
  .jim-app .lb-gate .lb-gate__shade { animation-duration:.35s !important; }
  :root[data-screen-state="gate_doors_closing"] .lb-gate__half { animation-delay:.35s; }
}
.lb-gate__half {
  position:absolute; top:0; bottom:0; width:calc(50% - 5px);   /* the 10px crack between them */
  background:linear-gradient(var(--ink),#2c2011);
  /* Heavy doors: slow and steady the whole way, a small ease at each end.
     A snappy ease-out here reads as "sticks then flies open" — not what we want. */
  /* translate3d keeps the doors on the GPU so the slide stays smooth on mobile */
  transform:translate3d(0,0,0);
  will-change:transform;
}
/* Each door is anchored by the edge that meets the other one, so the join down the
   middle is always the art's own edge: cover crops the OUTER side and the ceiling
   instead, wherever the window happens to be shaped. */
.lb-gate__half--l { left:0;  box-shadow:inset -6px 0 18px rgba(0,0,0,.45), 4px 0 22px rgba(0,0,0,.6);
  background-image:url("/growlmaster/gate-left.webp");
  background-size:cover; background-position:right center; background-repeat:no-repeat; }
.lb-gate__half--r { right:0; box-shadow:inset  6px 0 18px rgba(0,0,0,.45), -4px 0 22px rgba(0,0,0,.6);
  background-image:url("/growlmaster/gate-right.webp");
  background-size:cover; background-position:left center; background-repeat:no-repeat; }
.lb-gate__inner {
  position:absolute; inset:0; display:flex; flex-direction:column;
  align-items:center; justify-content:center; gap:30px; padding:24px;
  transition:opacity .38s ease; will-change:opacity;
}
/* The name rides above the handles rather than across them. Expressed against the window
   so it lands where it was drawn on a tall screen and cannot climb out of a short one. */
.lb-gate__logo { width:min(62vw,440px); height:auto; filter:drop-shadow(0 6px 18px rgba(0,0,0,.5));
  transform:translateY(calc(clamp(-414px, -36vh, 0px) + 100px)); }
.lb-gate__enter {
  font:inherit; font-weight:800; font-size:19px; letter-spacing:.02em;
  color:var(--sheet); background:var(--gold); border:2px solid var(--gold-dk);
  padding:14px 30px; cursor:pointer; border-radius:2px;
  box-shadow:0 4px 0 var(--gold-dk); transition:transform .1s ease, box-shadow .1s ease;
}
.lb-gate__enter:hover { background:var(--gold-dk); }
.lb-gate__enter:focus, .lb-gate__enter:focus-visible { outline:none; }   /* no ring round it at the gate (Maayan) */
.lb-gate__enter:active { transform:translateY(3px); box-shadow:0 1px 0 var(--gold-dk); }
/* The iron over the doors. Spikes stay on the floor line and the lattice fills upward,
   so a tall window gets more grid rather than stretched bars. It sits above the doors and
   below the writing, which is why it needs no z-index of its own: DOM order is enough. */
.lb-gate__grate {
  position:absolute; inset:0; pointer-events:none;
  background-image:url("/growlmaster/gate-grate.webp");
  background-size:cover; background-position:center bottom; background-repeat:no-repeat;
  filter:drop-shadow(0 8px 14px rgba(0,0,0,.55));
  /* Lifts a touch quicker than the doors, the way a portcullis clears before they open (--grate-lift). */
  transform:translate3d(0,0,0);
  will-change:transform;
}
/* The black the table fades in from as the doors open, and into as they shut (Maayan, 2026-10-26): the gate's own
   layer under the doors, with their duration, easing and delay (the states above). */
.lb-gate__shade { position:absolute; inset:0; background:#000; opacity:1; pointer-events:none; will-change:opacity; }
/* The table you are entering, above Enter: its name, its players' faces under it, in a box. */
.lb-gate__table { display:flex; flex-direction:column; align-items:center; gap:8px; padding:14px 26px; min-width:min(320px,80vw); box-sizing:border-box;
  background:rgba(250,245,232,.94); border:2px solid var(--edge); box-shadow:0 10px 30px rgba(0,0,0,.45); color:var(--ink); text-align:center; }
.lb-gate__tname { font-family:'Frank Ruhl Libre',serif; font-weight:900; font-size:26px; line-height:1.15; }
.gm-players--gate { font-size:14px; }
.lb-gate__tinfo { display:flex; flex-direction:column; align-items:center; gap:8px; }
.lb-gate__tinfo:empty { display:none; }
.lb-gate__table .lb-gate__enter { margin-top:8px; }
/* At the gate Gorak (retroConversation.css) stands where the table's box is: while he speaks on a phone, the box rises
   above him (Maayan, 2026-10-20). */
@media (max-width:680px){ .lb-gate:has(.retro) .lb-gate__table { translate:0 -20vh; transition:translate .4s ease; } }

/* Gorak's pointing hand — a tour hint that bobs like a tap. Above the app and
   panel, below modals. Never eats clicks. */
/* The hand is a pair: this box is PLACED (left/top rewritten every frame as it
   follows its target), and the picture inside it is ANIMATED. They were one
   element, which meant the per-frame position write and the bob were fighting
   over the same node — and on a phone the bob is what lost. */
.gm-hand { position:fixed; z-index:68; pointer-events:none; }
.gm-hand__img { display:block; width:100%; height:auto;
  filter:drop-shadow(0 5px 9px rgba(0,0,0,.45));
  animation:gmHandBob 1s ease-in-out infinite; }
@keyframes gmHandBob { 0%,100%{ transform:translateY(0); } 50%{ transform:translateY(7px); } }
/* Turned over to reach down at something on the bottom edge. It keeps bobbing, in its
   own turned frame. */
.gm-hand.gm-hand--down .gm-hand__img { animation:gmHandBobDown 1s ease-in-out infinite; }
@keyframes gmHandBobDown {
  0%,100%{ transform:rotate(-90deg) translateX(0); }
  50%    { transform:rotate(-90deg) translateX(-7px); }
}
/* Showing a swipe rather than a tap: the finger travels the way the row must be pushed,
   pauses, and starts again. */
.gm-hand.gm-hand--swipe .gm-hand__img { animation:gmHandSwipe 1.9s ease-in-out infinite; }
@keyframes gmHandSwipe {
  0%, 8%    { transform:translateX(46px); opacity:1; }
  52%       { transform:translateX(-46px); opacity:1; }
  62%       { transform:translateX(-46px); opacity:0; }
  70%, 100% { transform:translateX(46px); opacity:0; }
}
@media (prefers-reduced-motion:reduce){ .gm-hand.gm-hand--swipe .gm-hand__img { animation:none; } }
@media (prefers-reduced-motion:reduce){ .gm-hand__img { animation:none; } }

.lb-ovl { position:fixed; inset:0; background:rgba(58,44,23,.42); display:flex; align-items:center; justify-content:center; z-index:70; padding:12px; box-sizing:border-box; }
.lb-box { background:var(--sheet); border:2px solid var(--ink); padding:18px 22px; min-width:0; width:100%; max-width:640px; max-height:84vh; overflow:auto;
  font-family:'Grandstander',system-ui,sans-serif; color:var(--ink); font-size:16px; box-sizing:border-box; }
.lb-box .boxhead { display:flex; align-items:center; gap:10px; margin-bottom:14px; }
.lb-box .boxhead h2 { font-family:'Frank Ruhl Libre',serif; font-size:24px; margin:0; }
.lb-box .boxhead--sep { padding-bottom:12px; margin-bottom:4px; border-bottom:2px solid var(--edge); }
/* A WINDOW'S ✕ (Maayan, 2026-10-24: "on phones, the close × in GrowlMaster windows is too tiny"): every window's head
   ends in its close (studio.js backHeadOf, and the windows that draw their own head the same way), one rule for them
   all, at every width: a real thumb's target (48px, past the 44px floor) and a larger, bolder ✕. */
.boxhead > .gm-btn.gm-btn--icon:last-child { flex:none; width:48px; height:48px; min-width:48px; min-height:48px; margin:-6px -8px -6px 0; font-size:26px; font-weight:800; line-height:1; -webkit-text-stroke:1.2px currentColor; }
[dir="rtl"] .boxhead > .gm-btn.gm-btn--icon:last-child { margin:-6px 0 -6px -8px; }
.lb-box .spacer { flex:1; }
.lb-box .journal { margin:0; padding-inline-start:22px; } .lb-box .journal li { padding:5px 0; }
.lb-box .chatin { display:flex; gap:8px; margin-top:10px; }
.lb-box .chatin input { flex:1; min-width:0; font:inherit; font-size:16px; padding:8px 10px; border:2px solid var(--edge); background:var(--paper); }
/* new-hero + confirm modals */
.lb-box .nh-field { display:block; margin:2px 0 16px; }
.lb-box .nh-field .l { display:block; font-size:14px; color:var(--ink3); font-weight:700; margin-bottom:6px; }
.lb-box .nh-input { width:100%; box-sizing:border-box; font:inherit; font-size:18px; padding:10px 12px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); }
.lb-box .nh-input:focus { outline:none; border-color:var(--gold2); }
.lb-box .nh-actions { display:flex; gap:10px; }
.lb-box .nh-actions .gm-btn { flex:1; justify-content:center; margin-top:0; }

/* empty states + battle toolbar */
.lb .empty { margin:auto; text-align:center; color:var(--ink2); padding:12px; }
.lb .empty .ebig { font-size:64px; color:var(--ink3); }
.lb .empty h2 { font-size:26px; margin:6px 0 4px; }
/* The empty-state CTA is a direct child of .empty, which was missing from the
   `> .gm-btn { margin-top }` list above — so it sat flush against the subtitle.
   A standalone centred CTA wants more air than the 10px a card-appended button
   gets. Applies to every empty state (characters, map, shop). */
.lb .empty > .gm-btn { margin-top:16px; }
.lb .btoolbar { display:flex; align-items:center; gap:10px; flex-wrap:wrap; }

/* main header — 3 columns so the brand sits centered between the AI toggle
   (left) and the account/settings actions (right). */
/* The header strip (.lb-top) is GrowlMaster's one header, the app's and its public pages': its rules are in
   src/styles/gm-header.css, which components/growlmaster/GmHeader.astro brings wherever it is mounted. */
/* The assistant's button itself (face, name, "AI assistant") is the shared one: assistant.css. */
.lb.party .dm-only { display:none !important; }
/* AI drawer is DM-only — close + hide in party view */
.lb.party #aiToggle,
.lb.party #libToggle,
.lb.party .jim-ai { display:none !important; }
/* Party can look, not edit DM sheets / maps / adventure */
.lb.party .mapstack [contenteditable],
.lb.party .moncard [contenteditable],
.lb.party .moncard .ed,
.lb.party .shop2 [contenteditable],
.lb.party .shop2 .ed,
.lb.party .shopcard [contenteditable],
.lb.party .shopcard .ed,
.lb.party .shopcard select,
.lb.party .shopcard input,
.lb.party .item-worn,
.lb.party .item-equip,
.lb.party .item-fx { pointer-events:none; caret-color:transparent; }
.lb.party .itemtile { cursor:pointer; }
.lb.party #itemUse { pointer-events:auto; }
.lb.party #mapcv { pointer-events:none; cursor:default; }
.lb.party .shop-banner, .lb.party .shop-cover { pointer-events:none; }

/* AI edit flash: tint a changed field yellow three times when its tab is shown.
   Inset box-shadow layers over the field's own background but stays behind text. */
@keyframes gmFlash { 0%,100%{ box-shadow:inset 0 0 0 9999px rgba(255,208,54,0); } 50%{ box-shadow:inset 0 0 0 9999px rgba(255,208,54,.62); } }
.lb .gm-flash { animation:gmFlash .34s ease-in-out 3; border-radius:2px; }
@media (prefers-reduced-motion:reduce){ .lb .gm-flash { animation:gmFlash .6s ease-in-out 1; } }

/* The assistant's panel, and where it sits and how it pushes or overlays, are the shared
   assistant's (assistant.css, .jim-ai-host): the same for GrowlMaster and Jim Docs.
   This is only the row's size in the table, and its row for when there is no panel. */
.lb-body { flex:1; min-height:0; display:flex; position:relative; }
/* login modal */
:is(.lb-box, .gm-lobby__auth) .ldiv { display:flex; align-items:center; gap:10px; margin:14px 0; color:var(--ink3); font-size:14px; }
:is(.lb-box, .gm-lobby__auth) .ldiv::before,:is(.lb-box, .gm-lobby__auth) .ldiv::after { content:""; flex:1; height:1.5px; background:var(--edge); }
:is(.lb-box, .gm-lobby__auth) .lform .lfield { display:block; margin-bottom:12px; }
:is(.lb-box, .gm-lobby__auth) .lform .lfield span { display:block; margin-bottom:5px; font-size:14px; font-weight:700; color:var(--ink2); }
:is(.lb-box, .gm-lobby__auth) .lform .lfield input { width:100%; font:inherit; font-size:16px; padding:10px 12px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); }
:is(.lb-box, .gm-lobby__auth) .lform .lfield input:focus { outline:none; border-color:var(--gold); }
:is(.lb-box, .gm-lobby__auth) .lerr { margin:2px 0 10px; color:var(--red); font-size:14px; font-weight:700; }
:is(.lb-box, .gm-lobby__auth) .lerr.is-info { color:var(--green); }   /* a link sent (studio.js openLogin): news, not an error */
:is(.lb-box, .gm-lobby__auth) .lforgot { display:inline-block; margin-top:10px; font-size:14px; }
:is(.lb-box, .gm-lobby__auth) .lerr--google { margin:8px 0 0; }
:is(.lb-box, .gm-lobby__auth) .ltoggle { margin:14px 0 0; text-align:center; font-size:15px; color:var(--ink2); }
:is(.lb-box, .gm-lobby__auth) .link { background:none; border:0; padding:0; font:inherit; font-weight:800; color:var(--gold); cursor:pointer; text-decoration:underline; }
:is(.lb-box, .gm-tdrop) .setrow { display:grid; grid-template-columns:1fr auto; gap:4px 14px; align-items:start; padding:14px 0; border-bottom:1.5px solid var(--edge); }
:is(.lb-box, .gm-tdrop) .setrow .setcopy { min-width:0; }
/* The default-quality picker sits under its copy at full width, and only while
   auto-images is on; [hidden] must beat .setrow's display:grid. */
:is(.lb-box, .gm-tdrop) .setrow[hidden] { display:none; }
:is(.lb-box, .gm-tdrop) .setrow--tier { grid-template-columns:1fr; }
/* Gorak's credit cap: a number field drawn like the board's own (item-fx__input), button height. */
:is(.lb-box, .gm-tdrop) .setrow .setnum { width:6rem; max-width:6rem; min-height:40px; box-sizing:border-box; font:inherit; font-size:16px; font-weight:700;
  padding:6px 8px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); }
:is(.lb-box, .gm-tdrop) .setrow .setnum:focus { outline:none; border-color:var(--gold2); }
:is(.lb-box, .gm-tdrop) .setrow--tier .imgmodal__tiers { margin:6px 0 0; }
:is(.lb-box, .gm-tdrop) .setrow .setcopy b { display:block; font-size:16px; line-height:1.25; }
:is(.lb-box, .gm-tdrop) .setrow .setcopy .mini { margin-top:4px; line-height:1.4; overflow-wrap:break-word; hyphens:manual; }
:is(.lb-box, .gm-tdrop) .setrow > .gm-btn { grid-column:2; grid-row:1 / span 2; align-self:center; min-width:4.5em; }
:is(.lb-box, .gm-tdrop) .setrow > .gm-switch { grid-column:2; grid-row:1 / span 2; align-self:center; }
.lb-box .setfoot { margin:14px 0; line-height:1.45; }

/* Auto translation modal — sticky CTA row, scrollable string list */
.lb-box.gm-txbox {
  display:flex; flex-direction:column; padding:0; overflow:hidden;
  max-height:min(84vh,720px); width:min(560px,100%);
}
.gm-txbox .gm-tx__head { flex:0 0 auto; padding:18px 22px 8px; }
.gm-txbox .gm-tx__hint { margin-top:6px; line-height:1.4; }
.gm-txbox .gm-tx__body { flex:1 1 auto; min-height:0; overflow:auto; padding:4px 22px 12px; }
.gm-txbox .gm-tx__empty { padding:18px 0; text-align:center; }
.gm-txbox .gm-tx__row { padding:12px 0; border-bottom:1.5px solid var(--edge); }
.gm-txbox .gm-tx__row:last-child { border-bottom:0; }
.gm-txbox .gm-tx__label { font-size:13px; font-weight:800; color:var(--ink3); letter-spacing:.02em; margin-bottom:6px; }
.gm-txbox .gm-tx__edit { position:relative; margin-bottom:8px; }
.gm-txbox .gm-tx__ta {
  display:block; width:100%; box-sizing:border-box; resize:vertical; min-height:2.8em;
  font:inherit; font-size:16px; line-height:1.4; color:var(--ink);
  background:var(--sheet2); border:2px solid var(--edge); padding:8px 10px;
}
.gm-txbox .gm-tx__ta:focus { outline:none; border-color:var(--gold2); }
.gm-txbox .gm-tx__spin {
  position:absolute; top:50%; left:50%; z-index:1;
  width:16px; height:16px; margin:-8px 0 0 -8px; box-sizing:border-box;
  border:2px solid rgba(179,45,31,.2); border-top-color:var(--gold);
  border-radius:50%; animation:gmSpin .75s linear infinite;
}
.gm-txbox .gm-tx__row.is-busy .gm-tx__ta { opacity:.4; pointer-events:none; }
.gm-txbox .gm-tx__acts { display:flex; gap:8px; flex-wrap:wrap; }
.gm-txbox .gm-tx__acts .gm-btn { margin-top:0; flex:0 0 auto; }
.gm-txbox .gm-tx__cta {
  flex:0 0 auto; display:flex; gap:10px; flex-wrap:wrap; align-items:center;
  padding:12px 22px calc(12px + env(safe-area-inset-bottom,0px));
  border-top:2px solid var(--edge); background:var(--sheet);
  position:sticky; bottom:0;
}
.gm-txbox .gm-tx__cta .gm-btn { margin-top:0; flex:1 1 auto; justify-content:center; min-width:8em; }

/* ========== responsive ========== */

/* ============================ IN-PANE RESPONSIVE ============================
   These react to the PANE's width via @container, so resizing with the divider
   reflows the sheet exactly like a narrower screen would. */

/* A4 PAGES (Maayan): a hero's and a foe's sheets are A4 pages, here and on the public maker pages, the same pages
   their PDF prints. The page keeps A4's shape at whatever width it has, and grows when it holds more: an empty
   float as tall as A4 at the page's width holds it open (aspect-ratio would cap a page that holds more). A phone
   flows freely; its print is still the pages. */
.lb .page.page--a4::before { content:""; float:left; width:0; padding-top:141.42%; }
.lb .page.page--a4::after { content:""; display:table; clear:both; }
@media (max-width:680px){ .lb .page.page--a4::before { display:none; } }

/* stack two sheet pages when the pane can't hold them side by side */
@container pane (max-width:1180px){
  .lb .spread { flex-direction:column; align-items:center; }
  .lb .page { width:min(600px,100%); flex-basis:auto; }
}

/* narrow pane: the portrait shorter, the battle stacked */
@container pane (max-width:900px){
  .lb .portrait { min-height:180px; }
  .lb .battle { flex-direction:column; }
  .lb .side { flex:none; max-width:none; width:100%; }
}

/* very narrow pane: regroup related fields, smaller type */
@container pane (max-width:680px){
  .lb-tabsbar { padding:10px 12px 0; gap:6px; }
  .lb .lb-tab { padding:8px 12px 9px; font-size:15px; }
  .lb-stage { padding:12px 10px 16px; }
  .lb .page { width:100%; padding:14px 12px 16px; }

  /* coins stay 3-across (small equal fields) */
  .lb .grid-coins { grid-template-columns:1fr 1fr 1fr; }

  .lb .fld .l { font-size:13px; }
  .lb .fld .v { font-size:22px; }
  .lb .fld.name .v { font-size:26px; }
  .lb .abil .vl { font-size:30px; }
  .lb .card { padding:10px 10px; }
  .lb .card.big { min-height:0; }
}

/* tiny pane: everything single-column */
@container pane (max-width:380px){
  .lb .grid-coins { grid-template-columns:1fr; }
}

/* ============================ VIEWPORT CHROME ============================
   Header, full-screen modal, and the edge rails span the whole window, so they
   stay on @media (viewport), not pane width. */
@media (max-width:680px){
  /* Settings collapses to icon-only on phones (same button, label hidden). */

  /* modal: near full-bleed, stacked setting rows */
  .lb-ovl { padding:10px; align-items:flex-end; }
  .lb-box { padding:16px 16px calc(16px + env(safe-area-inset-bottom,0px)); max-width:none; max-height:min(92dvh,100%);
    border-radius:0; width:100%; }
  .lb-box .boxhead { margin-bottom:8px; }
  .lb-box .boxhead h2 { font-size:22px; }
  :is(.lb-box, .gm-tdrop) .setrow { grid-template-columns:1fr; gap:10px; padding:14px 0; }
  :is(.lb-box, .gm-tdrop) .setrow > .gm-btn { grid-column:1; grid-row:auto; justify-self:start; min-width:5.5em; }
  :is(.lb-box, .gm-tdrop) .setrow > .gm-switch { grid-column:1; grid-row:auto; justify-self:start; }
  .lb-box .setfoot { margin:12px 0 10px; }
  .lb-box.gm-txbox { max-height:min(92dvh,100%); }
  .gm-txbox .gm-tx__head { padding:16px 16px 6px; }
  .gm-txbox .gm-tx__body { padding:4px 16px 10px; }
  .gm-txbox .gm-tx__cta { padding:10px 16px calc(10px + env(safe-area-inset-bottom,0px)); }
}
@media (max-width:380px){
  .lb-rail button { padding:12px 7px; font-size:15px; }
}
/* ---- THE HEADER ON A PHONE ----
   Measured before touching: the bar is a three-column grid, `1fr auto 1fr`, and
   it does not fit on any phone.

     320px: "Guest" and "Settings" are BOTH off the right edge and the page
            scrolls sideways by 59px to compensate. Settings is unreachable.
     360px: "Settings" is off the edge. 19px of sideways scroll.
     390px: fits, but the LOGO is 0px wide.
     414px: logo 22px. 430px: logo 38px. Its natural size is 320x106.

   So the brand has been silently collapsing to nothing on every common phone,
   because the centre column is `auto` and there is nothing left to give it.
   Nothing looked broken, which is why it survived: a column that collapses to
   zero renders no error.

   Two changes, both taking away rather than adding. The centre column goes,
   deliberately rather than by collapse, which also returns its two 10px gaps.
   And Gorak keeps his face but loses his caption, because "Gorak / AI assistant"
   is 52px of the 122px pill and it is what pushed Settings off a 360px screen.
   The button keeps its aria-label, so nothing is lost to a screen reader, and
   his name is on the panel the moment it opens.

   After: 12 + 48 + 10 + 225 + 12 = 307px at 320, and nothing scrolls sideways. */
@media (max-width:430px){
  .lb-top__left .jim-ai-toggle { padding-inline:4px; }
  .lb-top__left .jim-ai-id { display:none; }
}

@media print {
  .lb-rail,.lb-divider,.lb-tabsbar .add,.gm-btn,.lb-top { display:none !important; }
  .lb{ background:#fff; height:auto; overflow:visible; }
}

/* RTL: full layout flip via <html dir="rtl">.
   Decorative icons stay as drawn; back-chevron copy uses → in Hebrew strings.
   (The rail's accent stripe used to be mirrored here. It is gone in both
   directions now, so there is nothing left to flip.) */

/* Don't mirror the logo mark */
html[dir="rtl"] .lb .brand img { transform: none; }

/* (the image lightbox's look: imageModal.css, shared with GrowlMaster v2) */

/* ---------------- calculated-field explainer ----------------
   A derived number (.calc gold values, .abil .mod) is now tappable and opens a
   modal showing the arithmetic. The dotted underline is the affordance: it says
   "there's something under this" without turning the sheet into a wall of buttons. */
.lb [data-calc] { cursor:help; -webkit-tap-highlight-color:transparent;
  text-decoration:underline; text-decoration-style:dotted; text-underline-offset:3px;
  text-decoration-color:var(--gold2); text-decoration-thickness:1px; }
.lb [data-calc]:hover { text-decoration-color:var(--gold); }
.lb [data-calc]:focus-visible { outline:2px solid var(--gold2); outline-offset:2px; text-decoration:none; }
.lb .abil .mod[data-calc] { display:block; }

.lb-box .gm-calc__why { margin:0 0 14px; color:var(--ink2); line-height:1.5; }
.lb-box .gm-calc { display:flex; flex-direction:column; }
.lb-box .gm-calc__row { display:flex; align-items:baseline; gap:12px; padding:9px 10px; }
/* Zebra bands, same language as the bestiary tables (SoT: /growlmaster/sot). */
.lb-box .gm-calc__row:nth-child(even) { background:var(--stripe); }
.lb-box .gm-calc__t { flex:1; min-width:0; }
.lb-box .gm-calc__s { display:block; font-size:13px; color:var(--ink3); font-weight:600; margin-top:1px; }
.lb-box .gm-calc__n { flex:0 0 auto; font-weight:800; color:var(--gold);
  font-variant-numeric:tabular-nums; min-width:3.2em; text-align:end; }
.lb-box .gm-calc__row--total { border-top:2px solid var(--edge); background:none; margin-top:2px; }
.lb-box .gm-calc__row--total .gm-calc__t { font-weight:800; color:var(--ink); }
.lb-box .gm-calc__row--total .gm-calc__n { font-size:19px; color:var(--ink); }

/* Signed numbers are Latin-ordered in BOTH languages. Without isolation, Hebrew's
   RTL run moves the sign to the far side and "+2" reads as "2+" — wrong, and worst
   of all in the explainer whose whole job is making the arithmetic legible. */
.lb .calc, .lb .abil .mod, .lb .abil-gear, .lb-box .gm-calc__n {
  direction:ltr; unicode-bidi:isolate;
}

/* ---------------- character-sheet footnote ----------------
   Quiet small print at the foot of the sheet: what actually feeds the derived
   numbers. Not a card — it should read as a note in the margin of a printed page,
   not as another box competing with the sheet's own fields. */
.lb .charnote { margin-top:18px; padding-top:12px; border-top:1px solid var(--edge); }
.lb .charnote .sect-t { margin-bottom:6px; }
.lb .charnote p { margin:0 0 7px; font-size:14px; line-height:1.5; color:var(--ink2); }
.lb .charnote p:last-child { margin-bottom:0; }
.lb .charnote b { color:var(--ink); font-weight:800; }
.lb .charnote__tap { color:var(--ink3); font-style:italic; }

/* ---------------- attack picker + attack damage type ----------------
   "+ Add attack" offers the hero's equipped weapons and damage spells instead of
   dropping an empty row. Rows are buttons: name | dice + type | to-hit. */
.lb-box .atkpick { display:flex; flex-direction:column; gap:14px; }
.lb-box .atkpick__group { display:flex; flex-direction:column; }
.lb-box .atkpick__group .sect-t { margin-bottom:6px; }
.lb-box .atkpick__row {
  display:grid; grid-template-columns:1fr auto 3.2em; align-items:baseline; gap:12px;
  width:100%; text-align:start; cursor:pointer;
  background:var(--paper); border:2px solid var(--edge); border-block-end:0;
  padding:10px 12px; font:inherit; color:var(--ink);
  -webkit-tap-highlight-color:transparent; transition:background .12s, border-color .12s;
}
.lb-box .atkpick__group .atkpick__row:last-child { border-block-end:2px solid var(--edge); }
.lb-box .atkpick__row:hover { background:var(--sheet3); border-color:var(--gold2); }
.lb-box .atkpick__name { font-weight:700; min-width:0; overflow-wrap:anywhere; }
.lb-box .atkpick__dmg { color:var(--ink2); white-space:nowrap; }
.lb-box .atkpick__type { color:var(--gold); font-weight:700; }
.lb-box .atkpick__hit { font-weight:800; color:var(--gold); text-align:end;
  direction:ltr; unicode-bidi:isolate; }
.lb-box .atkpick__foot { margin-top:14px; }

/* The damage type sits under the dice in the attacks table, quiet until used. */
/* 36px is the hit area app mode sets for a control, and this was 22. It is a
   real <select> on the DM's own sheet, so it wants a finger rather than a
   pointer. min-height rather than padding, so the text stays where it was. */
.lb .atk-type { display:block; margin-top:4px; font-size:13px; padding:2px 4px; max-width:12ch; min-height:36px; }
/* The same slot on a party phone, where it states the type instead of offering it. */
.lb .atk-type--ro { padding:2px 0; max-width:none; color:var(--ink2); }
/* Resist notes sit below the table so a full sentence can wrap instead of widening
   .gm-table__t (width:max-content) and pushing the other columns out of the pane. */
.lb .atk-hint { margin-top:8px; line-height:1.45; color:var(--ink3); }
.lb .atk-hint:first-of-type { margin-top:10px; }
.lb .atk-hint b { color:var(--ink2); }

/* ---------------- special abilities ----------------
   Abilities are tiles like spells and inventory, and open the same card. The gear
   list below them stays plain text — those come from items, not from this list. */
.lb .itemtile--abil .itemtile__glyph { color:var(--gold); }
.lb .abgear { margin-top:10px; }

/* ---------------- optional card fields + success roll ---------------- */
.lb .spellcard__desc { display:block; margin:0 0 12px; }
.lb .spellcard__desc > .mini { display:block; margin-bottom:2px; }
.lb .spellcard__fld { position:relative; }
.lb .spellcard__fldrm { position:absolute; top:-2px; inset-inline-end:-4px; }
/* The hero's live modifier for the ability a success roll leans on. */
.lb .item-fx__mod { flex:0 0 auto; align-self:center; font-weight:800; padding-inline:2px;
  direction:ltr; unicode-bidi:isolate; }

/* ================= ELEMENTS =================
   The "magic card" block that every item, spell and skill card carries: the
   headline estimate, the numbered how-to-use list and the uses/refill footer. See public/growlmaster/elements.js. */
.el-block{ margin-top:14px; border-top:1px solid var(--rule,rgba(0,0,0,.12)); padding-top:12px; }
.el-head{ display:flex; align-items:baseline; gap:10px; flex-wrap:wrap; margin-bottom:10px; }
/* Dice and ranges are latin no matter what the page direction is: "8 – 48" and
   "8d6" render reversed in Hebrew without an explicit isolate. */
.el-head__est,.el-head__dice,.el-uses__dots{ direction:ltr; unicode-bidi:isolate; }
.el-head__est{
  font-size:20px; line-height:1.1; font-weight:700;
  padding:4px 10px; border-radius:8px;
  background:var(--tint,rgba(0,0,0,.05)); border:1px solid var(--rule,rgba(0,0,0,.12));
}
.el-head__dice{ opacity:.7; }

.el-steps{ margin:8px 0 4px; }
.el-steps__t{ display:flex; align-items:center; gap:8px; }
.el-steps__t .gm-btn{ margin-inline-start:auto; }
.el-steps__list{ margin:6px 0 0; padding-inline-start:22px; }
.el-steps__list li{ margin:0 0 7px; line-height:1.45; }
.el-steps__list li:last-child{ margin-bottom:0; }
/* A token whose field is gone. Loud on purpose: a broken reference must never
   look like a working sentence. */
.el-steps__broken{ margin-top:6px; color:var(--danger,#B32D1F); }

.el-uses{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-top:10px; }
.el-uses__dots{ font-size:15px; letter-spacing:2px; }
.el-refill{ opacity:.7; }

/* ---- the step editor ---- */
.el-stepedit{ position:relative; }
.el-stepedit__hint{ margin:0 0 8px; opacity:.75; }
.el-stepta{
  width:100%; box-sizing:border-box; resize:vertical;
  font:inherit; line-height:1.5; padding:10px 12px;
  border-radius:8px; border:1px solid var(--rule,rgba(0,0,0,.2));
  background:var(--field,#fff); color:inherit;
}
.el-stepedit__act{ display:flex; gap:8px; justify-content:flex-end; margin-top:12px; }
/* The "@" menu. Absolute so it floats over the textarea instead of pushing the
   modal taller mid-typing. */
.el-tokmenu{
  position:absolute; inset-inline:0; bottom:0; z-index:2;
  max-height:220px; overflow:auto;
  border-radius:8px; border:1px solid var(--rule,rgba(0,0,0,.2));
  background:var(--panel,#fff); box-shadow:0 8px 24px rgba(0,0,0,.18);
}
.el-tokmenu__row{
  display:flex; align-items:baseline; gap:10px; width:100%;
  padding:7px 12px; border:0; background:none; color:inherit;
  font:inherit; text-align:start; cursor:pointer;
}
.el-tokmenu__row:hover,.el-tokmenu__row:focus-visible{ background:var(--tint,rgba(0,0,0,.06)); }
.el-tokmenu__row b{ direction:ltr; unicode-bidi:isolate; }
.el-tokmenu__row span{ margin-inline-start:auto; opacity:.7; direction:ltr; unicode-bidi:isolate; }
.lb-box--wide{ max-width:min(680px,94vw); }
/* Estimated range beside a dice field: "8d6" and "8 – 48" answer different
   questions, and a child needs the second one. */
.item-fx__est{ opacity:.75; white-space:nowrap; direction:ltr; unicode-bidi:isolate; }
.item-fx__vs{ opacity:.7; padding:0 2px; }
/* "How to use it" + its badge: on a narrow pane the badge takes its own line so
   the heading never breaks mid-phrase. */
.el-steps__t{ flex-wrap:wrap; gap:4px 8px; }
.el-steps__t .gm-btn{ order:2; }

/* ---- phones: the page scrolls, and the battle tab is two pages ----------------
   The shell is a fixed 100dvh column with its own scroller inside, which is right
   on a desktop and wrong on a phone: content that outgrew the box was simply
   unreachable. Below the single-pane breakpoint the shell gives up its height and
   the document scrolls, the way any other page does. The bottom nav sticks, since
   it is how you change tab and it must not scroll away. */
@media (max-width:820px){
  .lb { height:auto; min-height:100dvh; overflow:visible; }
  .lb-body, .lb-split { min-height:0; overflow:visible; }
  .lb-pane[data-side="right"] { overflow:visible; min-height:0; }
  /* The stage stops being a scroller and simply takes the height of its content. */
  .lb-stage { flex:0 0 auto; min-height:0; overflow:visible; }
  .lb-mnav { position:sticky; bottom:0; z-index:20; }
  /* Inner scrollers hand their height back for the same reason: two nested
     scrollers on a phone means one of them is a trap. */
  .lb .adv-sugs { overflow:visible; min-height:0; }
  /* The board keeps its own pan and zoom, so it must not also be a scroll target.
     ITS BOX KEEPS ONE SIZE (Maayan, 2026-10-24: "the box that holds the battle grid gets zoomed along with the grid.
     That box must keep its size; only the grid inside it zooms"): it was as tall as its content, and the zoom changes
     the content's layout size (fitBtBoard's --pw), so the box grew and shrank with it, and on a wide board it was a
     strip. Here it fills the room down to the bottom bar (the tab's column grows to the pane, the board to the
     column), at least 300px, the same at every zoom; the grid zooms and pans inside it. */
  .lb-pane:has(.bt-view) .lb-stage { flex:1 0 auto; display:flex; flex-direction:column; }
  .lb .monstack:has(> .bt-board .bt-view) { flex:1 0 auto; display:flex; flex-direction:column; }
  .lb .monbody.bt-board { flex:1 0 auto; }
  .lb .bt-view { flex:1 0 300px; min-height:300px; max-height:none; }
}

/* One page slides out as the next slides in. Direction is by writing mode, so
   "forward" means the same thing to a Hebrew reader as to an English one. */
.lb .bt-page { --bt-slide:26%; }
.lb[dir="rtl"] .bt-page, [dir="rtl"] .lb .bt-page { --bt-slide:-26%; }
.lb .bt-page--fwd { animation:btPageFwd .26s cubic-bezier(.22,.9,.24,1) both; }
.lb .bt-page--back { animation:btPageBack .26s cubic-bezier(.22,.9,.24,1) both; }
@keyframes btPageFwd { from { opacity:0; transform:translateX(var(--bt-slide)); } to { opacity:1; transform:none; } }
@keyframes btPageBack { from { opacity:0; transform:translateX(calc(-1 * var(--bt-slide))); } to { opacity:1; transform:none; } }
@media (prefers-reduced-motion:reduce){
  .lb .bt-page--fwd, .lb .bt-page--back { animation:none; }
}
/* Page two is only the card, so let it breathe instead of sitting in a strip. On a
   wide screen the strip is a ROW of a fixed height: card on the left, a 176px column
   of actions on the right. On a 430px phone that row is simply wider than the screen,
   which is how the action buttons ended up running off the edge. Here they stack:
   the card takes the width it wants and the actions become a pair of columns under
   it, each button big enough for a thumb. */
.lb .bt-board--cardpage { padding-top:6px; }
@media (max-width:820px){
  .lb .bt-board--cardpage .bt-selbar { height:auto; min-height:0; flex-direction:column;
    align-items:stretch; border-bottom:0; padding:14px; }
  .lb .bt-board--cardpage .bt-card { max-width:none; height:auto; }
  .lb .bt-board--cardpage .bt-card__top { min-height:150px; }
  .lb .bt-board--cardpage .bt-actions,
  .lb .bt-board--cardpage .bt-duel { flex:0 0 auto; display:grid; grid-template-columns:1fr 1fr; gap:8px; }
  .lb .bt-board--cardpage .bt-act { min-height:58px; flex:0 0 auto; }
}

/* Party QR. The code is the point of the panel, so it gets the room: big enough
   to scan from across a table in bad light, and capped so it never outgrows a
   phone in portrait. currentColor on the path means the code takes the sheet's
   ink and needs no second colour anywhere. */
.partyqr__code { width:min(320px,70vw); aspect-ratio:1; margin:4px auto 14px; color:var(--ink); }
.partyqr__code svg { display:block; width:100%; height:100%; }
.partyqr__note { margin-bottom:10px; }
.partyqr__link input { font-size:13px; }
/* The Present window's "Present on TV", under the party's code and link. */
.partyqr__tv { display:flex; justify-content:center; margin-top:14px; }

/* TABLES (app.js, docs/growlmaster/tables-and-growls.md). THE HEADER INSIDE A TABLE (Maayan's sketch, 2026-10-26,
   docs/mocks/table-header-settings-sketch-2026-10-26.jpg): one row at every width, the page's direction (mirrored in
   Hebrew): Exit table | the table's picture, its name, its players under it | the dice | Gorak, their own picture last.
   Its groups give their buttons to the row (display:contents), each in its order. "Inside a table" is its Exit. */
.lb-top__mid { display:none; }
.lb:has(#gmExitTable) .lb-top { display:flex; align-items:center; gap:8px; }
.lb:has(#gmExitTable) .lb-top .brand { display:none; }
.lb:has(#gmExitTable) .lb-top__left, .lb:has(#gmExitTable) .lb-top__actions { display:contents; }
.lb:has(#gmExitTable) #gmLogoSlot { order:1; }
.lb:has(#gmExitTable) .lb-top__mid { order:2; display:flex; flex:1 1 auto; min-width:0; }
.lb:has(#gmExitTable) #gmTableSlot { order:3; }
.lb:has(#gmExitTable) #aiToggleSlot { order:4; }
.lb:has(#gmExitTable) #gmAcct { order:5; }
#gmLogoSlot, #gmTableSlot { display:flex; align-items:center; gap:8px; }
.gm-dicebtn__i { width:24px; height:24px; object-fit:contain; display:block; }
/* Exit table: its way-out icon alone, "[←" (Maayan's notes, 2026-10-26), a full tap's size; mirrored in Hebrew. */
.gm-exittable { flex:none; display:inline-grid; place-items:center; width:44px; height:44px; padding:0; border:0; border-radius:12px; background:none; color:var(--ink); cursor:pointer; }
.gm-exittable:hover, .gm-exittable:focus-visible { background:rgba(139,47,34,.08); }
.gm-exittable__arrow svg { width:28px; height:28px; display:block; }
html[dir="rtl"] .gm-exittable__arrow svg { transform:scaleX(-1); }
/* The table: its picture (a rounded square), its name, and under it the players' faces, how many, and who. */
.gm-tablechip { display:flex; align-items:center; gap:10px; min-width:0; max-width:100%; min-height:48px; padding:3px 8px 3px 4px; border:0; border-radius:12px; background:none; color:var(--ink); font:inherit; text-align:start; cursor:pointer; }
.gm-tablechip:hover, .gm-tablechip:focus-visible, .gm-tablechip[aria-expanded="true"] { background:rgba(139,47,34,.08); }
.gm-tablechip__img, .gm-tdrop__img { flex:none; display:grid; place-items:center; width:44px; height:44px; border-radius:12px; border:2px solid var(--ink); background:var(--sheet3); color:var(--ink); }
/* its picture: its own, else the GrowlMaster eye (the open pin, the empty picture's .gm-nopic) */
.gm-tablechip__img, .gm-tdrop__img { overflow:hidden; }
.gm-tablechip__img img, .gm-tdrop__img img { width:100%; height:100%; object-fit:cover; display:block; }
.gm-tablepin { display:block; width:62%; aspect-ratio:1; background:var(--ink);
  -webkit-mask:url(/growlmaster/icons/pin-open.svg) center/contain no-repeat; mask:url(/growlmaster/icons/pin-open.svg) center/contain no-repeat; }
.gm-tablechip__txt { display:flex; flex-direction:column; gap:2px; min-width:0; }
/* a name in another script than the page's (an English name in Hebrew) reads and is cut from its own start */
.gm-tablechip__name, .gm-tdrop__name { unicode-bidi:plaintext; }
.gm-tablechip__name { font-family:'Frank Ruhl Libre',serif; font-weight:700; font-size:22px; line-height:1.1; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.gm-tablechip__players { gap:6px; font-size:14px; min-width:0; }
.gm-tablechip__players .gm-face { width:22px; height:22px; margin-inline-start:-7px; }
.gm-tablechip__players .gm-face:first-child { margin-inline-start:0; }
.gm-tablechip__players .gm-face b { font-size:10px; }
.gm-tablechip__who { min-width:0; overflow:hidden; text-overflow:ellipsis; font-weight:600; }
.gm-players { display:flex; align-items:center; flex:none; gap:8px; font-weight:700; white-space:nowrap; }
.gm-players__faces { display:flex; }
.gm-face { flex:none; width:30px; height:30px; border-radius:50%; overflow:hidden; display:grid; place-items:center; background:var(--sheet3); color:var(--ink); border:2px solid var(--sheet); box-shadow:0 0 0 1.5px var(--edge); margin-inline-start:-10px; }
.gm-face:first-child { margin-inline-start:0; }
.gm-face img { width:100%; height:100%; object-fit:cover; object-position:50% 12%; }
.gm-face b { font-size:13px; }
.gm-face.is-here { box-shadow:0 0 0 2px #2f9e5b; }
/* GORAK ON A PHONE, A CIRCLE (Maayan's notes, 2026-10-26): his portrait in the button's own ring, no words; on a wider
   screen the same button with them, as before ("one element restyled per width"; assistant.css .jim-ai-toggle). */
@media (max-width:600px){
  .lb:has(#gmExitTable) #aiToggle .jim-ai-id { display:none; }
  .lb:has(#gmExitTable) #aiToggle { gap:0; padding:4px; border-radius:50%; }
}
/* A party phone has the table's Exit, name and players (Gorak is the growlmaster's). */
.lb.party .lb-top__left.dm-only { display:flex !important; }
.lb.party #aiToggleSlot { display:none; }
@media (max-width:600px){
  .lb:has(#gmExitTable) .lb-top { gap:4px; padding-inline:8px; }
  .gm-tablechip { gap:8px; padding-inline:2px 4px; }
  .gm-tablechip__img { width:40px; height:40px; }
  .gm-tablechip__name { font-size:19px; }
  .gm-tablechip__players { font-size:13px; }
}

/* TABLE SETTINGS, A DROPDOWN (app.js openTableSettings): hanging from the header's table, its notch under the table's
   picture (--notch), the page's paper, its own scroll; under the windows opened from it (.lb-ovl, 70). */
.gm-tdrop { --notch:40px; position:fixed; z-index:65; box-sizing:border-box; display:flex; flex-direction:column; background:var(--sheet); color:var(--ink); border:2px solid var(--ink); border-radius:18px; box-shadow:0 14px 34px rgba(42,31,12,.28); }
.gm-tdrop::before { content:""; position:absolute; top:-10px; left:calc(var(--notch) - 9px); width:16px; height:16px; background:var(--sheet); border-left:2px solid var(--ink); border-top:2px solid var(--ink); transform:rotate(45deg); }
.gm-tdrop__in { flex:1 1 auto; min-height:0; overflow:auto; overscroll-behavior:contain; padding:12px 14px 16px; border-radius:16px; }
.gm-tdrop__top { display:grid; grid-template-columns:1fr auto 1fr; align-items:center; gap:8px; }
.gm-tdrop__lang { justify-self:start; min-width:0; }
.gm-tdrop__langbtn { display:inline-flex; align-items:center; gap:6px; min-height:44px; padding:0 8px; border:0; border-radius:10px; background:none; color:var(--ink); font:inherit; font-size:15px; font-weight:700; cursor:pointer; }
.gm-tdrop__langbtn:hover, .gm-tdrop__langbtn:focus-visible { background:rgba(139,47,34,.08); }
.gm-tdrop__langbtn .gm-chev svg { width:16px; height:16px; display:block; }
/* the languages' window (app.js openLangList): the header menu's own rows, a full tap's height each */
.gm-langlist { list-style:none; margin:0; padding:0; display:grid; gap:2px; }
.gm-langlist a { display:flex; align-items:center; gap:12px; min-height:48px; padding:6px 12px; border-radius:10px; color:var(--ink); text-decoration:none; font-weight:700; }
.gm-langlist a:hover, .gm-langlist a:focus-visible, .gm-langlist a.is-on { background:var(--sheet2); }
.gm-langlist .lp__rowflag { font-size:22px; line-height:1; }
.gm-langlist .lp__name { flex:1 1 auto; }
.gm-langlist .lp__tick { visibility:hidden; }
.gm-langlist a.is-on .lp__tick { visibility:visible; }
.gm-tdrop__img { width:64px; height:64px; border-radius:16px; }
.gm-tdrop__pill { justify-self:end; display:flex; border:2px solid var(--ink); border-radius:999px; overflow:hidden; }
.gm-tdrop__pb { min-width:44px; min-height:44px; display:grid; place-items:center; padding:0 10px; border:0; background:var(--sheet); color:var(--ink); font:inherit; font-size:20px; font-weight:700; cursor:pointer; }
.gm-tdrop__pb + .gm-tdrop__pb { border-inline-start:2px solid var(--ink); }
.gm-tdrop__pb:hover, .gm-tdrop__pb:focus-visible { background:var(--sheet3); }
.gm-tdrop__name { display:block; margin-top:8px; text-align:center; font-family:'Frank Ruhl Libre',serif; font-size:22px; line-height:1.15; overflow-wrap:anywhere; }
.gm-tdrop__n { display:block; text-align:center; }
.gm-tdrop__tabs { margin:10px 0 8px; }
/* every tab in the row, at 390 too: each its share, the words whole (a longer language scrolls sideways, never shrinks) */
.gm-tdrop__tabs .gm-tab { flex:1 0 auto; padding-inline:8px; }
.gm-tdrop__body { min-width:0; }
.gm-tdrop__add { display:flex; align-items:center; gap:12px; width:100%; min-height:56px; padding:6px 4px; border:0; border-bottom:2px solid var(--edge); background:none; color:var(--ink); font:inherit; font-size:18px; font-weight:700; text-align:start; cursor:pointer; }
.gm-tdrop__add:hover, .gm-tdrop__add:focus-visible { background:rgba(139,47,34,.06); }
.gm-tdrop__plus { flex:none; display:grid; place-items:center; width:44px; height:44px; border:2px solid var(--ink); border-radius:50%; }
.gm-tdrop__plus svg { width:24px; height:24px; display:block; }   /* an icon, drawn in the middle (a "+" letter sat high) */
/* Add Player's window (app.js openAddPlayer): their players, then the ways in */
.gm-addplayer .gm-tdrop__mine { margin:8px 0 12px; padding:0; border:0; }
/* the switch's words on two lines when the room is short, so the nickname keeps its own (a phone) */
.gm-tdrop__mine .gm-family__inout, .gm-tdrop__mine .gm-family__inout * { white-space:normal; line-height:1.15; text-align:center; }
@media (max-width:600px){ .gm-tdrop__mine .gm-family__inout { max-width:7.5em; padding-inline:10px; } }
.gm-tdrop__mine .gm-family__list { margin:0; }
.gm-tdrop__mine .gm-family__list li { display:grid; grid-template-columns:minmax(0,1fr) auto; align-items:center; gap:8px; }
.gm-tdrop__mine .gm-family__list li.is-out .gm-family__row { opacity:.5; }
.gm-addplayer > .gm-family__h, .gm-invite__h { font-size:16px; }
.gm-tdrop__invite { display:flex; flex-direction:column; gap:8px; padding-top:12px; border-top:1px solid var(--edge); }
.gm-tdrop__invite.is-alone { padding-top:0; border-top:0; }
.gm-tdrop__members { list-style:none; margin:4px 0 0; padding:0; }
.gm-tdrop__members > li { border-bottom:1px solid var(--edge); }
.gm-tdrop__m { display:flex; align-items:center; gap:12px; width:100%; min-height:60px; padding:6px 4px; border:0; background:none; color:var(--ink); font:inherit; text-align:start; }
button.gm-tdrop__m { cursor:pointer; }
button.gm-tdrop__m:hover, button.gm-tdrop__m:focus-visible { background:rgba(139,47,34,.06); }
.gm-tdrop__m .gm-family__pic { flex:none; width:44px; height:44px; display:grid; place-items:center; }
.gm-tdrop__m .gm-family__pic > * { width:44px; height:44px; }
.gm-tdrop__who { display:flex; flex-direction:column; flex:1 1 auto; min-width:0; }
.gm-tdrop__who .gm-family__n { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-weight:700; }
.gm-tdrop__gm { color:#7b3fc4; font-weight:700; font-size:14px; }
.gm-tdrop__go { flex:none; display:grid; place-items:center; align-self:center; color:var(--ink2, #6f5c3c); }
.gm-tdrop__go svg { width:22px; height:22px; display:block; }
html[dir="rtl"] .gm-tdrop__go { transform:scaleX(-1); }
.gm-tdrop__set { padding-top:4px; }
/* THE LOBBY ON A PHONE HAS ITS LOGO, top left (Maayan: "mobile lobby - logo missing on the top left"): there is room,
   only the account is beside it. */
@media (max-width:430px){
  .lb.is-outside .lb-top .brand { display:flex; grid-column:1; justify-self:start; }
  .lb.is-outside .lb-top .brand img { height:34px; }
}
/* PHONES, outside a table: Create / Join and Account. */
@media (max-width:430px){
  .lb.is-outside .lb-top { grid-template-columns:minmax(0,1fr) auto; gap:6px; padding-inline:10px; }
  /* Its room before the app fills it (the server's home), so nothing under it moves. */
  .lb.is-outside .lb-top { min-height:62px; box-sizing:border-box; }
  .lb.is-outside .lb-top__actions { grid-column:2; }
  .lb.is-outside #gmTableSlot { gap:6px; }
}
/* Who is at the table, Invite others, Create / Join, and the table's own settings (in Settings, the owner's). */
.settable { margin-bottom:10px; padding-bottom:6px; border-bottom:2px solid var(--edge); }
.settable__h { font-family:'Frank Ruhl Libre',serif; font-size:20px; margin:4px 0 2px; }
.setrow--seats { flex-direction:column; align-items:stretch; gap:8px; }
.setrow--seats #tsSeats { width:100%; }
.settable .gmname { display:flex; gap:8px; align-items:center; }
.settable .gmname input { flex:1; min-width:0; font:inherit; font-size:16px; padding:8px 10px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); border-radius:0; }
.gm-tablebox .gm-tablebox__name { overflow-wrap:anywhere; }
.gm-tablebox__at { font-weight:700; margin:10px 0 6px; }
.gm-tablebox__members { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:6px; }
.gm-tablebox__members li { display:flex; align-items:center; justify-content:space-between; gap:10px; padding:8px 10px; border:2px solid var(--edge); background:var(--sheet); }
.gm-tablebox__who { min-width:0; overflow-wrap:anywhere; }
.gm-tablebox__role { flex:none; }
.gm-tablebox__members li .gm-tablebox__who { flex:1; }
.gm-tablebox__foot { display:flex; justify-content:flex-end; gap:8px; margin-top:16px; }
.gm-invite { padding:12px 0; border-bottom:1.5px solid var(--edge); display:flex; flex-direction:column; gap:6px; align-items:flex-start; }
.gm-invite:last-child { border-bottom:0; }
.gm-invite__role { font-weight:700; }
.gm-invite .chatin { width:100%; }
.gm-tablename { display:flex; flex-direction:column; gap:6px; }
.gm-tablename input { font:inherit; font-size:16px; padding:8px 10px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); }
.gm-tablename__err { color:var(--red); }
.gm-tablename__code { font-size:22px !important; letter-spacing:.18em; text-transform:uppercase; text-align:center; font-weight:700; }
.gm-tablename__or { text-align:center; margin-top:2px; }
/* Presence: who is at the table now (a green dot). */
.gm-tablebox__who { display:flex; align-items:center; gap:8px; }
.gm-sr { position:absolute; width:1px; height:1px; overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }
/* The table's code, under Present's QR and in Invite others. */
.partyqr__join { display:flex; flex-direction:column; align-items:center; gap:2px; margin:0 auto 12px; }
.partyqr__join[hidden] { display:none; }
.partyqr__joincode { font-size:28px; font-weight:700; letter-spacing:.2em; color:var(--ink); font-variant-numeric:tabular-nums; }
/* the code selectable (a tap takes all of it), its Copy button beside it */
.partyqr__coderow { display:inline-flex; align-items:center; gap:10px; flex-wrap:wrap; justify-content:center; }
.partyqr__joincode { -webkit-user-select:all; user-select:all; cursor:text; }
.gm-invite .partyqr__join { align-items:flex-start; margin:0; }
.gm-invite .partyqr__joincode { font-size:20px; }
.gm-outside { position:absolute; inset:0; z-index:5; display:flex; flex-direction:column; background:var(--paper); }
/* Only the slim header and the bottom bar stay; each page (Home, My Growls) is its own scroller (Maayan, 2026-10-18). */
.gm-outside__pane { flex:1; min-height:0; overflow:auto; }
/* THE LOBBY'S STATES (docs/growlmaster-screen-states.md, "Lobby"; lobby.js goToState, goToWindow): what shows is the
   state on #lb, data-screen-state (the tab, the table forms) and data-window-state (what is open on top of it), never a
   part shown or hidden on its own. The server writes the first state, so the first paint is it. */
.lb[data-screen-state] .gm-outside__pane { display:none; }
.lb[data-screen-state="lobby_tab_my_tables"] #gmTables,
.lb[data-screen-state^="create_table_"] #gmTables, .lb[data-screen-state^="join_table_"] #gmTables,
.lb:is([data-screen-state="leaving_to_new_table"],[data-screen-state="leaving_to_joined_table"],[data-screen-state="leaving_to_open_table"]) #gmTables,
.lb[data-screen-state="lobby_tab_growls"] #gmHome,
.lb[data-screen-state="lobby_tab_create_growl"] #gmCreatePane,
.lb:is([data-screen-state="lobby_tab_profile"],[data-screen-state="leaving_to_open_table_from_profile"]) #gmMine,
.lb[data-screen-state="someone_else_profile_open"] #gmProfile { display:block; }
/* a table form's reason and Try again: in its failed state only */
.gm-lobby__failed { display:none; }
.lb[data-screen-state="create_table_failed_try_again"] .gm-lobby__box--create .gm-lobby__failed,
.lb[data-screen-state="join_table_failed_try_again"] .gm-lobby__box--join .gm-lobby__failed { display:block; }
/* its button at work: pressed and waiting until the server answers, and on the way out after it */
.lb:is([data-screen-state="create_table_button_busy"],[data-screen-state="leaving_to_new_table"]) .gm-lobby__box--create button[type="submit"],
.lb:is([data-screen-state="join_table_button_busy"],[data-screen-state="leaving_to_joined_table"]) .gm-lobby__box--join button[type="submit"] { opacity:.7; cursor:progress; pointer-events:none; }
/* going into a table: the screen goes black (gmTables.js goes when the black is in) */
.lb[data-screen-state^="leaving_to_"] #gmToBlack { opacity:1; pointer-events:auto; }
/* The bottom bar: small icon tabs, the active one marked (Instagram's). */
.gm-bottombar { flex:none; display:flex; justify-content:space-around; align-items:center; height:52px; padding-bottom:env(safe-area-inset-bottom,0);
  border-top:2px solid var(--edge); background:var(--sheet); }
.gm-bottombar__tab { position:relative; display:grid; place-items:center; width:64px; height:44px; background:none; border:0; color:var(--ink3); cursor:pointer; }
/* the tab on screen, marked by the screen state (lobby.js); My tables while a table form is open too */
:is(.lb:is([data-screen-state="lobby_tab_my_tables"],[data-screen-state^="create_table_"],[data-screen-state^="join_table_"],[data-screen-state="leaving_to_new_table"],[data-screen-state="leaving_to_joined_table"],[data-screen-state="leaving_to_open_table"]) .gm-bottombar__tab[data-pane="gmTables"], .lb[data-screen-state="lobby_tab_growls"] .gm-bottombar__tab[data-pane="gmHome"], .lb[data-screen-state="lobby_tab_create_growl"] .gm-bottombar__tab[data-pane="gmCreatePane"], .lb:is([data-screen-state="lobby_tab_profile"],[data-screen-state="leaving_to_open_table_from_profile"]) .gm-bottombar__tab[data-pane="gmMine"]) { color:var(--ink); }
:is(.lb:is([data-screen-state="lobby_tab_my_tables"],[data-screen-state^="create_table_"],[data-screen-state^="join_table_"],[data-screen-state="leaving_to_new_table"],[data-screen-state="leaving_to_joined_table"],[data-screen-state="leaving_to_open_table"]) .gm-bottombar__tab[data-pane="gmTables"], .lb[data-screen-state="lobby_tab_growls"] .gm-bottombar__tab[data-pane="gmHome"], .lb[data-screen-state="lobby_tab_create_growl"] .gm-bottombar__tab[data-pane="gmCreatePane"], .lb:is([data-screen-state="lobby_tab_profile"],[data-screen-state="leaving_to_open_table_from_profile"]) .gm-bottombar__tab[data-pane="gmMine"])::after { content:""; position:absolute; bottom:2px; width:20px; height:3px; border-radius:2px; background:var(--accent); }
.gm-bottombar__plus { color:var(--ink); }
.gm-bottombar__t { display:none; }
/* ON A WIDE SCREEN THE TABS ARE A LEFT PANEL, one below the other, with their names (Maayan: "like in instagram").
   It lies OVER the pages, so every page is the window's full width and centred on the window (Maayan: "the left
   panel needs to be on top of the main div, this way we won't need to mess with centering one by one"). */
@media (min-width:900px){
  .gm-outside .gm-bottombar { position:absolute; left:0; top:0; bottom:0; z-index:3; flex-direction:column; justify-content:flex-start; align-items:stretch; gap:4px; width:220px; height:auto;
    padding:18px 10px; border-top:0; border-right:2px solid var(--edge); box-sizing:border-box; background:var(--paper); }
  .gm-bottombar__tab { width:auto; height:48px; display:flex; align-items:center; justify-content:flex-start; gap:14px; padding:0 14px; border-radius:10px; font:inherit; font-size:16px; font-weight:700; color:var(--ink2); }
  .gm-bottombar__tab:hover { background:rgba(139,47,34,.07); }
  :is(.lb:is([data-screen-state="lobby_tab_my_tables"],[data-screen-state^="create_table_"],[data-screen-state^="join_table_"],[data-screen-state="leaving_to_new_table"],[data-screen-state="leaving_to_joined_table"],[data-screen-state="leaving_to_open_table"]) .gm-bottombar__tab[data-pane="gmTables"], .lb[data-screen-state="lobby_tab_growls"] .gm-bottombar__tab[data-pane="gmHome"], .lb[data-screen-state="lobby_tab_create_growl"] .gm-bottombar__tab[data-pane="gmCreatePane"], .lb:is([data-screen-state="lobby_tab_profile"],[data-screen-state="leaving_to_open_table_from_profile"]) .gm-bottombar__tab[data-pane="gmMine"]) { color:var(--ink); font-weight:800; }
  :is(.lb:is([data-screen-state="lobby_tab_my_tables"],[data-screen-state^="create_table_"],[data-screen-state^="join_table_"],[data-screen-state="leaving_to_new_table"],[data-screen-state="leaving_to_joined_table"],[data-screen-state="leaving_to_open_table"]) .gm-bottombar__tab[data-pane="gmTables"], .lb[data-screen-state="lobby_tab_growls"] .gm-bottombar__tab[data-pane="gmHome"], .lb[data-screen-state="lobby_tab_create_growl"] .gm-bottombar__tab[data-pane="gmCreatePane"], .lb:is([data-screen-state="lobby_tab_profile"],[data-screen-state="leaving_to_open_table_from_profile"]) .gm-bottombar__tab[data-pane="gmMine"])::after { left:0; top:12px; bottom:auto; width:3px; height:24px; }
  .gm-bottombar__t { display:inline; }
}
.gm-pinico { display:block; width:26px; height:26px; background:currentColor;
  -webkit-mask:url(/growlmaster/icons/pin-open.svg) center/contain no-repeat; mask:url(/growlmaster/icons/pin-open.svg) center/contain no-repeat; }
.gm-avatar { display:grid; place-items:center; width:28px; height:28px; border-radius:50%; overflow:hidden; background:var(--sheet3); border:2px solid transparent; }
:is(.lb:is([data-screen-state="lobby_tab_my_tables"],[data-screen-state^="create_table_"],[data-screen-state^="join_table_"],[data-screen-state="leaving_to_new_table"],[data-screen-state="leaving_to_joined_table"],[data-screen-state="leaving_to_open_table"]) .gm-bottombar__tab[data-pane="gmTables"], .lb[data-screen-state="lobby_tab_growls"] .gm-bottombar__tab[data-pane="gmHome"], .lb[data-screen-state="lobby_tab_create_growl"] .gm-bottombar__tab[data-pane="gmCreatePane"], .lb:is([data-screen-state="lobby_tab_profile"],[data-screen-state="leaving_to_open_table_from_profile"]) .gm-bottombar__tab[data-pane="gmMine"]) .gm-avatar { border-color:var(--ink); }
.gm-avatar img { width:100%; height:100%; object-fit:cover; }
/* A picture from the presets (GmAvatar, sprites.js avatarFace): its frame of the sheet, on its own colour until the sheet
   is there. */
/* (a preset picture's own drawing, .gm-avatar--preset and .gm-av: dice.css, beside the sprites', loaded on every page that shows one) */
/* THE PICKER (app.js openPick): its top and bottom stay, the grid scrolls between them (Maayan: "an inner scroll for the
   grid. top and bottom of modal are sticky"); the pictures big ("×2 larger, fewer columns"), a real tap on a phone. */
.lb-box.gm-pickbox { display:flex; flex-direction:column; overflow:hidden; max-height:min(86vh, 820px); }
/* PLAYER-PANEL (studio.js playerPanel): its head and, while editing, its footer stay; what is between them scrolls. Its
   tabs (to come) sit under the head. */
.lb-box.player-panel-box { display:flex; flex-direction:column; overflow:hidden; max-height:min(90vh, 820px); max-height:min(90dvh, 820px); }
/* ON AN IPHONE (Maayan, 2026-10-26: its top was cut and a finger scrolled nothing): vh there is the screen without
   Safari's bars, taller than what shows, so the window ran past the top and its body never had to scroll; dvh is what
   shows. A finger on its head or around it never scrolls the page under it; the body scrolls itself (contain). */
.lb-ovl:has(> .player-panel-box) { touch-action:none; overscroll-behavior:contain; }
.player-panel { display:flex; flex-direction:column; flex:1 1 auto; min-height:0; }
.player-panel__head, .player-panel__tabs, .player-panel__foot { flex:none; }
.player-panel__body { flex:1 1 auto; min-height:0; overflow:auto; overscroll-behavior:contain; padding:2px 2px 8px; }
/* THE TABS (the player, Stats) and the Stats table: real touch sizes; the table scrolls sideways, never made small */
/* ONE TAB ROW (the player window's, the account window's): real touch sizes; wider than its room, it scrolls sideways */
.gm-tabs:not([hidden]) { display:flex; gap:6px; margin:0 0 10px; border-bottom:1px solid var(--line, #d8c9a5); overflow-x:auto; scrollbar-width:none; -webkit-overflow-scrolling:touch; }
.gm-tabs::-webkit-scrollbar { display:none; }
.gm-tab { flex:none; min-height:44px; padding:8px 16px; font:inherit; font-weight:700; white-space:nowrap; color:var(--ink2, #6f5c3c); background:none; border:0; border-bottom:3px solid transparent; margin-bottom:-1px; cursor:pointer; }
.gm-tab.is-on { color:var(--ink, #3a2c17); border-bottom-color:var(--ink, #3a2c17); }
/* ACCOUNT & PLAYERS (Maayan's wireframe, 2026-10-24): cards, one under the other */
.gm-ep { display:flex; flex-direction:column; gap:12px; }
.gm-ep__card { background:var(--sheet2, #f3ead6); border:1px solid var(--edge, #d8c9a5); border-radius:14px; padding:16px; }
.gm-ep__me { display:flex; align-items:center; gap:16px; }
.gm-ep__pic { position:relative; flex:none; width:96px; height:96px; padding:0; border:0; background:none; border-radius:50%; cursor:pointer; }
.gm-ep__face, .gm-ep__face > * { display:block; width:96px; height:96px; border-radius:50%; overflow:hidden; }
.gm-ep__face img, .gm-ep__face svg { width:100%; height:100%; object-fit:cover; }
.gm-ep__pen { position:absolute; inset-inline-end:-2px; bottom:-2px; width:36px; height:36px; display:grid; place-items:center; border-radius:50%; background:var(--sheet, #fff8e8); border:2px solid var(--ink2, #6f5c3c); color:var(--ink, #3a2c17); }
.gm-ep__pen svg { width:18px; height:18px; }
.gm-ep__names { flex:1; min-width:0; display:flex; flex-direction:column; gap:6px; }
.gm-ep__name { width:100%; min-height:48px; box-sizing:border-box; padding:8px 12px; font:inherit; font-size:20px; font-weight:700; color:var(--ink, #3a2c17); background:var(--sheet, #fff8e8); border:2px solid var(--edge, #d8c9a5); border-radius:10px; }
.gm-ep__name:focus-visible { outline:3px solid var(--ink2, #6f5c3c); outline-offset:1px; }
.gm-ep__jim { color:var(--ink2, #6f5c3c); font-size:15px; overflow-wrap:break-word; }
.gm-ep__myname { font-family:'Frank Ruhl Libre',serif; font-size:24px; line-height:1.15; overflow-wrap:break-word; }
/* THE PROFILE'S TOP CARD (Maayan, 2026-10-24, on his phone): its nickname, whole, broken only between words, in a size
   a phone holds; Edit, a word in the card's top end corner (a 44px tap), the name kept clear of it */
.gm-ep__card--me { position:relative; }
.gm-ep__card--me .gm-ep__names { padding-inline-end:56px; }
.gm-ep__nickline { display:block; }
.gm-ep__editlink { position:absolute; top:4px; inset-inline-end:8px; min-width:44px; min-height:44px; padding:0 8px; font-size:16px; }
@media (max-width:480px) { .gm-ep__card--me .gm-ep__myname { font-size:20px; } }
.gm-ep__err { margin:0; color:#a3271b; font-size:14px; }
.gm-ep__btns { display:flex; flex-wrap:wrap; gap:8px; }
.gm-ep__credits { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:10px; }
.gm-ep__n { font-size:19px; }
.gm-ep__refill { flex-basis:100%; margin:0; }
.gm-ep__cardhead { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px; }
.gm-ep .gm-family__list { margin:0; }
/* A PLAYER'S ROW (Maayan, 2026-10-24, on his phone: the name ran into "Join game", cut): three columns, the name's
   (shrinks, wraps between words), the button's (its own width, never cut) and the ⋮'s (44px; their own row an empty one,
   so the buttons line up) */
.gm-ep .gm-family__list li { display:grid; grid-template-columns:minmax(0,1fr) auto 44px; align-items:center; gap:8px; }
.gm-family__inout { white-space:nowrap; overflow:visible; }
.gm-family__row .gm-family__n { word-break:normal; }   /* between words; a word broken only if it alone is wider than the column */
.gm-ep .gm-family__list li.is-out .gm-family__row { opacity:.45; }
.gm-ep .gm-family__row { flex:1; min-width:0; }
/* the ⋮, a real touch size (2026-10-24: "too small"); over the app's own 16px for every button (AppStyles) */
.gm-family__menu, .jim-app .gm-family__menu { flex:none; width:44px; height:44px; display:grid; place-items:center; font-size:28px; font-weight:900; line-height:1; color:var(--ink, #3a2c17); background:none; border:0; border-radius:10px; cursor:pointer; }
button.gm-family__menu:hover { background:var(--sheet3, #eadfc6); }
.gm-ep__manage { align-self:flex-start; padding:10px 2px; }
.gm-ep__bar { display:flex; align-items:center; min-height:52px; margin-bottom:12px; border-bottom:1px solid var(--edge, #d8c9a5); }
.gm-ep__back { min-height:44px; padding:6px 10px 6px 2px; font:inherit; font-size:18px; font-weight:700; color:inherit; background:none; border:0; cursor:pointer; }
.gm-ep__back span { display:inline-block; font-size:24px; }
[dir="rtl"] .gm-ep__back span { transform:scaleX(-1); }
.gm-ep__page { max-width:640px; margin:0 auto; padding:8px 16px 0; }
/* the legal lines well below the last of it (Maayan, 2026-10-24: "a lot of space"), a line above them */
.gm-ep__page > .gm-ep { margin-bottom:96px; }   /* (the legal lines' own style sets their margin: the room is the content's) */
.gm-ep__page > .gmlegal { padding-top:28px; border-top:1px solid var(--edge, #d8c9a5); }
.gm-ep__cheats { align-self:flex-start; padding:10px 2px; margin-top:-6px; }
.gm-cheats { display:flex; flex-direction:column; gap:12px; max-height:60vh; overflow:auto; }
.gm-cheats__p { border:1px solid var(--edge, #d8c9a5); border-radius:10px; padding:10px 12px; display:flex; flex-wrap:wrap; gap:10px 16px; align-items:center; }
.gm-cheats__p legend { font-weight:800; padding:0 4px; }
.gm-cheats__p input[type="number"] { width:90px; min-height:40px; font:inherit; }
.gm-cheats__r { flex-basis:100%; display:flex; flex-wrap:wrap; gap:6px 14px; }
.gm-cheats__g { display:inline-flex; align-items:center; gap:4px; min-height:32px; }
.gm-ep__logout { align-self:flex-start; margin-top:-6px; padding:10px 2px; }   /* lined up with Manage Jim Account above it */
/* MATH, a section under Players: no line of its own above it (the card's head has it), the parchment a bit smaller */
.gm-ep__math .gm-mathset { border-top:0; margin-top:10px; padding-top:0; }
.gm-ep__math .gm-mathset__art { font-size:36px; padding:2px 14px 4px; }   /* the card slimmer, its hand as readable (Maayan) */
/* ONE HEIGHT, WHATEVER THE TAB (Maayan, 2026-10-24: "the height can't change from tab to tab"): the window keeps its
   height, the tab's body scrolls inside it */
.lb-box.gm-account { display:flex; flex-direction:column; height:min(760px, 84vh); overflow:hidden; }
.lb-box.gm-account > .boxhead, .lb-box.gm-account > .gm-tabs { flex:none; }
.lb-box.gm-account > .gm-tabs { margin-bottom:8px; }   /* with the body's 4px: the cards' 12px gap under the tabs too */
.gm-account__body { flex:1 1 auto; min-height:0; overflow:auto; overscroll-behavior:contain; padding-top:4px; }   /* with the head's own 8px: the gap between the cards, above the first too (Maayan) */
@media (max-width:680px){ .lb-box.gm-account { height:min(92dvh, 100%); } }
/* a line under Players and + Add Player (Maayan's mock) */
.gm-ep__cardhead { padding-bottom:12px; border-bottom:1px solid var(--edge, #d8c9a5); }
/* THE TABLE TAB'S ROWS AND ITS INVITE (2026-10-24): a role as a small tag, quiet actions, the QR a phone can read */
.gm-ep__tname { display:block; font-size:20px; }
.gm-ep__quiet { min-height:44px; padding:6px 4px; }
.gm-ep__danger, .gm-link.gm-ep__danger { align-self:flex-start; color:#a3271b; }
.gm-ep__bartitle { margin-inline-start:8px; font-size:18px; }
/* TABLE SETTINGS' Settings tab: its name's label over the field, Close and Delete under it. */
.gm-tset__name { display:flex; flex-direction:column; gap:8px; }
/* Present on TV, the growlmaster's: last of the ways in (app.js drawInvite). */
.gm-tset__tv { margin:4px 0 6px; }
.gm-tset__foot { display:flex; flex-direction:column; align-items:flex-start; margin-top:16px; }
/* THE COPY BUTTON (app.js copyIcon): its icon alone, a full tap's size; copied, the icon spins away and a ✓ pops in,
   and back after a few moments. */
.gm-copy { flex:none; position:relative; display:inline-grid; place-items:center; width:44px; height:44px; padding:0; margin:0;
  border:0; border-radius:50%; background:none; color:var(--ink2); cursor:pointer; -webkit-tap-highlight-color:transparent; }
.gm-copy:hover { color:var(--ink); background:var(--sheet2); }
.gm-copy:focus-visible { outline:3px solid var(--gold2); outline-offset:2px; }
.gm-copy__i { grid-area:1 / 1; display:grid; place-items:center; transition:opacity .18s ease, transform .4s cubic-bezier(.34,1.56,.64,1); }
.gm-copy__i svg { width:22px; height:22px; display:block; }
.gm-copy__i--done { color:var(--green); opacity:0; transform:scale(.3) rotate(-45deg); }
.gm-copy.is-copied .gm-copy__i--copy { opacity:0; transform:scale(.3) rotate(45deg); }
.gm-copy.is-copied .gm-copy__i--done { opacity:1; transform:none; }
/* A starter adventure updated (app.js checkStarterUpdates): its question, then its two answers. */
.gm-starterup__q { margin:4px 0 20px; font-size:18px; line-height:1.4; color:var(--ink); }
.gm-invite__qr:not([hidden]) { display:grid; place-items:center; margin-top:12px; }
.gm-invite__qr svg { width:220px; height:220px; padding:14px; background:#fff; border-radius:10px; }
.player-panel__stats-wrap { overflow-x:auto; -webkit-overflow-scrolling:touch; }
.player-panel__statstable { border-collapse:collapse; min-width:520px; width:100%; font-size:15px; }
.player-panel__statstable th, .player-panel__statstable td { padding:10px 12px; text-align:start; white-space:nowrap; border-bottom:1px solid var(--line, #d8c9a5); }
.player-panel__statstable th { font-size:13px; color:var(--ink2, #6f5c3c); font-weight:700; white-space:normal; vertical-align:bottom; }
.player-panel__statstable td:not(:first-child), .player-panel__statstable th:not(:first-child) { text-align:end; font-variant-numeric:tabular-nums; }
.player-panel__statstable tr.is-live td { font-weight:700; background:rgba(201,143,28,.12); }
.player-panel__now { display:inline-block; margin-inline-start:6px; padding:1px 8px; border-radius:999px; font-size:12px; background:#c98f1c; color:#fff; }
.player-panel__stats-none { padding:16px 4px; color:var(--ink2, #6f5c3c); }
.player-panel__foot { display:flex; gap:10px; justify-content:flex-end; padding-top:12px; border-top:2px solid var(--edge); background:var(--sheet); }
.player-panel__foot .gm-btn { min-height:48px; }
.player-panel[data-mode="view"] .player-panel__foot { display:none; }
.player-panel__card { display:flex; flex-direction:column; align-items:center; gap:10px; padding:4px 0 16px; }
.player-panel__pic { position:relative; width:150px; height:150px; }
.player-panel__pic .gm-avatar { width:100%; height:100%; font-size:56px; }
.player-panel__pen { position:absolute; top:-2px; inset-inline-end:-6px; width:48px; height:48px; padding:0; border-radius:50%; border:2px solid var(--edge); background:var(--sheet); color:var(--ink);
  display:grid; place-items:center; cursor:pointer; box-shadow:0 3px 8px rgba(0,0,0,.18); }
.player-panel__pen:hover, .player-panel__pen:focus-visible { border-color:var(--ink); }
.player-panel__pen svg { width:22px; height:22px; }
.player-panel__name { font-family:'Frank Ruhl Libre',serif; font-weight:900; font-size:26px; line-height:1.15; text-align:center; overflow-wrap:break-word; }
.player-panel__stats { display:flex; flex-wrap:wrap; align-items:center; justify-content:center; gap:12px 22px; }
.player-panel__stat { display:inline-flex; align-items:center; gap:6px; font-weight:800; font-size:18px; }
.player-panel__bag .gm-player__bagimg { width:48px; }
.player-panel__flame svg { display:block; width:42px; height:54px; }
.player-panel__flame--sm svg { width:22px; height:28px; }
.player-panel__streak b { font-size:34px; line-height:1; }
.player-panel__flames i { font-style:normal; color:var(--ink3); }
/* THE PLAYER, SIDE BY SIDE (Maayan, 2026-10-24): the picture, and next to it the name (a field), Score and Flames */
.player-panel__card--row { flex-direction:row; align-items:center; gap:18px; }
.player-panel__card--row .player-panel__pic { flex:none; width:120px; height:120px; }
.player-panel__who { flex:1; min-width:0; display:flex; flex-direction:column; gap:8px; }
.player-panel__card--row .player-panel__name { text-align:start; }
.player-panel__scores:not([hidden]) { display:flex; flex-wrap:wrap; align-items:center; gap:6px 18px; font-weight:800; font-size:18px; }
.player-panel__flames:not([hidden]) { display:inline-flex; align-items:center; gap:2px; }
.player-panel__h { display:block; margin:4px 0 10px; font-family:'Frank Ruhl Libre',serif; font-weight:800; font-size:19px; }
/* PLAYER DETAILS (Maayan's wireframe, 2026-10-24; studio.js playerPanel drawView): the picture and, beside it, the name
   and the record; a line; the round's start and Round history; Score and Flames; four boxes; the rewards. One element at
   every width: on a phone the top row and the boxes wrap, nothing shrinks below a thumb's size. */
.pd { display:flex; flex-direction:column; gap:16px; }
.pd__marks { display:flex; flex-wrap:wrap; gap:10px 28px; }
.pd__mark { display:inline-flex; align-items:center; gap:8px; font-size:20px; font-weight:800; }
.pd__icon { width:30px; height:30px; flex:none; }
.pd__round { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:8px 16px; font-size:16px; font-weight:700; }
.pd__link { min-height:44px; padding:0 4px; }
.pd__line { display:flex; flex-wrap:wrap; gap:8px 40px; font-size:22px; font-weight:800; }
.pd__line > span { display:inline-flex; align-items:center; gap:6px; }
.pd__boxes { display:grid; grid-template-columns:repeat(4, minmax(0, 1fr)); gap:12px; }
@media (max-width:560px) { .pd__boxes { grid-template-columns:repeat(2, minmax(0, 1fr)); } }
.pd__box { position:relative; display:flex; flex-direction:column; align-items:center; gap:6px; min-height:120px; text-align:center; }   /* the Account's card (gm-ep__card), its layout here */
.pd__boxi { position:absolute; top:8px; inset-inline-end:10px; }
.pd__boxi .pd__icon { width:28px; height:28px; }
.pd__n { font-size:36px; line-height:1; font-weight:800; }
.pd__boxw { font-size:15px; font-weight:700; line-height:1.25; }
.player-panel__rewards { display:grid; grid-template-columns:repeat(auto-fill, minmax(100px, 1fr)); gap:10px; }
/* a gift's die (dice.js giftFace): the ones still to win, faint */
.gm-reward { display:flex; flex-direction:column; align-items:center; gap:4px; padding:10px 6px 8px; border:2px solid var(--edge); background:var(--sheet2); text-align:center; }
.gm-reward__die { display:block; width:64px; height:64px; }
.gm-reward__die svg { display:block; width:100%; height:100%; overflow:visible; }
.gm-reward__bag { width:100%; }
.gm-reward b { font-size:14px; line-height:1.2; }
.gm-reward small { font-size:13px; color:var(--ink3); }
.gm-reward.is-locked { opacity:.45; filter:grayscale(.85); }
.gm-reward.is-won { border-color:var(--gold); }
/* the players on this device, in the game or out of it (Exit game / Enter game) */
.gm-family__inout { flex:none; min-height:44px; }
.gm-family li.is-out .gm-family__row { opacity:.5; }
.gm-pick__top, .gm-pick__foot { flex:none; }
.gm-pick__top .mini { margin:0 0 10px; }
.gm-pick__grid { flex:1 1 auto; min-height:0; overflow:auto; display:grid; gap:14px; padding:6px 4px 10px; overscroll-behavior:contain;
  align-content:start; grid-auto-rows:max-content; }
.gm-pick__grid--av { grid-template-columns:repeat(auto-fill, minmax(128px, 1fr)); }
.gm-pick__grid--bag { grid-template-columns:repeat(auto-fill, minmax(110px, 1fr)); }
@media (max-width:560px){ .gm-pick__grid--av { grid-template-columns:repeat(auto-fill, minmax(104px, 1fr)); } }
.gm-pick__it { display:grid; place-items:center; width:100%; height:auto; aspect-ratio:1 / 1; padding:5px; border:3px solid transparent; border-radius:50%; background:none; cursor:pointer; }
.gm-pick__grid--bag .gm-pick__it { border-radius:14px; background:rgba(0,0,0,.05); }
.gm-pick__it .gm-avatar { width:100%; height:100%; }
.gm-pick__it .gm-player__bagimg { width:72%; }
/* A BAG JUST WON (dice.js showGift, Maayan: "we show the bag transition adding to the grid. it's not selected"): it drops
   into its place in the grid, settles with a bounce, and glows a moment; never chosen by it */
.gm-pick__it.is-arriving { animation:gm-bag-land .9s cubic-bezier(.2,1.25,.4,1) both, gm-bag-glow 1.6s ease-out .7s both; }
@keyframes gm-bag-land { 0% { transform:translateY(-140%) scale(1.5); opacity:0; } 60% { opacity:1; } 100% { transform:none; opacity:1; } }
@keyframes gm-bag-glow { 0% { box-shadow:0 0 0 0 rgba(243,210,122,.9); } 100% { box-shadow:0 0 0 14px rgba(243,210,122,0); } }
@media (prefers-reduced-motion: reduce) { .gm-pick__it.is-arriving { animation:gm-bag-glow 1.6s ease-out both; } }
.gm-pick__it.on { border-color:var(--ink); }
.gm-pick__foot { padding-top:12px; border-top:2px solid var(--edge); display:flex; justify-content:flex-end; }
/* CHOOSE AN AVATAR (studio.js openAvatarPick): the grid scrolls between the head and the foot; at the foot's start the
   background's swatch (the player window's own, .gm-player__bgsw) and its words, at its end Save changes */
.gm-pick__gridwrap { flex:1 1 auto; min-height:0; display:flex; flex-direction:column; }
.gm-pick__foot--av { justify-content:space-between; align-items:center; gap:12px; }
.gm-pick__bg { display:inline-flex; align-items:center; gap:10px; min-height:48px; padding:0 4px; border:0; background:none; color:var(--ink); font:inherit; font-weight:700; cursor:pointer; }
.gm-pick__bgrow { display:inline-flex; align-items:center; gap:12px; }
.gm-pick__bgrow[hidden] { display:none; }
.gm-pick__bg.is-off { opacity:.4; cursor:default; }
.gm-pick__bg .gm-player__bgsw { margin:0; display:inline-block; box-sizing:border-box; }
/* its window: the chosen picture large, live, and the two sliders */
.gm-bgpick { display:flex; flex-direction:column; align-items:center; gap:18px; padding:8px 0 16px; }
.gm-bgpick__pv.gm-ep__face, .gm-bgpick__pv.gm-ep__face > * { width:180px; height:180px; }
.gm-bgpick__sl { align-self:stretch; }
.gm-bgpick__sl .slider { height:44px; border-radius:22px; margin-bottom:14px; }   /* a thumb's height on a phone */
.gm-acct__pic, .gm-family__pic { padding:0; border:0; background:none; cursor:pointer; border-radius:50%; }
/* A PLAYER ON THIS DEVICE (app.js openPlayer): their picture big in the middle (tap: the grid), the name, the bag. */
.gm-player { display:flex; flex-direction:column; gap:14px; }
.gm-player__av { align-self:center; width:120px; height:120px; padding:0; border:0; border-radius:50%; background:none; cursor:pointer; }
.gm-player__av .gm-avatar { width:100%; height:100%; }
.gm-player__f { display:flex; flex-direction:column; gap:6px; }
.gm-player__name { align-self:center; font-size:18px; color:var(--ink); }
/* its picture's background: a swatch under the picture; tapped, the two sliders (the dice's own) open under it */
.gm-player__bgsw { align-self:center; width:48px; height:48px; margin-top:-6px; padding:4px; border:2px solid var(--edge); border-radius:50%; background:#fff; cursor:pointer; }
.gm-player__bgsw[hidden] { display:none; }
.gm-player__bgsw span { display:block; width:100%; height:100%; border-radius:50%; }
.gm-player__bgsw[aria-expanded="true"] { border-color:var(--ink); }
.gm-player__bgsl[hidden] { display:none; }
/* a seat's face with its picture from the presets (TableCards, the header's faces) */
.gm-face .gm-avatar { width:100%; height:100%; border:0; }
.gm-player__f input { min-height:44px; font:inherit; font-size:17px; padding:0 12px; border:2px solid var(--edge); background:#fff; color:var(--ink); }
.gm-player__f--bag { align-items:flex-start; }
.gm-player__bag { display:grid; place-items:center; width:84px; height:84px; padding:6px; border:2px solid var(--edge); border-radius:14px; background:rgba(0,0,0,.05); cursor:pointer; }
.gm-player__bagimg { width:56px; }
.gm-family__row { display:flex; align-items:center; gap:10px; flex:1; min-height:48px; padding:4px 6px; border:0; background:none; font:inherit; color:var(--ink); text-align:start; cursor:pointer; }
.gm-family__row .gm-avatar { width:40px; height:40px; }
.gm-family__row .gm-player__bagimg { width:28px; margin-inline-start:auto; }
.gm-avatar b { font-size:13px; color:var(--ink); }
/* "+": the kinds to make a growl of, as cards with their badge colour. */
/* A growlmaster's profile: their picture (your own) and name, then Growls / Saved, then the grid. */
.gm-prof { display:flex; align-items:center; gap:12px; margin:18px 0 10px; }
/* FOLLOWING (Maayan, 2026-10-21; lib/growlmaster/follows.ts): how many new growls from the people they follow, on My
   Growls and on their Account, until they have looked; and the list of them in the Account window (studio.js). */
.gm-notices { min-width:20px; height:20px; padding:0 6px; box-sizing:border-box; border-radius:10px; background:var(--red); color:#fff; font-size:12px; font-weight:800; line-height:20px; text-align:center; pointer-events:none; }
.gm-bottombar__tab .gm-notices { position:absolute; top:0; inset-inline-start:calc(50% + 6px); }
@media (min-width:900px){ .gm-bottombar__tab .gm-notices { top:2px; inset-inline-start:36px; } }
.gm-prof__acct { position:relative; display:inline-flex; }
.gm-prof__acct .gm-notices { position:absolute; top:-8px; inset-inline-end:-8px; }
.gm-follows { margin-top:16px; padding-top:14px; border-top:2px solid var(--edge); }
.gm-follows__h { display:block; margin-bottom:10px; }
.gm-follows__list { list-style:none; margin:0; padding:0; display:grid; gap:8px; }
.gm-follows__list a { display:flex; align-items:center; gap:12px; min-height:52px; color:var(--ink); text-decoration:none; }
.gm-follows__list img, .gm-follows__list i { width:52px; height:52px; border-radius:8px; object-fit:cover; background:var(--sheet2); flex:none; }
.gm-follows__list span { display:flex; flex-direction:column; min-width:0; }
.gm-follows__list b { font-size:14px; color:var(--ink2); }
.gm-follows__list li.is-new b::after { content:""; display:inline-block; width:8px; height:8px; border-radius:50%; background:var(--red); margin-inline-start:6px; vertical-align:middle; }
.gm-prof .gm-avatar { width:56px; height:56px; }
.gm-prof .gm-avatar b { font-size:22px; }
.gm-outside .gm-prof__name { margin:0; font-family:'Frank Ruhl Libre',serif; font-size:24px; }
.gm-prof__tabs { display:flex; border-bottom:2px solid var(--edge); margin-bottom:12px; }
.gm-prof__tab { flex:1; padding:10px 6px; background:none; border:0; border-bottom:3px solid transparent; margin-bottom:-2px; font:inherit; font-weight:700; color:var(--ink3); cursor:pointer; }
.gm-prof__tab.is-on { color:var(--ink); border-bottom-color:var(--ink); }
.gm-prof__none { text-align:center; padding:28px 12px; }
/* My Growls: the hubs' tiles, a grid (three across, two on a phone). */
.gm-mine { max-width:900px; }
.gm-outside .gm-minebook { background:none; padding:0; }
.gm-minegrid { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:12px; }
.gm-minegrid > li { min-width:0; cursor:pointer; }
@media (max-width:560px){ .gm-outside__in.gm-mine { padding-inline:12px; } .gm-minegrid { grid-template-columns:repeat(2,minmax(0,1fr)); gap:8px; } }
/* A PROFILE, THE INSTAGRAM WAY (Maayan, 2026-10-23; ProfilePane.astro, GrowlSquare.astro): a big picture beside the
   name, @handle and counts; wide buttons; Growls | Saved; then squares edge to edge, three across on a phone too. */
.gm-outside__in.gm-ig { max-width:935px; padding-top:32px; }   /* Instagram's column; the lobby's 520 is the feed's */
.gm-ig__head { display:flex; align-items:center; gap:clamp(20px,6vw,72px); padding:0 clamp(0px,4vw,48px); }
.gm-ig__pic { flex:none; }
.gm-ig .gm-ig__pic .gm-avatar { width:clamp(88px,22vw,150px); height:clamp(88px,22vw,150px); border:3px solid var(--edge); box-shadow:0 3px 10px rgba(58,44,23,.18); }
.gm-ig .gm-ig__pic .gm-avatar b { font-size:clamp(34px,8vw,58px); }
.gm-ig .gm-prof__sack { right:-6px; bottom:-4px; width:clamp(34px,8vw,52px); }
.gm-ig__who { min-width:0; display:flex; flex-direction:column; gap:6px; }
.gm-outside .gm-ig__name { margin:0; font-family:'Frank Ruhl Libre',serif; font-size:clamp(24px,5.4vw,34px); line-height:1.1; color:var(--ink); overflow-wrap:anywhere; }
.gm-ig__handle { margin:0; font-size:16px; font-weight:700; color:var(--ink3); unicode-bidi:isolate; }
html[dir="rtl"] .gm-ig__handle { text-align:right; }
.gm-ig__counts { list-style:none; margin:6px 0 0; padding:0; display:flex; flex-wrap:wrap; gap:6px 22px; }
.gm-ig__counts li { display:flex; align-items:baseline; gap:6px; font-size:16px; color:var(--ink2); }
.gm-ig__counts b { font-size:18px; font-weight:800; color:var(--ink); }
.gm-ig__btns { display:flex; gap:8px; margin:20px 0 0; padding:0 clamp(0px,4vw,48px); }
.gm-ig__btns > .gm-btn, .gm-ig__btns > .gm-prof__acct { flex:1; }
/* A GUEST'S PROFILE (ProfilePane.astro): Log in and Create account under their name, the free credits under them; signing
   in, the one form in their place (lobby.js) */
.gm-prof--guest .gm-prof__auth { padding:0; margin-top:12px; }
.gm-prof--guest .gm-prof__auth .gm-btn { min-height:44px; padding:6px 14px; white-space:normal; line-height:1.15; }
.gm-prof__credits { margin:10px 0 0; }
.gm-prof__form { max-width:420px; margin:0 auto; padding:0 16px; }
/* A button at work (lobby.js busy): pressed and waiting, nothing more until it is done */
.gm-btn.is-busy { opacity:.7; cursor:progress; }
.gm-btn.is-busy .gm-btn__t::after { content:"…"; }
/* The feed that has nothing to show, or could not be read: its line and Try again (FeedList.astro) */
.gm-feed__none { display:grid; justify-items:center; gap:12px; padding:48px 16px; text-align:center; }
.gm-feed__none .gm-btn { min-height:44px; }
/* What went wrong under a lobby form, and Try again beside it */
.gm-lobby__retry { margin-inline-start:8px; min-height:44px; }
.gm-ig__btns .gm-btn { width:100%; min-height:44px; justify-content:center; margin:0; }
.gm-ig__tabs { display:flex; justify-content:center; gap:clamp(12px,8vw,64px); margin-top:24px; border-top:2px solid var(--edge); }
.gm-ig__tab { display:inline-flex; align-items:center; gap:8px; min-height:48px; padding:0 14px; margin-top:-2px; background:none; border:0; border-top:3px solid transparent;
  font:inherit; font-size:15px; font-weight:800; letter-spacing:.04em; text-transform:uppercase; color:var(--ink3); cursor:pointer; }
.gm-ig__tab[aria-selected="true"] { color:var(--ink); border-top-color:var(--ink); }
.gm-ig .gm-minebook { margin-top:2px; }
.gm-iggrid { list-style:none; margin:0; padding:0; display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:3px; }
.gm-iggrid > li { min-width:0; cursor:pointer; }
.gm-sq { position:relative; display:block; aspect-ratio:1; overflow:hidden; background:var(--sheet2); color:var(--ink); text-decoration:none; }
.gm-sq img { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; transition:transform .25s ease; }
.gm-sq--icon { display:grid; place-items:center; }
.gm-sq--cut img { object-fit:contain; padding:8%; box-sizing:border-box; background:radial-gradient(circle at 50% 60%, var(--sheet), var(--sheet2)); }
.gm-sq--icon i { font-style:normal; font-size:clamp(34px,9vw,64px); opacity:.75; }
.gm-sq__name { position:absolute; inset-inline:0; bottom:0; padding:22px 10px 8px; font-size:15px; font-weight:800; line-height:1.2; color:#fff;
  background:linear-gradient(transparent, rgba(20,14,6,.78)); text-shadow:0 1px 2px rgba(0,0,0,.6); opacity:0; transition:opacity .2s ease;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.gm-sq:hover img, .gm-sq:focus-visible img { transform:scale(1.04); }
.gm-sq:hover .gm-sq__name, .gm-sq:focus-visible .gm-sq__name { opacity:1; }
@media (hover:none){ .gm-sq__name { opacity:1; font-size:13px; padding:16px 6px 6px; } }
@media (max-width:560px){ .gm-outside__in.gm-ig { padding-inline:0; } .gm-ig__head, .gm-ig__btns { padding-inline:16px; } .gm-ig__counts { gap:4px 16px; } }
.gm-kinds { list-style:none; margin:8px 0 0; padding:0; display:grid; grid-template-columns:repeat(auto-fill,minmax(120px,1fr)); gap:10px; }
.gm-kinds__kind { width:100%; display:flex; flex-direction:column; align-items:center; gap:6px; padding:14px 8px; font:inherit; color:var(--ink); background:var(--sheet);
  border:2px solid var(--edge); border-top:4px solid var(--kind); cursor:pointer; }
.gm-kinds__kind:hover, .gm-kinds__kind:focus-visible { border-color:var(--ink); border-top-color:var(--kind); }
.gm-kinds__icon { font-size:34px; line-height:1; color:var(--ink3); }   /* the kind's line icon (snippet-card.js ICON_PATHS), in the nav's colour */
.gm-kind-ico { display:inline-block; vertical-align:middle; }
.lb.is-outside { height:100dvh; overflow:hidden; }
.lb.is-outside .lb-body { flex:1; min-height:0; }
.gm-outside__tables { width:100%; max-width:720px; margin-inline:auto; padding:8px 16px 14px; box-sizing:border-box; }
.gm-outside__tables .gm-outside__h { margin-top:12px; }
.gm-outside .gm-outside__feed { width:100%; max-width:none; }
.gm-outside__in { max-width:520px; margin:0 auto; padding:0 16px 40px; }
.gm-outside__feed .gm-outside__in { padding-top:24px; }   /* the first post has room above it (its title is gone) */
.gm-outside .gm-outside__h { font-family:'Frank Ruhl Libre',serif; font-size:24px; margin:32px auto 12px; }
/* The server's home: a heading keeps its line until the app gives it its words (no jump), and Your tables its room. */
.gm-outside__h[data-t]:empty::before { content:"\00a0"; }
/* Outside a table there is no board: its tabs, Present, Gorak and Settings go too (all a table's); Create a table and
   Join a table take their place in the header (app.js syncTableChip), and Account stays. */
.lb.is-outside .lb-mnav, .lb.is-outside .lb-rail, .lb.is-outside #aiToggleSlot { display:none; }
.lb.is-outside #gmTableSlot { display:flex; gap:8px; }
.gm-outside__list { list-style:none; margin:0; padding:0; display:flex; flex-wrap:wrap; gap:10px; }
.gm-outside__list > li { flex:0 0 auto; width:200px; max-width:100%; display:flex; }
.gm-outside__list > li > .gm-outside__table { height:100%; }   /* a row's cards are one height */
/* The Tables page: Create / Join, then the tables as a grid. */
/* Join, open on the page: its code and Join on one line. Create a table: the grid's first card, a +. */
.gm-joinline { margin-bottom:20px; }
.gm-joinline__h { display:block; font-family:'Frank Ruhl Libre',serif; font-weight:700; font-size:20px; margin-bottom:8px; color:var(--ink); }
.gm-joinline__row { display:flex; gap:10px; }
.gm-joinline__row input { flex:1; min-width:0; }
.gm-outside__table.gm-tablenew { align-items:center; justify-content:center; gap:6px; min-height:110px; border-style:dashed; text-align:center; }
.gm-tablenew__plus { font-size:34px; line-height:1; font-weight:400; color:var(--ink2); }
.gm-tablespane .gm-outside__list, .gm-prof__tables .gm-outside__list { display:grid; grid-template-columns:repeat(auto-fill,minmax(180px,1fr)); gap:12px; }
.gm-tablespane .gm-outside__list > li, .gm-prof__tables .gm-outside__list > li { width:auto; }
/* their tables on their own Profile (ProfilePane.astro), the lobby's cards */
.gm-prof__tables { margin:20px 0 0; padding:0 clamp(0px,4vw,48px); }
/* why they are in the lobby, once (LobbyTables.astro `why`) */
.gm-outside .gm-lobby__why { margin:0 0 18px; padding:12px 14px; border-radius:12px; background:rgba(255,233,136,.12); border:1px solid rgba(255,233,136,.35); color:var(--ink); font-size:16px; line-height:1.4; }
/* A phone: Your tables is one row of compact cards that scrolls sideways (Maayan), the page's edge to edge. */
@media (max-width:560px){
  .gm-outside__tables .gm-outside__list { flex-wrap:nowrap; overflow-x:auto; margin-inline:-16px; padding:2px 16px 6px; scroll-padding-inline:16px; scroll-snap-type:x proximity; scrollbar-width:none; }
  .gm-outside__tables .gm-outside__list::-webkit-scrollbar { display:none; }
  .gm-outside__tables .gm-outside__list > li { width:160px; scroll-snap-align:start; }
  .gm-outside__tables .gm-outside__table { padding:10px 12px; }
  .gm-outside__tables .gm-outside__name { font-size:16px; }
}
/* The Open GrowlMaster window: Create / Join, then every table (the same cards, a column). */
/* A card's players: the header's faces and count (app.js playersHtml), smaller, not a button of its own. */
.gm-players--card { padding:0; margin-top:4px; cursor:inherit; font-size:14px; }
.gm-players--card .gm-face { width:22px; height:22px; margin-inline-start:-8px; }
.gm-players--card .gm-face:first-child { margin-inline-start:0; }
.gm-players--card .gm-face b { font-size:10px; }
.gm-players--card .gm-players__word { display:inline; }
.gm-outside__table { width:100%; display:flex; flex-direction:column; align-items:flex-start; gap:4px; text-align:start; font:inherit; color:var(--ink);
  padding:14px; border:2px solid var(--edge); background:var(--sheet); cursor:pointer; }
.gm-outside__table:hover, .gm-outside__table:focus-visible { border-color:var(--ink); }
.gm-outside__name { font-weight:700; font-size:18px; overflow-wrap:anywhere; }
/* The Account window: their growlmaster name, then the full account settings. */
/* THE ACCOUNT WINDOW (app.js openAccount): the person on top, a line, then the settings. */
.gm-acct { display:flex; align-items:center; gap:18px; padding:6px 0 4px; }
.gm-acct__pic .gm-avatar { width:76px; height:76px; border-width:3px; border-color:var(--edge); }
.gm-acct__pic .gm-avatar b { font-size:32px; }
.gm-acct__pic .gm-avatar svg { width:36px; height:36px; }
.gm-acct__who { display:flex; flex-direction:column; gap:6px; min-width:0; flex:1; }
.gm-acct__name { font-family:'Frank Ruhl Libre',serif; font-size:24px; line-height:1.1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.gm-acct__credits { display:flex; align-items:center; flex-wrap:wrap; gap:10px; }
/* Get more credits at the far end of its row, away from the count (Maayan, 2026-10-20). */
.gm-acct__credits #acMore { margin-inline-start:auto; }
.gm-acct__n { font-weight:800; font-size:17px; }
.gm-acct__edit { color:var(--gold-dk); font-weight:700; text-decoration:underline; text-underline-offset:3px; }
.gm-acct--guest { flex-direction:column; text-align:center; gap:14px; padding:10px 0; }
.gm-acct__btns { display:flex; flex-wrap:wrap; justify-content:center; gap:10px; }
.gm-acct__sep { border:0; border-top:2px solid var(--edge); margin:16px 0 8px; }
/* The header's one button for the person: their picture in a circle (a visitor's, empty). */
/* the round picture that opens the account window, larger (Maayan, 2026-10-24): one button, every width */
.gm-acctbtn { display:grid; place-items:center; width:52px; height:52px; padding:0; border:0; background:none; cursor:pointer; border-radius:50%; }
.gm-acctbtn .gm-avatar { width:48px; height:48px; border:2px solid var(--edge); }
.gm-acctbtn:hover .gm-avatar, .gm-acctbtn:focus-visible .gm-avatar { border-color:var(--ink); }
/* A player's phone: the hero they play (chosen once), Watch only's note, and the Present tab. */
.gm-heropick { list-style:none; margin:8px 0 0; padding:0; display:grid; grid-template-columns:repeat(auto-fill,minmax(130px,1fr)); gap:10px; max-height:60vh; overflow:auto; }
.gm-heropick__hero { width:100%; display:flex; flex-direction:column; align-items:center; gap:4px; padding:10px; font:inherit; color:var(--ink);
  border:2px solid var(--edge); background:var(--sheet); cursor:pointer; text-align:center; }
.gm-heropick__hero:hover, .gm-heropick__hero:focus-visible { border-color:var(--ink); }
.gm-heropick__hero img { width:100%; aspect-ratio:1; object-fit:contain; }
.gm-heropick__nopic { font-size:48px; line-height:1.6; }
.gm-watchnote { position:absolute; inset:0; z-index:4; display:grid; place-items:center; padding:24px; text-align:center; font-size:18px; color:var(--ink2); background:var(--paper); }
.lb.party .gm-presenttab { font-weight:700; }
/* On a phone the presented thing stops above the tabs, so a player can step back to their hero (the TV has none). */
@media (max-width:820px){ html:not(.gm-tv):has(.lb.party .lb-mnav .gm-presenttab) .gm-pinstage { bottom:var(--gm-mnav-h,46px); } }
/* The home's words for a visitor, at the top of Home (HomeIntro.astro: its h1 and its line). */
.gm-homewords { max-width:720px; margin:0 auto; padding:16px 16px 0; box-sizing:border-box; }
.gm-homewords .gmhome__h1 { font-family:'Frank Ruhl Libre',serif; color:var(--ink); }
/* A growl opened in the app (app.js openGrowl): over the feed (or the table), its own scroller, ‹ back on top. */
/* --gm-growl-w: the growl's paper column, one width for the paper and Back's bar */
.gm-growlview { --gm-growl-w:720px; position:absolute; inset:0; z-index:8; display:flex; flex-direction:column; background:var(--paper); }
.lb #gmGrowl, .lb #gmMaker { display:none; }
/* the maker's Create growl and Save as draft: once its page is in its frame at its height, never jumping down under it */
#gmMaker:not([data-loaded]) .gm-createpane__go { visibility:hidden; }
.lb[data-window-state="growl_window_open"] #gmGrowl, .lb[data-window-state="growl_maker_window_open"] #gmMaker { display:flex; }
/* BACK ON THE PAPER'S EDGE (Maayan, 2026-10-24, his dashed line): Back's ‹ stands where the paper starts (and on a
   phone, where the paper is the whole width, 16px in); 13px is the small button's own border and padding, taken back
   so the ‹ itself is on the line. RTL mirrors it (inline). */
.gm-growlview__bar { flex:none; display:flex; align-items:center; padding-block:6px; padding-inline:calc(max(16px, (100% - var(--gm-growl-w)) / 2) - 13px);
  border-bottom:2px solid var(--edge); background:var(--sheet); }
.gm-growlview__back .gm-btn__i { font-size:22px; line-height:1; }
.gm-growlview__body { flex:1; min-height:0; overflow:auto; }
.gm-growlbook { max-width:var(--gm-growl-w); margin:0 auto; }
/* The sheet comes in the book's frame (.gmbook): in the app, without the public page's desk and header room. */
.gm-growlview .gmbook { background:none; padding:0 0 32px; }
.gm-growlbook > p.mini { padding:20px 16px; }
.lb:is([data-window-state="growl_window_open"],[data-window-state="growl_maker_window_open"]) { height:100dvh; overflow:hidden; }
.lb:is([data-window-state="growl_window_open"],[data-window-state="growl_maker_window_open"]) .lb-body { flex:1; min-height:0; }
/* The feed: the hubs' tiles (droptile.css) on the book's variables, without the book's desk around them. */
.gm-outside .gm-feedbook { background:none; padding:0; }
.gm-feed { list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:16px; }
.gm-feed > li { display:block; min-width:0; }
/* One post per row (Maayan, 2026-10-18, "like Instagram"): the narrow column, and the page's own edges on a phone. */
@media (max-width:560px){ .gm-outside__in { padding-inline:0; } .gm-outside__feed .gm-outside__h { margin-inline:16px; } .gm-feed { gap:12px; } }

/* The turn-undo list and the assistant's receipt. Both are readings of what
   happened, so they are quiet: no colour shouting, the tick and the cross carry
   the meaning. */
.turnundo { display:flex; flex-direction:column; gap:2px; margin:10px 0; }
.turnundo__row { display:flex; align-items:flex-start; gap:9px; padding:9px 10px; border:2px solid var(--edge);
  border-radius:10px; background:var(--paper); cursor:pointer; }
.turnundo__row:hover { border-color:var(--gold); }
.turnundo__row input { margin-top:3px; width:18px; height:18px; flex:0 0 auto; }
.turnundo__what { flex:1; min-width:0; }
.turnundo__warn { color:var(--ink3); display:block; }
.turnundo__hint { margin-bottom:4px; }
.turnundo__foot { display:flex; justify-content:flex-end; gap:8px; }
/* (the receipt is the shared panel's: assistant.css) */

/* ---- OBJECTS: only what objects ADD -----------------------------------------
   The list rows, the page shell, the card, the fields and the resistance table
   are all the shared components, so they have no rules here. What is left is the
   category heading inside the list and the label chips, because nothing else in
   the product has either.
   The editor's own rules are gone with the editor: an object is now a card built
   from the foe's parts, so it inherits moncard, fld, portrait and restab. */

/* A heading between groups of rows. A SIBLING of .librow inside .liblist, not a
   wrapper around them, so the flex column still governs every row exactly as it
   does on every other Library page. */
.lb .libtab .liblist__h { font-size:15px; font-weight:800; color:var(--ink);
  padding:6px 2px 0; margin-top:6px; }
.lb .libtab .liblist__h:first-child { margin-top:0; }
.lb .libtab .liblist__h--none { color:var(--ink3); font-style:italic; }

/* The label filter above the list. Chips rather than a select, because the answer
   is usually "these two" and a multi-select hides which ones are on. */
.lb .objfilter { display:flex; flex-wrap:wrap; gap:6px; margin:0 0 12px; }
.lb .objfilter__c { font:inherit; font-size:13px; line-height:1; cursor:pointer;
  padding:7px 11px; border-radius:999px; border:1px solid var(--edge);
  background:var(--sheet2); color:var(--ink2); }
.lb .objfilter__c:hover { border-color:var(--gold2); }
.lb .objfilter__c.is-on { background:var(--ink); border-color:var(--ink); color:var(--paper); }
.lb .objfilter__c--clear { background:none; border-style:dashed; }

/* Label chips on the card, with the input on the same line so adding one does not
   move the section. */
.lb .objlabels { display:flex; flex-wrap:wrap; gap:6px; align-items:center; }
.lb .objchip { display:inline-flex; align-items:center; gap:2px; font-size:13px;
  padding:3px 4px 3px 10px; border-radius:999px; background:var(--sheet3); color:var(--ink2); }
.lb .objlabels__in { flex:1 1 140px; min-width:120px; border:0; background:none; padding:4px; }
.lb .objlabels__in:focus { outline:none; }

/* ---- the signup offer, shown before the login box ---------------------------
   Built on .lb-box like every other overlay, so it is content height with no
   dead space. Only the two things this one adds have rules: the subtitle and the
   separation before the second way through. */
.lb .offer__sub { margin:-2px 0 14px; }
/* "I already have an account" is a BUTTON with room around it, not a link under
   the fold. A returning DM meets this every time a session lapses, and hunting
   for the way in is the whole complaint this modal exists to answer. */
.lb .offer__alt { margin-top:10px; padding-top:12px; border-top:1px solid var(--edge); }

/* A failed read of the game: nothing saves until it loads (see readFailed in app.js). */
.gm-readfail { position:fixed; z-index:9000; top:10px; left:50%; transform:translateX(-50%); display:flex; align-items:center; gap:12px;
  padding:8px 8px 8px 16px; background:#3a1a14; color:#fff; border-radius:10px; box-shadow:0 6px 20px rgba(0,0,0,.35); font-weight:600; }
.gm-readfail__retry { border:0; border-radius:8px; padding:8px 14px; background:#fff; color:#3a1a14; font:inherit; cursor:pointer; min-height:36px; }

/* The adventure's Library rows: a small, even picture; an icon on a dark tile so its white lines show. */
.lb .advlibrow .librow__pic { width:44px; height:44px; flex:0 0 44px; display:flex; align-items:center; justify-content:center; overflow:hidden; border-radius:6px; }
.lb .advlibrow .librow__pic img { width:100%; height:100%; object-fit:cover; }
.lb .advlibrow .librow__pic img.advlib__glyph { object-fit:contain; padding:6px; box-sizing:border-box; background:#865309; }

/* The language button on the gate, beside Gorak's greeting. */
.lb-gate__lang { position:fixed; z-index:67; /* above Gorak's conversation layer (.retro, 66) */ top:14px; inset-inline-end:14px; border:1px solid rgba(255,255,255,.4);
  background:rgba(20,12,6,.65); color:#fff; border-radius:999px; padding:8px 14px; font:600 15px/1 system-ui,sans-serif; cursor:pointer; min-height:40px; }
.lb-gate__lang:hover { background:rgba(20,12,6,.85); }

/* THE SEARCH BAR (app.js searchBar / bindSearch): one for every list and grid. */
:is(.lb, #lb-ovl) .gm-search { position: relative; display: flex; align-items: center; margin: 0 0 12px; max-width: 520px; }
:is(.lb, #lb-ovl) .gm-search__ico { position: absolute; inset-inline-start: 12px; width: 18px; height: 18px; fill: none; stroke: var(--ink2, #6b5a3e); stroke-width: 2; stroke-linecap: round; pointer-events: none; }
:is(.lb, #lb-ovl) .gm-search__in {
  flex: 1 1 auto; min-width: 0; height: 44px; box-sizing: border-box; padding: 0 40px 0 38px; padding-inline: 38px 40px;
  border: 2px solid var(--edge, #c9b98f); border-radius: 8px; background: var(--sheet2, #fbf6ea); color: var(--ink, #2b2118);
  font: 600 16px/1.2 "Grandstander", system-ui, sans-serif; -webkit-appearance: none; appearance: none;
}
:is(.lb, #lb-ovl) .gm-search__in::-webkit-search-cancel-button { display: none; }
:is(.lb, #lb-ovl) .gm-search__in::placeholder { color: var(--ink2, #6b5a3e); opacity: .75; }
:is(.lb, #lb-ovl) .gm-search__in:focus { outline: none; border-color: var(--accent, #865309); box-shadow: 0 0 0 2px rgba(134, 83, 9, .25); }
:is(.lb, #lb-ovl) .gm-search__x { position: absolute; inset-inline-end: 6px; width: 32px; height: 32px; border: 0; border-radius: 50%; background: transparent; color: var(--ink, #2b2118); font-size: 22px; line-height: 1; cursor: pointer; }
:is(.lb, #lb-ovl) .gm-search__x:hover { background: rgba(134, 83, 9, .12); }
:is(.lb, #lb-ovl) .gm-search__x[hidden] { display: none; }
:is(.lb, #lb-ovl) .gm-search__none { margin: 4px 2px 12px; }
:is(.lb, #lb-ovl) .gm-search__none[hidden] { display: none; }
:is(.lb, #lb-ovl) .gm-search-hide { display: none !important; }
@media (max-width: 560px) { :is(.lb, #lb-ovl) .gm-search { max-width: none; } }
/* Over a full-width table the bar keeps the page's gutter. */
.lb .monbody > .gm-search { margin: 14px 16px 10px; }
.lb .monbody > .gm-search__none, .lb .shop-scroll > .gm-search__none { margin-inline: 16px; }
/* An item whose picture is one of GrowlMaster's own icons (white line art) sits on
   the dark tile those icons use everywhere else (the Library list, the snippet card). */
.lb .equip-slot.has-item .equip-slot__pic:has(> img[src^="/growlmaster/icons/"]:not(.equip-slot__icon--png)) { background:#865309; }
.lb .equip-slot__pic > img[src^="/growlmaster/icons/"]:not(.equip-slot__icon--png) { object-fit:contain; padding:14%; box-sizing:border-box; }
/* A worn item with no picture of its own: its slot's ink icon on paper, darker than an empty slot's (Maayan: dark ink icons that read on paper). */
.lb .equip-slot.has-item .equip-slot__pic > svg.equip-slot__icon { color:var(--ink); opacity:1; }

/* THE PRESENT PIN (app.js "THE PRESENT PIN"): an eye on a needle, shut when not
   pinned and open when pinned (Ogre's icons, drawn in currentColor by a mask). */
.lb .gm-pin, .gm-pin { display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto; width:36px; height:36px; padding:0;
  border:2px solid var(--edge, #c9b98f); border-radius:8px; background:var(--paper, #fbf6ea); color:var(--ink2, #6f5c3c); cursor:pointer; }
.lb .gm-pin:hover { border-color:var(--gold2, #b08a3e); color:var(--ink, #3a2c17); }
.lb .gm-pin.is-on { background:#865309; border-color:#865309; color:#fff; }
.gm-pin__ico { display:block; width:22px; height:22px; background:currentColor;
  -webkit-mask:url(/growlmaster/icons/pin-closed.svg) center/contain no-repeat; mask:url(/growlmaster/icons/pin-closed.svg) center/contain no-repeat; }
.gm-pin__ico--open { -webkit-mask-image:url(/growlmaster/icons/pin-open.svg); mask-image:url(/growlmaster/icons/pin-open.svg); }
/* Both pins fetched with the page, off screen: a pin drawn later (the Blackboard's bar) has its icon in the same frame, never an empty box first. */
body::after { content:""; position:fixed; left:-10px; top:-10px; width:1px; height:1px; pointer-events:none;
  -webkit-mask:url(/growlmaster/icons/pin-closed.svg) 0 0/1px no-repeat, url(/growlmaster/icons/pin-open.svg) 0 0/1px no-repeat;
  mask:url(/growlmaster/icons/pin-closed.svg) 0 0/1px no-repeat, url(/growlmaster/icons/pin-open.svg) 0 0/1px no-repeat; background:transparent; }
/* On a picture: its corner. In a caption or a row: small, before the name. */
.lb .has-pin { position:relative; }
.lb .gm-pin--slot { position:absolute; top:6px; inset-inline-start:6px; z-index:3; width:34px; height:34px; box-shadow:0 1px 4px rgba(0,0,0,.25); }
.lb .gm-pin--cap, .lb .gm-pin--row { width:30px; height:30px; margin-inline-end:8px; vertical-align:middle; }
.lb .gm-pin--cap .gm-pin__ico, .lb .gm-pin--row .gm-pin__ico { width:18px; height:18px; }
.lb .mapbar__side .gm-pin { margin-inline-end:auto; }

/* The party's screen: black, slowly, then the pinned thing fades in over it. */
.gm-pinstage { position:fixed; inset:0; z-index:9000; pointer-events:none; }
.gm-pinstage[hidden] { display:none; }
.gm-pinstage__black { position:absolute; inset:0; background:#000; opacity:0; }
.gm-pinstage.is-black .gm-pinstage__black { opacity:1; }
.gm-pinstage.is-anim .gm-pinstage__black { transition:opacity 1.2s ease-in; }
.gm-pinstage[data-kind="image"], .gm-pinstage[data-kind="card"] { pointer-events:auto; }
.gm-pinstage__item { position:absolute; inset:0; display:flex; align-items:center; justify-content:center; padding:24px; box-sizing:border-box; opacity:0; }
.gm-pinstage__item.is-in { opacity:1; transition:opacity .9s ease-out; }
.gm-pinstage__img { max-width:100%; max-height:100%; object-fit:contain; }
.gm-pinstage__card { width:min(520px, 100%); max-height:100%; overflow:auto; }
/* A battle page's header never overlaps (Maayan's shot of a foe page): its buttons keep
   their size and go to a second line when the pane is narrow, and a long name ends in "…". */
.lb .montoolbar.btoolbar { flex-wrap:wrap; row-gap:8px; }
.lb .montoolbar.btoolbar > .gm-btn, .lb .montoolbar.btoolbar > button { flex:0 0 auto; }
.lb .montoolbar__title { flex:1 1 8em; min-width:0; overflow:hidden; text-overflow:ellipsis; }
/* A picture's pin in a list: its corner. */
.lb .gm-table__pic, .lb .librow__pic, .lb .mapthumb .th, .lb .monarchive__open { position:relative; }
.lb .gm-pin--thumb { position:absolute; top:3px; inset-inline-start:3px; z-index:2; width:26px; height:26px; border-width:1.5px; border-radius:6px; box-shadow:0 1px 3px rgba(0,0,0,.25); }
.lb .gm-pin--thumb .gm-pin__ico { width:16px; height:16px; }
/* In a row the pin has its own spot beside the picture (never over it), at least 32 px to tap. */
.lb .gm-pin--row { width:32px; height:32px; flex:0 0 32px; }
.lb .gm-pin--row .gm-pin__ico { width:18px; height:18px; }
.lb .librow > .gm-pin--row { align-self:center; margin-inline:0 4px; }
/* On a card's bigger picture: a corner badge, 32 px to tap. */
.lb .gm-pin--thumb { width:32px; height:32px; }
.lb .gm-pin--thumb .gm-pin__ico { width:18px; height:18px; }
/* In a table the pin sits above the row's ⋮, in the same column: no wider table, the picture clear. */
.lb .gm-table__t td.gm-table__menu:has(> .gm-pin--row) > * { display:flex; margin-inline-start:auto; }
.lb .gm-table__t td.gm-table__menu > .gm-pin--row { margin-block-end:4px; }

/* THE TV (app.js "THE TV"): nothing but what the Present Pin shows, large and
   centred on black; a calm screen when nothing is pinned. html.gm-tv, and
   data-tv names what fills it: "map" or "battle" (that page of the board),
   "stage" (a picture or a card, on #gmPinStage), or "idle". */
html.gm-tv, html.gm-tv body, html.gm-tv .site-content { background:#000 !important; overflow:hidden !important; }
html.gm-tv .lb-top, html.gm-tv .lb-rail, html.gm-tv .lb-mnav, html.gm-tv .lb-divider,
html.gm-tv .lb-tabsbar, html.gm-tv .jim-ai, html.gm-tv .lb-pane { display:none !important; }
html.gm-tv[data-tv="map"] .lb-pane[data-side="right"],
html.gm-tv[data-tv="battle"] .lb-pane[data-side="left"] { display:flex !important; position:fixed; inset:0; width:auto !important; max-width:none !important; z-index:50; background:#000; }
/* A map: the map alone, the whole of it (effectiveZoom fits it), no toolbars. */
html.gm-tv .maptoolbar, html.gm-tv .mapbar, html.gm-tv .mapside, html.gm-tv .maptoast, html.gm-tv .mapdoorhint-hold { display:none !important; }
html.gm-tv .lb-pane { border:0 !important; }
html.gm-tv .mapstack, html.gm-tv .mapbody { height:100%; }
html.gm-tv .mapview { background:#000; overflow:hidden; }
/* The pin stage over everything, the picture or card large. */
html.gm-tv .gm-pinstage__item { padding:4vh 4vw; }
html.gm-tv .gm-pinstage__img { width:100%; height:100%; object-fit:contain; }   /* up to the screen, not only its own size */
html.gm-tv .gm-pinstage__card { width:min(46vw, 900px); font-size:1.6em; }
/* Idle: the logo on black, breathing slowly. */
.gm-tvidle { position:fixed; inset:0; z-index:8999; background:#000; display:grid; place-items:center; }
.gm-tvidle[hidden] { display:none; }
.gm-tvidle img { width:min(36vw, 640px); height:auto; opacity:.8; animation:gmTvBreathe 6s ease-in-out infinite; }
@keyframes gmTvBreathe { 0%,100% { opacity:.55; transform:scale(.98); } 50% { opacity:.85; transform:scale(1); } }
@media (prefers-reduced-motion:reduce){ .gm-tvidle img { animation:none; } }
/* The laptop's TV window, until it is full screen: what to do, and the TV mode button. */
.gm-tvbar { position:fixed; left:50%; top:24px; transform:translateX(-50%); z-index:9100; display:flex; align-items:center; gap:14px;
  padding-block:10px; padding-inline:20px 10px; background:var(--paper, #fbf6ea); color:var(--ink, #3a2c17); border:2px solid var(--ink, #3a2c17);
  border-radius:999px; font:700 18px 'Grandstander', system-ui, sans-serif; box-shadow:0 8px 30px rgba(0,0,0,.5); max-width:calc(100% - 32px); box-sizing:border-box; }
.gm-tvbar[hidden] { display:none; }
/* Maayan: "tv mode button rounded corners 30px". */
#gmTvFull { border-radius:30px; }
/* The DM's Present on TV: the link big enough to type into a TV. */
.lb-box--tv .tvp__lead { margin:0 0 14px; line-height:1.45; }
.lb-box--tv .tvp__main { min-width:220px; }
.lb-box--tv .tvp__more { margin-top:20px; padding-top:12px; border-top:1.5px solid var(--edge); }
.lb-box--tv .tvp__more > summary { cursor:pointer; font-weight:700; color:var(--ink2); }
.lb-box--tv .tvp__more[open] > summary { margin-bottom:12px; }
.lb-box--tv .tvp__more .tvp__url { font-size:clamp(15px, 1.8vw, 20px); }
.lb-box--tv .tvp__on { font-weight:800; margin:4px 0 10px; }
.lb-box--tv .tvp__note { margin-top:10px; color:var(--ink2); }
.lb-box--tv .tvp__note[hidden] { display:none; }
.lb-box--tv .tvp__or { margin:18px 0 8px; }
.lb-box--tv .tvp__url { font:700 clamp(18px, 2.4vw, 28px)/1.3 ui-monospace, Menlo, monospace; word-break:break-all; padding:12px 14px;
  background:var(--sheet2); border:2px solid var(--edge); user-select:all; }
.lb-box--tv .tvp__code { margin-top:14px; }
/* The template just saved from a maker or item page ("Save in GrowlMaster"): it glows a moment in its Library list. */
.librow--flash { outline:3px solid var(--gold, #b98a2e); outline-offset:2px; border-radius:8px; animation:librowFlash 2.4s ease-out 1; }
@keyframes librowFlash { 0%, 40% { background:rgba(201,163,92,.35); } 100% { background:transparent; } }
/* Their growlmaster name in Settings (app.js bindGrowlmasterName): the field and its Save, side by side. */
.setrow--gmname .gmname { display:flex; gap:8px; align-items:center; flex:1 1 220px; min-width:0; }
.setrow--gmname .gmname input { flex:1 1 auto; min-width:0; font-size:17px; }
/* THE DICE over the board (app.js openDice, dice/dice.css): the board is their table. The dice button pressed while
   they are out; beside it, on a wide screen, Roll, Clear and the score (Maayan: "on mobile don't show them"). */
#split { position:relative; }
/* The dice button and its bar, joined (Maayan's mock): the button dark while the dice are out, the bar a box beside
   it that slides open. */
.gm-dicegroup { display:inline-flex; align-items:stretch; }
#gmDice.is-on { background:var(--ink); border-color:var(--ink); }
#gmDice.is-on .gm-dicebtn__i { filter:invert(1) brightness(1.6); }
.gm-dicebar { display:inline-flex; align-items:center; gap:6px; overflow:hidden; white-space:nowrap; max-width:0; padding:0; opacity:0;
  border:2px solid transparent; border-left:0; background:var(--sheet2,#efe6d2); transition:max-width .3s ease, padding .3s ease, opacity .2s ease, border-color .3s; }
.gm-dicegroup.is-on .gm-dicebar { max-width:440px; padding:0 8px; opacity:1; border-color:var(--edge); }
.gm-dicebar__score { display:inline-flex; align-items:center; gap:6px; height:32px; padding:0 10px; border:2px solid var(--edge); background:var(--sheet);
  font-weight:800; font-size:12px; letter-spacing:.06em; text-transform:uppercase; color:var(--ink2); }
.gm-dicebar__score b { font-family:'Frank Ruhl Libre',serif; font-size:19px; letter-spacing:0; color:var(--ink); }
@media (max-width:820px){ .gm-dicebar { display:none !important; } }
/* THE LOBBY IS DARK (Maayan, 2026-10-19: "growlmaster lobby needs to be dark bg so that when entering there will be a
   strong visual difference. the feed as dark feed will look more like a feed"; and, after trying parchment: "the dark
   mode is so much cooler… please bring it back"): the same palette names, night values, for everything outside a
   table (its header, its tabs, its pages, the feed's posts); a table stays parchment. */
.lb.is-outside {
  --paper:#14100b; --sheet:#1d1710; --sheet2:#241c13; --sheet3:#2e241a; --edge:#4a3b28; --line:#4a3b28;
  --ink:#f1e6cf; --ink2:#cdbb98; --ink3:#9c8a68;
  background:var(--paper); color:var(--ink);
}
.lb.is-outside .gmbook { --parch-hi:#1d1710; --parch:#14100b; --hair:#3a2e20; --burgundy:#f3d27a; color:#eadfc8; }
.lb.is-outside .gmbook .droptile--post .droptile__pic { background:#0b0806; }
.lb.is-outside .gmbook a.droptile:hover, .lb.is-outside .gmbook a.droptile:focus-visible { background:#241c13; }
/* The Add button on a dark post: GrowlMaster's red, as on parchment (the dark --burgundy is the words' gold). */
.lb.is-outside .gmbook .gadd { background:#8b2f22; border-color:#8b2f22; color:#fff; }
.lb.is-outside .gmbook .gadd:hover:not(:disabled), .lb.is-outside .gmbook .gadd:focus-visible { background:#a3382a; }
.lb.is-outside .gmbook .gadd.is-on { background:transparent; color:#f3d27a; }
/* TAPPED, A THING TURNS PARCHMENT (Maayan): the lobby's tabs, cards and buttons, no blue ring. */
.lb.is-outside :is(.gm-bottombar__tab, .gm-kinds__kind, .gm-prof__tab):is(:active, :focus-visible) { background:#efe7d3; color:#3a2c17; outline:none; }
.lb.is-outside :is(.gm-bottombar__tab, .gm-outside__table, .gm-kinds__kind, .gm-prof__tab):focus { outline:none; }
/* THE LOBBY'S TABLES PAGE (LobbyTables.astro, Maayan's mock): its title, its welcome, Create and Join side by side,
   then Your tables. */
.gm-outside .gm-lobby { box-sizing:border-box; text-align:center; }
.gm-lobby__main { max-width:720px; margin:0 auto; padding:28px 24px 56px; box-sizing:border-box; }
.gm-lobby__art { margin:0; overflow:hidden; position:relative; }
/* THE language menu over the picture's top corner (LobbyTables.astro; Maayan, 2026-10-20), the lobby's dark. */
/* the corner: the language, then Log in for a guest (mirrored right to left by the page's own direction) */
.gm-lobby__corner { position:absolute; top:14px; inset-inline-end:14px; z-index:2; display:flex; align-items:center; gap:8px; }
/* its colours (Maayan, 2026-10-23): #ffe988 with a 1px #fff1b6 edge, dark words on it */
.gm-lobby__login { min-height:44px; padding:8px 16px; border-radius:999px; font:inherit; font-weight:700; cursor:pointer;
  background:#ffe988; color:#2a1f0c; border:1px solid #fff1b6; }
.gm-lobby__login:hover { background:#fff1b6; }
.gm-lobby__login:focus-visible { outline:3px solid var(--ink2); outline-offset:2px; }
.gm-lobby__art .gm-lobby__lang {
  --lp-bg:var(--sheet); --lp-ink:var(--ink); --lp-line:var(--edge); --lp-hover:var(--sheet3); --lp-on:var(--sheet2); --lp-accent:var(--ink2); }
.gm-lobby__lang .lp__flag { background:color-mix(in srgb, var(--paper) 78%, transparent); color:var(--ink); border:1px solid var(--edge); }
.gm-lobby__art img { display:block; width:100%; height:100%; object-fit:cover; --py:50%; animation:gmLobbyPan 90s ease-in-out infinite; }
/* Where the picture is cropped, it travels very slowly to its right edge, then its left, and again (Maayan); where it
   isn't, moving its position does nothing. */
@keyframes gmLobbyPan { 0% { object-position:50% var(--py); } 25% { object-position:100% var(--py); } 75% { object-position:0% var(--py); } 100% { object-position:50% var(--py); } }
@media (prefers-reduced-motion: reduce){ .gm-lobby__art img { animation:none; } }
/* the forms side by side, no boxes: a line between them and one under them (Maayan's mock) */
.gm-lobby__boxes { gap:0 !important; border-bottom:2px solid var(--edge); padding-bottom:22px; }
.gm-lobby__box { border:0 !important; background:none !important; padding:6px 28px 0 !important; }
.gm-lobby__box + .gm-lobby__box { border-left:2px solid var(--edge) !important; }
.gm-lobby__box { min-width:0; }
.gm-lobby__box input { width:100%; min-width:0; box-sizing:border-box; }
/* WIDE: the words beside the left panel, the picture filling the right, as tall as the screen */
@media (min-width:1200px){
  .gm-outside .gm-lobby { display:grid; grid-template-columns:minmax(520px,1fr) minmax(0,1fr); min-height:100%; }
  .gm-lobby__art { order:2; position:sticky; top:0; height:100svh; }
  .gm-lobby__main { order:1; width:100%; padding-top:48px; }
  .gm-outside .gm-lobby .gm-lobby__yours { text-align:start; }
  .gm-lobby .gm-lobby__list .gm-outside__list { justify-content:start; }
}
/* A MIDDLE WIDTH: the picture a band across the top */
@media (max-width:1199px){
  .gm-lobby__art { height:220px; }
  .gm-lobby__art img { --py:55%; }
}
@media (max-width:560px){ .gm-lobby__art { height:150px; } }
.gm-outside .gm-lobby__h { margin:8px 0 10px; font-family:'Frank Ruhl Libre',serif; font-weight:900; font-size:clamp(34px,6vw,54px); line-height:1.05; color:var(--ink); }
.gm-outside .gm-lobby__sub { margin:0 0 30px; font-family:'Frank Ruhl Libre',serif; font-size:20px; line-height:1.45; color:var(--ink2); }
.gm-lobby__boxes { display:grid; grid-template-columns:1fr 1fr; gap:16px; }
.gm-lobby__box { display:flex; flex-direction:column; gap:12px; padding:20px 22px 22px; border:2px solid var(--edge); background:var(--sheet); text-align:center; }
.gm-lobby__box input { font:inherit; font-size:17px; padding:10px 12px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); text-align:center; min-height:46px; }
.gm-lobby__box .gm-btn { min-height:46px; }
/* THE SIGN-IN STATE (LobbyTables.astro auth; studio.js loginForm, the login window's own form): the form where the
   logo and Create a table are, words left-aligned as in Catbears' auth pages (Maayan's reference), Join a table under it. */
.gm-lobby__auth { max-width:440px; margin:0 auto 28px; text-align:start; }
.gm-outside .gm-lobby__auth .lpage__title { margin:0 0 10px; font-family:'Frank Ruhl Libre',serif; font-weight:900; font-size:clamp(30px,5vw,40px); line-height:1.1; color:var(--ink); }
.gm-lobby__auth .ltoggle--top { margin:0 0 6px; text-align:start; font-size:16px; }
.gm-lobby__auth .lsay { margin:0 0 18px; color:var(--ink2); font-size:15px; }
:is(.lb-box, .gm-lobby__auth) .lform { margin-top:16px; }   /* the window's too: "New here?" never on the Email field */
.gm-lobby__auth .lform .lfield input { min-height:50px; box-sizing:border-box; border-radius:10px; padding:12px 14px; }
:is(.lb-box, .gm-lobby__auth) .lcheck { display:flex; align-items:center; gap:10px; min-height:44px; font-size:15px; color:var(--ink); cursor:pointer; }
:is(.lb-box, .gm-lobby__auth) .lcheck input { width:22px; height:22px; margin:0; accent-color:var(--gold); flex:none; }
/* unticked when an account was asked for: the box and its words red (studio.js requireAdult) */
:is(.lb-box, .gm-lobby__auth) .lcheck.is-bad { color:var(--red); }
/* Create your account: their picture, big, and Change profile image beside it (studio.js loginForm) */
.lavatar { display:flex; align-items:center; gap:16px; margin:4px 0 16px; }
.lavatar__pic .gm-avatar { width:76px; height:76px; border:3px solid var(--edge); }
.lavatar .gm-link { min-height:44px; font-size:16px; }
:is(.lb-box, .gm-lobby__auth) .lcheck.is-bad input { outline:2px solid var(--red); outline-offset:2px; }
/* the window's too (Maayan, 2026-10-26: "Forgot your password?" sat on the Log in button): its own touch height, then room */
:is(.lb-box, .gm-lobby__auth) .lforgot { display:inline-flex; align-items:center; min-height:44px; margin:0 0 8px; }
.gm-lobby__auth .gm-btn { width:100%; min-height:50px; }
:is(.lb-box, .gm-lobby__auth) .lterms, :is(.lb-box, .gm-lobby__auth) .lhelp { margin:16px 0 0; font-size:14px; color:var(--ink2); }
.gm-outside .gm-lobby__yours { margin:32px 0 16px; padding-bottom:0; border-bottom:0; font-family:'Frank Ruhl Libre',serif; font-weight:700; font-size:22px; text-align:center; color:var(--ink); }
/* Everything on it centred (Maayan), the cards too. */
.gm-lobby__list { text-align:start; }
.gm-lobby__list .gm-outside__list { justify-content:center; grid-template-columns:repeat(auto-fit,minmax(180px,220px)); }
/* A form's title is a title; Back belongs to a form opened as a page (a phone). */
.gm-lobby .gm-lobby__boxh { all:unset; display:block; margin:0; font-family:'Frank Ruhl Libre',serif; font-weight:700; font-size:22px; line-height:1.3; color:var(--ink); text-align:center; cursor:default; }
.gm-lobby__back { display:none; }
/* A PHONE (Maayan, 2026-10-20): the picture a band on top, a smaller title, and each form only its title, as a button;
   tapped, that same form becomes a page of its own sliding in from the right (Back, no tabs). */
@media (max-width:560px){
  .gm-lobby__main { padding:16px 16px 40px; }
  .gm-outside .gm-lobby__h { font-size:30px; margin:2px 0 6px; }
  .gm-outside .gm-lobby__sub { font-size:16px; line-height:1.4; margin:0 0 18px; }
  .gm-lobby__boxes { display:flex !important; flex-direction:column; gap:10px !important; border-bottom:0; padding-bottom:0; }
  .gm-lobby__box { padding:0 !important; gap:12px; }
  .gm-lobby__box + .gm-lobby__box { border-left:0 !important; }
  /* a form's own page while it is open (create_table_…, join_table_…); else only its title, as its button */
  :is(.lb:not(:is([data-screen-state="create_table_form_open"],[data-screen-state="create_table_button_busy"],[data-screen-state="create_table_failed_try_again"])) .gm-lobby__box--create, .lb:not(:is([data-screen-state="join_table_form_open"],[data-screen-state="join_table_button_busy"],[data-screen-state="join_table_failed_try_again"])) .gm-lobby__box--join) > :not(.gm-lobby__boxh) { display:none; }
  .gm-lobby :is(.lb:not(:is([data-screen-state="create_table_form_open"],[data-screen-state="create_table_button_busy"],[data-screen-state="create_table_failed_try_again"])) .gm-lobby__box--create, .lb:not(:is([data-screen-state="join_table_form_open"],[data-screen-state="join_table_button_busy"],[data-screen-state="join_table_failed_try_again"])) .gm-lobby__box--join) .gm-lobby__boxh { box-sizing:border-box; width:100%; min-height:52px; display:flex; align-items:center; justify-content:center; font-family:'Grandstander',system-ui,sans-serif;
    font-size:18px; font-weight:800; border:2px solid var(--edge); color:var(--ink); cursor:pointer; }
  .gm-lobby .lb:not(:is([data-screen-state="create_table_form_open"],[data-screen-state="create_table_button_busy"],[data-screen-state="create_table_failed_try_again"])) .gm-lobby__box--create .gm-lobby__boxh { background:var(--gold); border-color:var(--gold); color:#fff; }
  .lb:not(:is([data-screen-state="create_table_form_open"],[data-screen-state="create_table_button_busy"],[data-screen-state="create_table_failed_try_again"])) .gm-lobby__box--create .gm-lobby__boxh::before { content:"+\00a0"; }
  :is(.lb:is([data-screen-state="create_table_form_open"],[data-screen-state="create_table_button_busy"],[data-screen-state="create_table_failed_try_again"]) .gm-lobby__box--create, .lb:is([data-screen-state="join_table_form_open"],[data-screen-state="join_table_button_busy"],[data-screen-state="join_table_failed_try_again"]) .gm-lobby__box--join) { position:fixed; inset:0; z-index:30; overflow:auto; padding:max(10px,env(safe-area-inset-top,0px)) 16px 24px !important;
    background:var(--paper) !important; animation:gmLobbyIn .3s cubic-bezier(.3,.7,.2,1); }
  :is(.lb:is([data-screen-state="create_table_form_open"],[data-screen-state="create_table_button_busy"],[data-screen-state="create_table_failed_try_again"]) .gm-lobby__box--create, .lb:is([data-screen-state="join_table_form_open"],[data-screen-state="join_table_button_busy"],[data-screen-state="join_table_failed_try_again"]) .gm-lobby__box--join) .gm-lobby__back { display:block; width:48px; height:48px; border:2px solid var(--edge); background:none; color:var(--ink); font-size:28px; line-height:1; cursor:pointer; }
  .lb:is([data-screen-state^="create_table_"],[data-screen-state^="join_table_"]) .gm-bottombar { display:none; }
  .gm-outside .gm-lobby__yours { margin:28px 0 12px; }
  .gm-lobby__list .gm-outside__list { grid-template-columns:1fr; }
}
@keyframes gmLobbyIn { from { translate:100% 0; } to { translate:0 0; } }
.gm-outside > .gm-outside__pane { position:relative; z-index:1; }
.gm-outside > .gm-bottombar { position:relative; z-index:2; }
/* the wide screen's left panel lies over the pages (its rule above) */
@media (min-width:900px){ .gm-outside > .gm-bottombar { position:absolute; z-index:3; } }
/* …so every lobby page starts beside it, once for all of them: the panel's width, then the page centred in the rest. */
@media (min-width:900px){ .gm-outside > .gm-outside__pane { padding-left:220px; box-sizing:border-box; } }
/* CREATE A GROWL, a page of the lobby (app.js openCreate): the kinds as a grid, then a kind's own maker page in it and,
   under it, Create growl (big) and Save as draft. */
.gm-createpane { max-width:900px; margin:0 auto; padding:24px 16px 56px; box-sizing:border-box; }
.gm-createpane .gm-lobby__h { text-align:center; }
/* Three across on a desk and in between, two on a phone (Maayan, 2026-10-20), in one rule: a column is never narrower
   than a third of the row (so never more than three) nor than 150px (so two where three would be smaller). */
.gm-createpane .gm-kinds { grid-template-columns:repeat(auto-fill,minmax(max(150px,calc((100% - 20px) / 3)),1fr)); }
.gm-createpane--make { max-width:1100px; padding-top:8px; }
.gm-createpane__maker, #mkFrame { display:block; width:100%; min-height:400px; border:0 !important; outline:0; background:transparent; color-scheme:normal; }
.gm-createpane__go { display:flex; flex-wrap:wrap; justify-content:center; align-items:center; gap:12px; margin-top:18px; }
.gm-createpane__go .gm-createpane__create { min-height:56px; padding:0 40px; font-size:19px; }
.gm-createpane__go .gm-tablename__err { flex-basis:100%; text-align:center; }
/* A GROWL OPENED IN THE LOBBY: THE PAGE ON PARCHMENT, THE ROOM AROUND IT THE LOBBY'S DARK (Maayan, 2026-10-19:
   "tapping on a growl - background parchment"; 2026-10-20: the Back bar and the area around the growl in the lobby's
   dark). The view and its Back bar keep the lobby's own variables (.lb.is-outside); only the growl's column takes
   GrowlMaster's day colours, as on its own page, with room between the bar and the page. */
.lb.is-outside .gm-growlview .gm-growlbook { --paper:#efe7d3; --sheet:#faf5e8; --sheet2:#f2ead6; --sheet3:#e7dcc0; --edge:#cbb98f; --line:#b7a473;
  --ink:#3a2c17; --ink2:#6f5c3c; --ink3:#8f7c56; color:var(--ink); padding-top:20px; }
.lb.is-outside .gm-growlview .gmbook { --parch:#f4eddc; --parch-hi:#faf5e8; --hair:rgba(34,28,20,.18); --burgundy:#8b2f22; color:inherit; }
.lb.is-outside .gm-growlview .gmbook .gadd.is-on { color:#8b2f22; }
/* A table card (app.js tablesListHtml): room around its words, the owner's ⋮ in its corner. */
.gm-outside__card { position:relative; }
/* No outline on a table card (Maayan, 2026-10-20): the paper shows it; a keyboard still sees where it is. Its Open
   table at the foot, lit with the card. */
.gm-outside__card > .gm-outside__table { border:0; }
.gm-outside__card > .gm-outside__table:focus-visible { box-shadow:0 0 0 2px var(--ink); }
.gm-outside__open { margin-top:auto; align-self:flex-start; pointer-events:none; }
.gm-outside__card > .gm-outside__table:hover .gm-outside__open { --bg:var(--gold-dk); --b:var(--gold-dk); }
/* THE CARD, SEEN AND CALM (Maayan, 2026-10-25): the Profile's cards' paper and edge (the lobby's --sheet2, --edge), room
   inside it and between its lines, its table icon (the Tables tab's, TABLE_ICON) before its name; a tap never lights
   the whole card, only its Open table presses (a shade darker, a pixel down). */
.gm-lobby__list .gm-outside__card > .gm-outside__table { padding:22px; gap:14px; min-height:96px; background:var(--sheet2); border:1.5px solid var(--edge);
  -webkit-tap-highlight-color:transparent; }
.gm-lobby__list .gm-outside__card > .gm-outside__table:is(:active, :focus) { background:var(--sheet2); color:var(--ink); outline:none; }
.gm-lobby__list .gm-outside__card > .gm-outside__table:focus-visible { box-shadow:0 0 0 2px var(--ink); }
@media (hover:hover){ .gm-lobby__list .gm-outside__card > .gm-outside__table:hover { border-color:var(--ink3); } }
.gm-outside__table:active .gm-outside__open { --bg:var(--gold-dk); --b:var(--gold-dk); transform:translateY(1px); }
.gm-outside__head { display:flex; align-items:center; gap:10px; min-width:0; padding-inline-end:36px; }   /* room for the ⋮ */
.gm-outside__icon { flex:none; display:inline-flex; color:var(--ink2); }
/* two cards a row (narrow cards): the icon above the name, so the name has the card's width */
@media (min-width:561px){ .gm-lobby__list .gm-outside__head { flex-direction:column; align-items:flex-start; gap:8px; } }
/* beside the icon, a name wraps between its words, never inside one (a word longer than the line still breaks) */
.gm-outside__head .gm-outside__name { min-width:0; overflow-wrap:break-word; }
.gm-outside__icon svg { width:30px; height:30px; display:block; }
/* THE TABLE CARD, Maayan's mock (2026-10-24): two a row, one on a phone, every card of a row one height; its name top
   start with room for the ⋮ at the top end (plain, a real touch size); its players on a darker pill; Open table across
   its foot. */
.gm-lobby__list .gm-outside__list { display:grid; grid-template-columns:repeat(2, minmax(0, 1fr)); justify-content:stretch; gap:12px; }
@media (max-width:560px){ .gm-lobby__list .gm-outside__list { grid-template-columns:1fr; } }
.gm-lobby__list .gm-outside__list > li { width:auto; display:flex; }
.gm-lobby__list .gm-outside__table { gap:10px; padding:18px 20px 20px; min-height:0; }
.gm-outside__card .gm-outside__name { padding-inline-end:40px; font-size:20px; }
.gm-outside__by { margin-top:-6px; }
.gm-players--card.gm-players { align-self:flex-start; padding-block:6px; padding-inline:8px 12px; border-radius:6px; background:rgba(0,0,0,.18); }
.gm-outside__card .gm-outside__open { align-self:stretch; width:100%; justify-content:center; min-height:52px; font-size:18px; margin-top:auto; }
.gm-outside__kebab, .jim-app .gm-outside__kebab { position:absolute; top:10px; inset-inline-end:8px; width:44px; height:44px; display:grid; place-items:center; padding:0;
  border:0; border-radius:10px; background:none; color:var(--ink2); font-size:28px; font-weight:900; line-height:1; cursor:pointer; }
.gm-outside__kebab:hover, .gm-outside__kebab:focus-visible { color:var(--ink); background:rgba(0,0,0,.14); }
.gm-tablemenu { justify-content:center; gap:10px; }
/* THE GATE'S CURTAIN, the first part of gate_closed_* (Growlboard's, BoardApp.astro .gb-intro): black, GrowlMaster's
   logo fading in big and slowly growing (2.1s); then the black lifts off the gate (.9s) as the logo glides to its own
   place on it (app.js startGate, at the logo's animationend); the gate's own logo shows once the black is gone. */
.lb-gate { --curtain-logo:.8s; --curtain-lift:.4s; }
/* gate_closed_waiting_for_enter_tap: no curtain, Enter is there to tap from the first paint (no wait before it) */
:root[data-screen-state="gate_closed_waiting_for_enter_tap"] .lb-gate__intro { display:none; }
.lb-gate__intro { position:absolute; inset:0; z-index:70; display:grid; place-items:center; background-color:#000; pointer-events:none;
  animation:gmCurtainLift var(--curtain-lift) ease var(--curtain-logo) both; }
@keyframes gmCurtainLift { from { background-color:#000; } to { background-color:rgba(0,0,0,0); } }
.lb-gate__introLogo { width:min(78vw,640px); height:auto; opacity:0; transform:scale(.82); transform-origin:50% 50%;
  animation:gmGateLogo var(--curtain-logo) ease-out both; }
@keyframes gmGateLogo { 0% { opacity:0; transform:scale(.82); } 22% { opacity:1; } 100% { opacity:1; transform:scale(1.22); } }
:root[data-screen-state="gate_closed_gorak_welcomes_first_visit"] .lb-gate__logo { animation:gmShownAfterCurtain calc(var(--curtain-logo) + var(--curtain-lift)) step-end both; }
@keyframes gmShownAfterCurtain { from { opacity:0; } to { opacity:1; } }

/* Players on this device (app.js drawFamily), in the Account window. */
/* their picture and name in GrowlMaster, and that they show there only (studio.js accountHtml) */
.gm-acct__gm { display:flex; flex-wrap:wrap; gap:8px; margin-top:12px; }
.gm-acct__gm .gm-btn { min-height:44px; margin:0; }
.gm-acct__gmnote { flex-basis:100%; margin:2px 0 0; }
.gm-gmname__err { margin:6px 0 0; color:var(--red, #B32D1F); font-weight:700; }
.gm-family { margin-top:14px; padding-top:14px; border-top:2px solid var(--edge); }
/* You do the math, in the Account window under the players (studio.js mathHtml): over it a sum on a strip of parchment,
   in one line, in the math card's hand (math.css .msum: Caveat), tilted a little, so it is found at once. */
.gm-mathset { margin-top:14px; padding-top:14px; border-top:2px solid var(--edge); }
/* the parchment at the start (Maayan, 2026-10-23): left, and right in Hebrew (its own numbers stay left to right) */
.gm-mathset__art { direction:ltr; width:max-content; max-width:100%; margin:2px auto 10px 0; padding:6px 22px 8px; rotate:-2deg;
  background:linear-gradient(180deg, #fbf3dd, #f1e2bd); border:2px solid var(--edge); border-radius:3px 9px 4px 8px / 8px 3px 9px 4px;
  box-shadow:0 4px 12px rgba(0,0,0,.1), inset 0 0 14px rgba(150,110,40,.14);
  font-family:'Caveat', cursive; font-weight:700; font-size:38px; line-height:1.05; color:#2a2a3a; white-space:nowrap; }
[dir="rtl"] .gm-mathset__art { margin-left:auto; margin-right:0; }
/* on its parchment, whatever the page (a dark one too): dark ink, the ? a deep gold */
.gm-mathset__art { color:#2a2a3a; }
.gm-mathset__art b { color:#8a5a0c; font-weight:700; }
.gm-mathset .setrow { display:flex; align-items:center; justify-content:space-between; gap:12px; padding:10px 0; border:0; }
.gm-mathset .setcopy { min-width:0; }
.gm-mathset__who { padding:6px 0 4px; }
.gm-mathset__h { display:block; margin:0 0 8px; font-weight:800; font-size:16px; }

/* THE TOGGLE WITH WORDS (studio.js segmented, GMSegmented): the switch's own track and thumb (.gm-switch), with words;
   the thumb is placed by the script (its place and its width, on a spring). */
.gm-seg { position:relative; display:inline-flex; max-width:100%; padding:3px; border-radius:999px; isolation:isolate;
  background:var(--edge, #cbb98f); border:2px solid var(--line, #b7a473); box-sizing:border-box; vertical-align:middle;
  overflow:hidden;   /* the thumb's bounce stays inside the track */
  font-family:'Grandstander',system-ui,sans-serif; transition:opacity .18s ease; }
.gm-seg__thumb { position:absolute; top:3px; bottom:3px; left:0; width:0; z-index:0; border-radius:999px; background:#fff;
  box-shadow:0 1px 3px rgba(58,44,23,.35); will-change:transform, width; }
.gm-seg__opt { position:relative; z-index:1; flex:0 1 auto; min-width:0; min-height:40px; padding:8px 18px; border:0; border-radius:999px;
  background:none; font:inherit; font-weight:800; font-size:15px; line-height:1.15; color:var(--ink, #3a2c17); opacity:1;
  cursor:pointer; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; transition:opacity .18s ease; -webkit-tap-highlight-color:transparent; }
/* READABLE ON ANY PAGE (Maayan, 2026-10-24: "dark mode needs text readable here"): the chosen one's words are dark,
   always, on its white thumb; the others the page's own ink on the track */
.gm-seg__opt[aria-checked="true"] { opacity:1; color:#2a1f0c; }
.gm-seg__opt:hover:not(:disabled) { opacity:1; }
.gm-seg__opt:focus-visible { outline:2px solid var(--gold2, #a8935f); outline-offset:-2px; }
/* one that cannot be chosen: fainter, not tappable */
.gm-seg__opt[aria-disabled="true"] { opacity:.35; cursor:not-allowed; }
/* the whole of it off: faint, nothing pressed */
.gm-seg.is-disabled { opacity:.82; }   /* off, and still readable */
.gm-seg.is-disabled .gm-seg__opt { cursor:not-allowed; pointer-events:none; }
.gm-mathset__who .gm-btn { min-height:44px; }
.gm-family__h { display:block; font-family:'Frank Ruhl Libre',serif; font-size:19px; }
.gm-family .mini { margin:4px 0 10px; }
.gm-family__list { list-style:none; margin:0 0 10px; padding:0; display:flex; flex-direction:column; gap:6px; }
.gm-family__list li { display:flex; align-items:center; gap:10px; }
.gm-family__names { flex:1; min-width:0; display:flex; flex-direction:column; gap:2px; }
.gm-family__n { font-weight:700; overflow-wrap:break-word; }
/* the nickname under the name (Maayan's wireframe, 2026-10-24): a smaller line, dimmed with the row when they are out */
.gm-family__nick { font-size:14px; color:var(--ink2, #6f5c3c); overflow-wrap:break-word; }
/* Delete, under Save: red words, a real tap, well apart from it */
.gm-player__del { align-self:center; min-height:44px; margin-top:14px; padding:12px 18px; border:0; background:none; font:inherit; font-weight:700; color:var(--danger,#B32D1F); text-decoration:underline; text-underline-offset:3px; cursor:pointer; }
.gm-tablebox #pdIn { width:100%; min-height:44px; font-size:16px; }
.gm-family__add { display:flex; gap:8px; }
.gm-family__add[hidden], .gm-family__login[hidden] { display:none; }
.gm-family__login { display:flex; align-items:center; gap:10px; flex-wrap:wrap; margin-top:8px; }
.gm-family__add input { flex:1; min-width:0; min-height:44px; font:inherit; font-size:16px; padding:8px 10px; border:2px solid var(--edge); background:var(--paper); color:var(--ink); }

/* The DM's throw on a party phone or the TV (app.js liveThrowIn): over the whole screen, under the windows. */
.gmdice.gmdice--layer.gm-replay { position:fixed; inset:0; z-index:60; pointer-events:none; }

/* Going into a table: the lobby goes to black first (app.js enterTable). */
.gm-toblack { position:fixed; inset:0; z-index:200; background:#000; opacity:0; transition:opacity .4s ease; pointer-events:none; }
.gm-toblack.is-on { opacity:1; }

/* THE LOBBY HAS NO HEADER (Maayan, 2026-10-20: "the header actually IS redundant"): the tabs and the pages only; the
   account is on Profile. */
.lb.is-outside .lb-top { display:none; }
/* Tables: GrowlMaster's logo over the words, a little over the picture's band */
.gm-lobby .gm-lobby__logo { margin:0 0 10px; line-height:0; position:relative; z-index:1; }
.gm-lobby__logo img { width:min(78%,420px); height:auto; filter:drop-shadow(0 6px 16px rgba(0,0,0,.7)); }
@media (max-width:1199px){ .gm-lobby .gm-lobby__logo { margin-top:-64px; } }
@media (max-width:560px){ .gm-lobby .gm-lobby__logo { margin-top:-46px; } .gm-lobby__logo img { width:min(80%,300px); } }
.gm-prof .gm-btn { margin-left:auto; }
/* Their bag of dice beside their picture on their profile (ProfilePane.astro): the badge's sack, over the picture's lower right. */
.gm-prof__pic { position:relative; display:inline-block; }
.gm-prof__sack { position:absolute; right:-18px; bottom:-12px; width:40px; filter:drop-shadow(0 2px 3px rgba(0,0,0,.3)); pointer-events:none; }

/* The guest's-picture notice (components/GuestImageNotice.astro, shared by every Jim product), in GrowlMaster's look */
.jim-gin, .jim-gim { --jim-gin-bg: var(--sheet, #faf5e8); --jim-gin-ink: var(--ink, #3a2c17); --jim-gin-line: var(--edge, #cbb98f); --jim-gin-accent: #865309; --jim-gin-on-accent: #fff; --jim-gin-radius: 0; --jim-gin-font: 'Grandstander', system-ui, sans-serif; }

/* JOIN A TABLE (Maayan's mock, 2026-10-24): "Write the table's code here", the code, Join; an OR line; "Scan table QR
   code", a square the camera shows in once opened (app.js scanJoin, qrScan.js), Open Camera. The scan half only on a
   phone or a tablet (hidden elsewhere). */
.gm-lobby__lead { margin:4px 0 0; font-size:17px; font-weight:700; color:var(--ink); }
/* the scan half: only where there is a camera to hold up (a phone, a tablet: a coarse pointer) */
.gm-lobby__scan { display:none; flex-direction:column; gap:12px; }
@media (pointer:coarse){ .gm-lobby__scan { display:flex; } }
.gm-lobby__or { display:flex; align-items:center; gap:12px; margin:10px 0 0; font-weight:700; color:var(--ink); }
.gm-lobby__or::before, .gm-lobby__or::after { content:""; flex:1; border-top:2px solid var(--edge); }
.gm-lobby__cam { position:relative; width:100%; aspect-ratio:1; box-sizing:border-box; border:2px solid var(--edge); background:var(--paper); overflow:hidden; }
.gm-lobby__cam.is-live { background:#000; }
.gm-lobby__cam video { position:absolute; inset:0; width:100%; height:100%; object-fit:cover; }

/* EVERY DROPDOWN'S CHEVRON (Maayan, 2026-10-24, through Troll: "padding from the end, about 16px, the same as the text's
   start padding… every dropdown, in LTR and RTL"): THE chevron (lib/ui/icons.js "down") drawn by the box itself, 16px in
   from its end edge as the words are 16px in from its start, and the words kept clear of it. The app's selects (.lb .sel)
   and the nickname window's (gmNick.css). The small ones (.sel--sm): 8px each side. */
:root { --sel-chev:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%233a2c17' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); }   /* in the ink (var(--ink)) of the page it is on */
.lb.is-outside { --sel-chev:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23f1e6cf' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E"); }
.lb .sel { -webkit-appearance:none; appearance:none; padding-inline-start:16px; padding-inline-end:44px;
  background-image:var(--sel-chev);
  background-repeat:no-repeat; background-size:16px 16px; background-position:right 16px center; }
[dir="rtl"] .lb .sel { background-position:left 16px center; }
.lb .sel--sm { padding-inline-start:8px; padding-inline-end:28px; background-size:12px 12px; background-position:right 8px center; }
[dir="rtl"] .lb .sel--sm { background-position:left 8px center; }

/* THE LINK (Maayan, 2026-10-25: "the brighter cream text with underlined links… the ONE link design for every lobby and
   tab page"): the words in the text's own colour (cream on the night pages, ink on parchment), underlined. Every link on
   Tables, Growls, Create, Profile, the log-in and sign-up and the Account window: this class, and no colour of its own
   (studio.js: the sign-in's, the Account's, Player details'). A button stays a button. */
.gm-link { padding:0; border:0; background:none; font:inherit; color:var(--ink); text-decoration:underline; text-underline-offset:3px; cursor:pointer; }
button.gm-link { display:inline-flex; align-items:center; }
.gm-link:focus-visible { outline:3px solid var(--ink2); outline-offset:2px; }

/* A PROFILE NOBODY HAS (ProfilePane.astro, Maayan, 2026-10-25): GrowlMaster's own not-found, in the page's gutter, centred */
.gm-prof--none { padding:48px 16px; text-align:center; }
.gm-prof--none .gm-ig__name { margin:0 0 8px; }
/* the legal lines' links too (GmLegal.astro, Maayan, 2026-10-25): in the app the one link's colour, over the legal
   block's own (it keeps the text's colour on pages outside the app, where app.css is not) */
.lb .gmlegal a.gm-link { color:var(--ink); }

/* A table's own picture on its lobby card (gm_table.image), the table icon's place, a little rounded square. */
.gm-outside__icon.has-img img { width:40px; height:40px; display:block; border-radius:10px; object-fit:cover; }
