/* Jihanki puzzle — ported from the standalone prototype.
   Every selector is scoped under .jihanki-app so the prototype's generic
   class names (.card, .grid, .controls, .legend, .rule, .slot, .toast)
   and its palette never leak into the rest of Puzzmallow (or collide with Bulma).
   The prototype's own :root / body theming is rehomed onto .jihanki-app. */

/* The page background — the colour and the dot pattern over it — is declared on
   the BODY rather than with the rest of the palette below, because one other
   element has to paint the same surface: the shared page footer, which sits after
   .jihanki-app in the content column and so cannot read anything scoped to it (see
   the footer rule further down). Declaring it once here is what keeps the two from
   drifting apart. The --jh- prefix keeps it clear of everything else on the page. */
body:has(.jihanki-app) {
  --jh-page-bg: #fff3e3;
  --jh-page-dot: rgba(255,150,120,0.16);
  --jh-page-muted: #86799f;
}
@media (prefers-color-scheme: dark) {
  body:has(.jihanki-app) {
    --jh-page-bg: #0c0f1f; --jh-page-dot: rgba(120,150,255,0.10); --jh-page-muted: #9c93c2;
  }
}
:root[data-theme="light"] body:has(.jihanki-app) {
  --jh-page-bg: #fff3e3; --jh-page-dot: rgba(255,150,120,0.16); --jh-page-muted: #86799f;
}
:root[data-theme="dark"] body:has(.jihanki-app) {
  --jh-page-bg: #0c0f1f; --jh-page-dot: rgba(120,150,255,0.10); --jh-page-muted: #9c93c2;
}

.jihanki-app {
  /* --- day konbini (light) --- */
  --bg: var(--jh-page-bg);
  --dot: var(--jh-page-dot);
  --burst: rgba(255,209,62,0.28);
  --ink: #2b2444;
  --muted: var(--jh-page-muted);
  --card: #ffffff;
  --card-line: #ffe0cd;
  --card-shadow: 0 16px 30px -20px rgba(120,60,40,0.5);

  /* the machine — a red jihanki, same day or night */
  --chassis-1: #ff5d6b;
  --chassis-2: #ef2f4c;
  --chassis-edge: #c81f39;
  --trim: #fffdf8;
  --trim-line: #ffd0c4;
  --glass: #141a2e;
  --glass-2: #0c1120;
  --glass-edge: #05070f;
  --bay: #1b2338;
  --bay-line: #2c3550;
  --on-dark: #fdf1e7;
  --muted-dark: #8f9bc4;

  /* pop accents */
  --accent: #ffca28;     /* sunny yellow */
  --accent-ink: #5a3d00;
  --pop-blue: #2ec5ff;
  --pop-pink: #ff6faa;
  --good: #22c07a;
  --bad: #ff4d63;
  --pending: #b9b0c9;

  /* packaging brand colours (poppy, all distinct) */
  --funco: #ff9130;   /* Sunco — warm amber, shifted yellower to read clearly apart from Tasty's pink */
  --fresh: #12bfa4;
  --allgood: #5b7cff;
  --tasty: #ff5aa8;

  --vfont-display: "Arial Rounded MT Bold", "Hiragino Maru Gothic ProN", "Baloo 2", "Quicksand", system-ui, sans-serif;
  --vfont-jp: "Hiragino Maru Gothic ProN", "Yu Gothic", "YuGothic", "Meiryo", "Hiragino Kaku Gothic ProN", sans-serif;
  --vfont-body: "Hiragino Maru Gothic ProN", system-ui, -apple-system, "Segoe UI", "Yu Gothic", Roboto, sans-serif;
  --vfont-mono: ui-monospace, "SF Mono", "Cascadia Code", Consolas, monospace;

  color: var(--ink);
  font-family: var(--vfont-body);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  background-color: var(--bg);
  background-image: radial-gradient(var(--dot) 1.6px, transparent 1.7px);
  background-size: 18px 18px;
  /* full-bleed dotted background: fills the whole content column, square edges */
  border-radius: 0;
  padding: clamp(16px, 2.8vw, 26px) clamp(12px, 2vw, 18px);
}
@media (prefers-color-scheme: dark) {
  .jihanki-app {
    --burst: rgba(255,90,120,0.22);
    --ink: #f3ecff;
    --card: #171a2e;
    --card-line: #2a2f4a;
    --card-shadow: 0 18px 34px -20px rgba(0,0,0,0.7);
    --pending: #59607a;
  }
}
:root[data-theme="light"] .jihanki-app {
  --burst: rgba(255,209,62,0.28);
  --ink: #2b2444; --card: #ffffff; --card-line: #ffe0cd;
  --card-shadow: 0 16px 30px -20px rgba(120,60,40,0.5); --pending: #b9b0c9;
}
:root[data-theme="dark"] .jihanki-app {
  --burst: rgba(255,90,120,0.22);
  --ink: #f3ecff; --card: #171a2e; --card-line: #2a2f4a;
  --card-shadow: 0 18px 34px -20px rgba(0,0,0,0.7); --pending: #59607a;
}

.jihanki-app * { box-sizing: border-box; }

/* ---- Jihanki-only page chrome -------------------------------------------
   This stylesheet ships on every page, so everything below is gated on the
   jihanki puzzle actually being on the page (body:has(.jihanki-app)). The
   markup is pre-rendered server-side, so the gate matches on first paint and
   there is no flash of the old narrow layout. ---- */

/* Drop the shared puzzle-container top padding (Bulma .pt-2 ≈ 8px) so the
   dotted jihanki background starts flush under the header, and let the
   container run the full width it is given instead of the shared 600px cap. */
.puzzle-container:has(> .jihanki-app) {
  padding-top: 0 !important;
  max-width: none;
  width: 100%;
}

/* The pink puzzle header runs edge to edge on jihanki (it is capped at 600px
   and centred everywhere else). !important beats the scoped .puzzle-menu rule
   in HeaderMenu.razor.css, which ships in the later Client.styles.css bundle.
   No extra inline padding: the logo already carries 15px of its own (.column
   padding + .mallow margin), which is as close to the edge as it should get. */
body:has(.jihanki-app) .puzzle-menu {
  max-width: none !important;
  width: 100%;
  padding-left: 0;
  padding-right: 0;
}

/* Take the content column out of the ad rails' flex row so it can be genuinely
   full-bleed: the rails are lifted out of flow and pinned over the left and
   right edges, and the content column becomes the full width of the page. The
   ads therefore sit exactly where they always did — same 300px boxes, same
   edges, same order in the DOM — but they no longer subdivide the width, so the
   dotted jihanki background runs the whole way across underneath them.
   The rails carry a z-index so they paint above everything in the content column.
   Relying on "positioned beats static" is not enough: the rails come BEFORE
   .main-content-wrapper in the DOM, so any positioned descendant of the content
   column paints over the LEFT rail (the right rail comes after it and wins). 10
   clears the puzzle chrome (which tops out at z-index 4) and stays well below the
   modals at 140/150. */
body:has(.jihanki-app) .page-with-ads-layout { display: block; position: relative; }
body:has(.jihanki-app) .left-rail,
body:has(.jihanki-app) .right-rail { position: absolute; top: 0; z-index: 10; }
body:has(.jihanki-app) .left-rail { left: 0; }
body:has(.jihanki-app) .right-rail { right: 0; }
body:has(.jihanki-app) .main-content-wrapper { width: 100%; max-width: none; }

/* The how-to-play section lives INSIDE .jihanki-app rather than beside it, so the
   dotted background carries on under it instead of stopping at the puzzle and
   handing over to a white slab. One element owns that background, so there is no
   second copy of the gradient to keep in step.
   What it costs: Bulma gives .content headings and <strong> a fixed #363636. That
   is right on the day palette and all but invisible on the night one, so point
   them at the puzzle's own ink — which flips with the theme — instead. */
.jihanki-app #how-to-play { color: var(--ink); }
/* blazor-ui.css paints every Bulma .container white, and this block carries that
   class for its max-width. That white slab is what was still showing through the
   dotted ground after the block moved inside .jihanki-app. */
.jihanki-app #how-to-play.container { background-color: transparent; }

/* The shared page footer belongs to the layout, not the puzzle, so it sits AFTER
   .jihanki-app rather than inside it and keeps its own white background (plus the
   600px cap every other page wants). Against the dotted ground that reads as a slab
   cut out of the page — and on the night palette it is a white one. Carry the same
   surface across it: the colour and dots come from the body-level --jh-page-* pair
   at the top of this file, which is where .jihanki-app takes its own background
   from, so the two can never disagree. Full width for the same reason — a 600px
   band would leave the page's own background showing down either side of it. */
body:has(.jihanki-app) .page-footer {
  max-width: none;
  background-color: var(--jh-page-bg);
  background-image: radial-gradient(var(--jh-page-dot) 1.6px, transparent 1.7px);
  background-size: 18px 18px;
}
/* Its links are a hardcoded #666, chosen against the white slab: fine on the day
   palette, barely there on the night one. Give them the palette's own muted ink,
   which is declared alongside the background above for exactly this reason. */
body:has(.jihanki-app) .page-footer .privacy-policy-container a { color: var(--jh-page-muted); }
.jihanki-app #how-to-play .content h1,
.jihanki-app #how-to-play .content h2,
.jihanki-app #how-to-play .content h3,
.jihanki-app #how-to-play .content h4,
.jihanki-app #how-to-play .content strong { color: var(--ink); }

/* The how-to bullets read as the day's products talking: the bare type artwork on
   the left, the step's text in a speech bubble beside it. The list drops Font
   Awesome's absolute .fa-li marker for a plain flex row — the marker column was
   fighting Bulma's `.content ul` for the indent, and a bubble that has to own a
   tail wants to be a real sibling of the icon rather than text beside an overlay.
   list-style/margin/padding are restated for the same reason: `.content ul` (disc
   outside, margin-left:2em) outweighs a bare class selector, and its disc would
   otherwise paint behind every product. */
.jihanki-app #how-to-play .jh-howto {
  --jh-howto-icon: clamp(3.2em, 8vw, 5.2em);
  /* How far down the icon the characters' mouths sit. The artwork is object-fit
     contained in a square box, so a product with tall leaves (beet) wears its face
     lower than a squat one (carrot); this is the middle of that spread. */
  --jh-tail: calc(var(--jh-howto-icon) * 0.55);
  list-style: none;
  margin: 1.2em 0 0; padding: 0;
  display: flex; flex-direction: column; gap: 1.1em;
}
.jihanki-app #how-to-play .jh-howto > li {
  display: flex; align-items: flex-start; gap: .8em; margin: 0;
}
.jihanki-app #how-to-play .jh-howto-prod {
  flex: none; width: var(--jh-howto-icon); height: var(--jh-howto-icon);
}
.jihanki-app #how-to-play .jh-howto-prod .picon {
  width: 100%; height: 100%; filter: drop-shadow(0 2px 2px rgba(0,0,0,.28));
}
/* Brands in the bubble text are shown as their wordmarks in packaging colour, the
   same icons the manifest uses — a touch larger than the clues' since this is
   body copy. */
.jihanki-app #how-to-play .bicon.inline { width: 1.5em; height: 1.5em; vertical-align: -0.45em; margin: 0 1px; }
/* ...and the day's product types as their artwork, at the same size. */
.jihanki-app #how-to-play .picon.inline { display: inline-block; width: 1.5em; height: 1.5em; vertical-align: -0.45em; margin: 0 1px; }
/* A bubble that holds several points lists them as bullets. Bulma's `.content ul`
   (margin-left:2em, margin-top:1em) is sized for page copy, too loose in here. */
.jihanki-app #how-to-play .jh-bubble-list {
  list-style: disc outside; margin: .3em 0 0 1.2em; padding: 0;
}
.jihanki-app #how-to-play .jh-bubble-list > li + li { margin-top: .2em; }
.jihanki-app .jh-bubble {
  position: relative; flex: 1 1 auto; min-width: 0;
  background: var(--card); border: 2px solid var(--card-line);
  border-radius: 16px; padding: .75em 1em;
  box-shadow: 0 6px 14px -10px rgba(120,60,40,.55);
}
/* The tail is two stacked triangles so it carries the bubble's border as well as
   its fill: ::before is the outline in --card-line, ::after the fill in --card sat
   2px inside it. Both hang off the bubble's left edge and are placed from the ICON's
   height rather than the bubble's, at --jh-tail down it, so the tail lands level
   with the character's mouth instead of drifting with how much text a bubble holds.
   Each triangle subtracts its own half-height so the two stay concentric. */
.jihanki-app .jh-bubble::before,
.jihanki-app .jh-bubble::after {
  content: ""; position: absolute;
  width: 0; height: 0; border-style: solid; border-left-width: 0;
}
.jihanki-app .jh-bubble::before {
  left: -11px; top: calc(var(--jh-tail) - 9px);
  border-width: 9px 11px 9px 0; border-color: transparent var(--card-line) transparent transparent;
}
.jihanki-app .jh-bubble::after {
  left: -8px; top: calc(var(--jh-tail) - 7px);
  border-width: 7px 9px 7px 0; border-color: transparent var(--card) transparent transparent;
}
/* On a phone the icon + bubble + tail no longer fit side by side, so the product
   sits above its bubble and the tail swings round to point down at it. */
@media (max-width: 560px) {
  .jihanki-app #how-to-play .jh-howto > li { flex-direction: column; gap: .5em; }
  .jihanki-app #how-to-play .jh-howto-prod { margin-left: .5em; }
  .jihanki-app #how-to-play .jh-bubble { align-self: stretch; }
  .jihanki-app #how-to-play .jh-bubble::before,
  .jihanki-app #how-to-play .jh-bubble::after { top: auto; border-left-width: 0; }
  .jihanki-app #how-to-play .jh-bubble::before {
    left: 1.1em; bottom: 100%;
    border-width: 0 9px 11px 9px; border-color: transparent transparent var(--card-line) transparent;
  }
  .jihanki-app #how-to-play .jh-bubble::after {
    left: calc(1.1em + 2px); bottom: calc(100% - 2px);
    border-width: 0 7px 9px 7px; border-color: transparent transparent var(--card) transparent;
  }
}

/* The playable box. The dotted .jihanki-app background is full-bleed, but the
   puzzle inside it must not run under the overlaid ad rails, so this box gives
   back an equal --jh-rail on each side and centres what's left. Reserving the
   space symmetrically is what makes the machine AND the Restock/Manifest card
   centre on the page as a single unit, rather than the machine centring on its own.
   It is also the query container: whether the sidebar fits beside the machine
   depends on this width, not on the viewport's. */
.jihanki-app { --jh-rail: 0px; }
@media (min-width: 1246px) {           /* where .desktop-only stops hiding the rails */
  .jihanki-app { --jh-rail: 320px; }   /* 300px ad + a 20px gutter */
}
/* Paid members never see an ad (body.pm-premium hides every [data-ad]), so the
   rails are empty for them and there is nothing to reserve. */
body.pm-premium .jihanki-app { --jh-rail: 0px; }
.jihanki-app .jihanki-bounds {
  container: jihanki / inline-size;
  width: 100%;
  max-width: calc(100% - 2 * var(--jh-rail));
  margin-inline: auto;
}

/* ---- Vertical stack for all devices: machine, then crate, then manifest ----
   Capped and centred so the 4x4 grid gets most of the width without becoming
   absurdly tall on wide desktops. */
.jihanki-app .jihanki-stack {
  display: flex; flex-direction: column;
  gap: clamp(12px, 2.2vw, 20px);
  max-width: 640px; margin: 0 auto;
  text-align: left;
}

/* ---- Machine ---- : a single thin red frame around the glass, so nearly all
   the width goes to the 4x4 grid (no cream inner cabinet, no marquee plate). */
.jihanki-app .machine {
  position: relative;
  background: linear-gradient(158deg, var(--chassis-1), var(--chassis-2));
  border: 1.5px solid var(--chassis-edge);
  /* red chassis frame ~a third of its former thickness (was 2px border +
     clamp(7,1.3vw,11px) padding) so almost all the width goes to the glass. */
  border-radius: 15px; padding: clamp(2px, 0.45vw, 4px);
  box-shadow: 0 16px 32px -22px rgba(200,30,55,0.6), inset 0 2px 0 rgba(255,255,255,0.28);
  display: flex; flex-direction: column; gap: 8px;
}
/* Soft burst behind the machine's top edge. It must NOT bleed sideways: the
   machine is only clamp(12px, 2vw, 18px) from the edge of the content column on
   mobile, so a horizontal bleed pushed the page's scroll width past the viewport
   and left an empty strip you could scroll to. Vertical bleed is free — overflow
   above the page never scrolls. */
.jihanki-app .machine::before {
  content: ""; position: absolute; inset: -30px 0 auto 0; height: 160px; z-index: -1;
  background: radial-gradient(closest-side, var(--burst), transparent 72%);
  filter: blur(4px);
}
/* glass display */
.jihanki-app .glass {
  position: relative; background: linear-gradient(170deg, var(--glass), var(--glass-2));
  /* black inset band (holds the row/column numbers) halved: was 22/7/7/19. */
  border-radius: 11px; padding: 14px 7px 7px 13px;
  border: 2px solid var(--glass-edge);
  box-shadow: inset 0 3px 26px rgba(0,0,0,0.7);
  overflow: hidden;
}
.jihanki-app .glass::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(115deg, rgba(255,255,255,0.10) 0%, transparent 30%, transparent 72%, rgba(255,255,255,0.05) 100%);
}
.jihanki-app .grid { position: relative; display: grid; grid-template-columns: repeat(var(--jh-n, 4), 1fr); gap: 6px; }
/* faint interior lighting from the top */
.jihanki-app .glass::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 40%; pointer-events: none; z-index: 1;
  background: linear-gradient(180deg, rgba(180,210,255,0.10), transparent);
}
.jihanki-app .colnums { position: absolute; top: 4px; left: 13px; right: 7px; display: grid; grid-template-columns: repeat(var(--jh-n, 4),1fr); gap: 6px; }
.jihanki-app .colnums span { text-align: center; font-family: var(--vfont-mono); font-size: 9px; color: var(--muted-dark); }
.jihanki-app .rownums { position: absolute; top: 14px; bottom: 7px; left: 3px; width: 9px; display: grid; grid-template-rows: repeat(var(--jh-n, 4),1fr); gap: 6px; }
.jihanki-app .rownums span { display: flex; align-items: center; justify-content: center; font-family: var(--vfont-mono); font-size: 9px; color: var(--muted-dark); }

.jihanki-app .bay {
  position: relative; aspect-ratio: 1 / 1; border: 2px solid var(--bay-line);
  background: var(--bay); border-radius: 12px; padding: 0; cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: border-color .15s ease, box-shadow .15s ease, background .15s ease, transform .12s ease;
}
/* Only a STOCKED bay reacts to hover/tap (it can be emptied). An empty bay is a
   notes surface — highlighting the whole cell fought with the mark under the
   finger, and on touch the hover stuck after the tap. .stocked is set in the
   paint, so the highlight also clears the moment the bay is emptied. */
.jihanki-app .bay.stocked:hover { border-color: color-mix(in srgb, var(--accent) 65%, var(--bay-line)); }
/* selecting a crate product no longer outlines the empty bays (.droptarget is a no-op). */
.jihanki-app .bay:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }
.jihanki-app .bay .coil { width: 46%; height: 3px; border-radius: 3px; background: repeating-linear-gradient(90deg, #333c58 0 4px, transparent 4px 7px); opacity: .7; }
.jihanki-app .bay.given { cursor: default; }

/* Pencil marks (notes overlay) on empty bays: 2 rows of 4 (types, brands).
   Price is fixed on the bay, so it has no note. The top strip is left clear for
   the .bprice tag. Each mark cycles grey → crossed → highlighted. */
.jihanki-app .bay .marks {
  position: absolute; inset: 14px 12px 8px 12px; z-index: 3;
  /* Two rows of --jh-n notes: the product types, then the brands. */
  display: grid; grid-template-columns: repeat(var(--jh-n, 4), 1fr); grid-auto-rows: min-content;
  align-content: center; row-gap: 10px; column-gap: 2px;
}
.jihanki-app .mk {
  position: relative; display: flex; align-items: center; justify-content: center;
  border-radius: 4px; cursor: pointer; user-select: none; line-height: 1;
  font-size: 11px; color: var(--on-dark);
  opacity: 1;                                        /* notes are full-colour in every state */
  padding: 6px 3px; box-sizing: border-box;          /* taller hit/ring box — a rectangle around the icon */
  transition: filter .1s ease, background .1s ease;
}
.jihanki-app .mk:hover { background: rgba(255,255,255,.12); }
.jihanki-app .mk .sw { width: 60%; height: auto; aspect-ratio: 1 / 1; border-radius: 3px; box-shadow: inset 0 1px 0 rgba(255,255,255,.4); }
/* highlighted: this candidate is "on" — full colour, ringed */
.jihanki-app .mk.on { opacity: 1; filter: none; background: rgba(255,255,255,.14); box-shadow: inset 0 0 0 1.5px var(--accent); }
/* crossed out: eliminated — marked only by the red slash, no dimming */
.jihanki-app .mk.cross { opacity: 1; }
.jihanki-app .mk.cross::after {
  content: ""; position: absolute; inset: 12%; pointer-events: none;
  background: linear-gradient(45deg, transparent 42%, var(--bad) 42%, var(--bad) 58%, transparent 58%);
}
/* Note icons: +25% on desktop (the mobile block at the foot of the file takes it
   to +10% there instead). A transform rather than a bigger width percentage —
   the icon is a flex item inside .mk, so any width over 100% of .mk's content box
   is just shrunk straight back by flex-shrink. Scaling also leaves the layout
   untouched: the 4-up grid, .mk's hit area and its "on" ring all stay put, and
   the icons still clear each other (the overflow lands in .mk's own padding).
   The crossed-out slash scales with them so it goes on covering the icon. */
.jihanki-app .mk-type .picon,
.jihanki-app .mk-brand .bicon,
.jihanki-app .mk.cross::after { transform: scale(1.25); }
.jihanki-app .bay.given.stocked:hover { border-color: var(--bay-line); }
/* pre-placed (given) products: no yellow ring — they just read as normal tiles that can't be moved. */
.jihanki-app .prod.pinned { cursor: default; box-shadow: inset 0 2px 0 rgba(255,255,255,0.32), inset 0 0 0 2px rgba(255,255,255,0.14), 0 3px 6px rgba(0,0,0,0.4); animation: none; }

/* product tile — brand label on top, emoji centred. Price is on the bay, not the
   product. */
.jihanki-app .prod {
  --b: var(--muted);
  width: 100%; height: 100%; border: none; cursor: pointer;
  border-radius: 11px; padding: 6px;
  background: linear-gradient(165deg, color-mix(in srgb, var(--b) 82%, white 18%), var(--b));
  color: #fff; display: grid; grid-template-rows: auto 1fr; gap: 3px;
  position: relative; z-index: 2; overflow: hidden; text-align: left;
  box-shadow: inset 0 2px 0 rgba(255,255,255,0.32), inset 0 0 0 2px rgba(255,255,255,0.14), 0 3px 6px rgba(0,0,0,0.4);
  transition: transform .13s ease, box-shadow .13s ease, opacity .13s ease;
  animation: jihanki-drop .2s ease;
}
@keyframes jihanki-drop { from { transform: translateY(-8px) scale(.9); opacity: 0; } to { transform: none; opacity: 1; } }
.jihanki-app .prod .pb { display: block; height: 12px; }
.jihanki-app .prod .pe { font-size: clamp(15px, 3vw, 20px); line-height: 1; align-self: center; justify-self: center; filter: drop-shadow(0 2px 2px rgba(0,0,0,.35)); }
.jihanki-app .prod:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* Brand wordmark icon — an SVG rendered as a CSS mask so it can be recoloured:
   black on the product tile, brand-coloured in notes and the manifest. */
.jihanki-app .bicon {
  display: inline-block; width: 1em; height: 1em; background-color: currentColor;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  -webkit-mask-size: contain; mask-size: contain;
}
.jihanki-app .prod .pb .bicon { display: block; width: 100%; height: 100%; -webkit-mask-position: left center; mask-position: left center; }
.jihanki-app .mk-brand .bicon { width: 81%; height: auto; aspect-ratio: 1 / 1; }
.jihanki-app .clues .bicon.inline { width: 1.2em; height: 1.2em; vertical-align: -0.3em; margin: 0 1px; }
/* Product-type artwork standing in for the type's name in a clue, sized to match
   the brand wordmarks beside it. */
.jihanki-app .clues .picon.inline { display: inline-block; width: 1.2em; height: 1.2em; vertical-align: -0.3em; margin: 0 1px; }

/* fixed bay price tag — the slot's printed price, shown top-right whether the bay
   is empty or filled (sits above the product tile and the notes overlay). */
.jihanki-app .bay .bprice {
  position: absolute; top: 3px; right: 4px; z-index: 4; pointer-events: none;
  font-family: var(--vfont-mono); font-size: 9.5px; font-weight: 700; letter-spacing: .01em;
  padding: 1px 5px; border-radius: 999px;
  background: var(--accent); color: #3a2800; border: 1.5px solid #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.4);
}

/* Product icons are SVGs, served as <img> and — on the tiles jihanki.js owns —
   swapped for inline <svg> by jihanki-eyes.js so the pupils can track the pointer.
   These rules must therefore hold for both element types. Sizing the tile icon in
   `em` keeps the existing .pe font-size rules (base + the desktop machine
   enlargement/offset) in charge. */
.jihanki-app .picon { display: block; object-fit: contain; user-select: none; -webkit-user-drag: none; pointer-events: none; }
.jihanki-app .pe .picon { width: 1.15em; height: 1.15em; }
.jihanki-app .mk-type .picon { width: 81%; height: auto; aspect-ratio: 1 / 1; }
.jihanki-app .legend .picon { width: 15px; height: 15px; }
/* Restock crate: the product icon FILLS its tile instead of following .pe's
   font-size. That font-size is viewport-clamped (20px on desktop) while a crate
   tile follows --jh-tile and can reach 120px, so an em-sized icon shrinks away
   inside a big tile. Stretching .pe across the tile's 1fr row and filling it ties
   the icon to the tile at every width; object-fit on .picon keeps it square. */
.jihanki-app .tray .prod .pe {
  align-self: stretch; justify-self: stretch;
  display: flex; align-items: center; justify-content: center;
  /* A grid item's automatic minimum is its content's min-content size, which for
     an image is its intrinsic size — that grows the 1fr row PAST the tile and the
     icon spills out under .prod's overflow:hidden. min-height:0 hands the row back
     to the space the tile actually has left under the wordmark. */
  min-width: 0; min-height: 0;
}
.jihanki-app .tray .prod .pe .picon { width: 100%; height: 100%; }

/* Mobile: halve the tile's padding (6→3) and gap (3→1.5) so the brand wordmark
   and type icon sit tighter on the small tiles. The rows stay `auto 1fr` (as on
   desktop): packing them and centring the pair pushed the wordmark down, so it no
   longer lined up with the bay's price tag. The wordmark stays pinned to the top
   of the tile, level with the price, and the type icon centres below it. */
@media (max-width: 899px) {
  .jihanki-app .prod { grid-template-rows: auto 1fr; align-content: stretch; gap: 1.5px; padding: 3px; }
}

/* Tabbed card: the Restock crate and the Manifest share one card + footprint */
.jihanki-app .vtabs {
  display: flex; gap: 6px; margin-bottom: 13px;
  background: color-mix(in srgb, var(--card-line) 45%, transparent);
  padding: 4px; border-radius: 13px;
}
.jihanki-app .vtab {
  flex: 1; font-family: var(--vfont-display); font-weight: 800; font-size: 13px;
  padding: 8px 10px; border: none; border-radius: 10px; cursor: pointer;
  background: transparent; color: var(--muted);
  display: flex; align-items: center; justify-content: center; gap: 6px;
  transition: background .15s ease, color .15s ease, box-shadow .15s ease;
}
.jihanki-app .vtab .jp { font-family: var(--vfont-jp); font-size: 11px; color: var(--pop-pink); }
.jihanki-app .vtab.active { background: var(--card); color: var(--ink); box-shadow: 0 2px 6px -2px rgba(0,0,0,.3); }
.jihanki-app .vtab:focus-visible { outline: 3px solid var(--pop-blue); outline-offset: 2px; }
/* both panels occupy the same grid cell, so the card height stays constant when switching */
.jihanki-app .vtab-panels { display: grid; }
.jihanki-app .vtab-panel { grid-area: 1 / 1; align-self: start; }
.jihanki-app .vtab-panel:not(.active) { visibility: hidden; }

.jihanki-app .tray {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px;
  background: linear-gradient(180deg, #1a2136, #10131f); padding: 9px; border-radius: 12px; border: 2px solid var(--glass-edge);
  box-shadow: inset 0 3px 14px rgba(0,0,0,0.55);
}
.jihanki-app .slot { aspect-ratio: 1/1; border-radius: 11px; }
.jihanki-app .slot.empty { border: 2px dashed #3a4361; background: repeating-linear-gradient(135deg, transparent 0 6px, rgba(255,255,255,0.03) 6px 12px); }

/* ---- Crate sort controls ----
   Two cells of the crate are permanently spare — the machine starts with two bays
   pre-stocked, so the products never fill the last row — and these sit in them.
   They only regroup the crate for display (brand-major or type-major); the board
   and the save never see it, which is why the whole feature lives in jihanki.js.
   Built to read as crate furniture rather than as a product: dashed like an empty
   slot until it is the active grouping, then solid in the accent colour. */
.jihanki-app .slot.sortslot { display: grid; }
/* flex, not grid: the chips box is sized as a percentage of the button, and an
   auto-sized grid track would resolve that against the chips' own (near zero)
   intrinsic width and collapse the whole face. A flex item's percentage width
   resolves against the button's definite content box instead. */
.jihanki-app .sortbtn {
  display: flex; align-items: center; justify-content: center;
  width: 100%; height: 100%; padding: 13%;
  border: 2px dashed #3a4361; border-radius: 11px; background: rgba(255,255,255,0.02);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.jihanki-app .sortbtn:hover { border-color: var(--pop-blue); }
.jihanki-app .sortbtn.on {
  border-style: solid; border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.jihanki-app .sortbtn:focus-visible { outline: 3px solid var(--pop-blue); outline-offset: 2px; }
/* The chips are the whole face of the control, 2x2 in the square the tile already
   is. A crate tile runs from ~30px to 120px, so they are sized as a share of the
   button rather than in px; min-width/height 0 keeps the icons' intrinsic size from
   growing the track past the tile — the same trap .tray .prod .pe documents above. */
.jihanki-app .sortbtn .chips {
  display: grid; grid-template-columns: repeat(2, 1fr); gap: 8%;
  width: 100%; min-width: 0; min-height: 0;
}
.jihanki-app .sortbtn .chip {
  width: 100%; min-width: 0; aspect-ratio: 1/1; border-radius: 3px;
  display: flex; align-items: center; justify-content: center;
}
/* A 3x3 machine stocks three brands and three types, so the last chip is alone on
   its row: centre it over both columns at a column's width rather than leaving a
   hole in the corner. 4% is half the gap above. */
.jihanki-app .sortbtn .chip:nth-child(3):last-child {
  grid-column: 1 / -1; justify-self: center; width: calc(50% - 4%);
}
.jihanki-app .sortbtn .chip .picon { width: 100%; height: 100%; }
.jihanki-app .prod.selected { transform: translateY(-4px) scale(1.04); box-shadow: 0 0 0 3px var(--accent), 0 0 18px color-mix(in srgb, var(--accent) 60%, transparent), inset 0 2px 0 rgba(255,255,255,.3); }
.jihanki-app .prod.done { opacity: .32; }

/* ---- Guide / manifest cards ---- */
.jihanki-app .v-card { background: var(--card); border: 2px solid var(--card-line); border-radius: 20px; padding: 17px 17px 15px; box-shadow: var(--card-shadow); }
/* the tabbed card stacks its parts, so .v-stats can be CSS-ordered to the top
   (desktop) or the bottom (mobile) of the card without moving in the markup. */
.jihanki-app .tabcard { display: flex; flex-direction: column; }
.jihanki-app .v-card h2 { margin: 0 0 3px; font-family: var(--vfont-display); font-weight: 800; font-size: 15px; letter-spacing: 0.01em; display: flex; align-items: center; gap: 8px; }
.jihanki-app .v-card h2 .jp { font-family: var(--vfont-jp); font-size: 12px; font-weight: 700; color: var(--pop-pink); }
.jihanki-app .v-card .sub { margin: 0 0 13px; font-size: 12.5px; color: var(--muted); }

.jihanki-app ol.clues { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 3px; counter-reset: c; }
.jihanki-app ol.clues li {
  display: grid; grid-template-columns: 22px 1fr; gap: 9px; align-items: start;
  padding: 8px 7px; border-radius: 12px; font-size: 13.5px; transition: background .15s ease;
}
.jihanki-app ol.clues li::before { counter-increment: c; content: counter(c); font-family: var(--vfont-display); font-weight: 800; font-size: 12px; color: #fff; background: var(--pop-blue); width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; }
.jihanki-app ol.clues li.ok { background: color-mix(in srgb, var(--good) 13%, transparent); }
.jihanki-app ol.clues li.ok::before { background: var(--good); }
.jihanki-app ol.clues li.bad { background: color-mix(in srgb, var(--bad) 14%, transparent); }
.jihanki-app ol.clues li.bad::before { background: var(--bad); }
.jihanki-app ol.clues li.bad .txt { color: var(--bad); }

/* the pinned, always-on brand-latin rule sits first, tinted to read as fixed */
.jihanki-app ol.clues li.latin { background: color-mix(in srgb, var(--accent) 16%, transparent); }
.jihanki-app ol.clues li.latin::before { background: var(--accent); color: var(--accent-ink); }

/* ---- Undo / Reset: at the foot of the Restock/Manifest card ---- */
.jihanki-app .v-actions { display: flex; gap: 10px; margin-top: 13px; }

/* ---- Mode switcher: Classic / Mini, below the puzzle ----
   The same pill toolbar the other puzzles use (grey track, pink active chip),
   but painted from this stylesheet's tokens so it holds up in dark mode. */
.jihanki-app .v-modes { display: flex; justify-content: center; margin-top: clamp(12px, 2.2vw, 20px); }
.jihanki-app .v-modes-track {
  display: inline-flex; align-items: center; gap: 8px;
  background: color-mix(in srgb, var(--muted) 16%, var(--card));
  border-radius: 24px; padding: 5px 7px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.jihanki-app button.v-mode {
  font-family: var(--vfont-display); font-size: 14px; font-weight: 500; cursor: pointer;
  padding: 4px 12px; border: none; border-radius: 16px;
  background: var(--card); color: var(--ink);
  transition: background .12s ease, color .12s ease;
}
.jihanki-app button.v-mode.on { background: #ff69b4; color: #fff; font-weight: 600; }
.jihanki-app button.v-mode:disabled { opacity: .5; cursor: default; }
.jihanki-app button.v-mode:focus-visible { outline: 3px solid var(--pop-blue); outline-offset: 2px; }

/* ---- Loading screen ------------------------------------------------------
   Stands in for the whole puzzle while the generator runs (a synchronous search
   on the UI thread), showing one of the day's own products. The <img> is swapped
   for an inline SVG whose eyes track the pointer — see initJihankiFace in
   jihanki-eyes.js — so the wait has something alive in it.
   Given the same height the machine would have had, so the page does not jump
   when the board takes over. */
.jihanki-app .jh-loading {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: clamp(12px, 2vw, 20px);
  min-height: min(60vh, 420px);
  text-align: center;
  font-family: var(--vfont-display); font-weight: 800; color: var(--muted);
}
.jihanki-app .jh-loading p { margin: 0; font-size: clamp(14px, 1.4vw, 17px); }

.jihanki-app .jh-loading-face {
  width: clamp(96px, 22vw, 150px);
  height: clamp(96px, 22vw, 150px);
  margin: 0;
  display: flex; align-items: center; justify-content: center;
  animation: jh-bob 2.4s ease-in-out infinite;
}
.jihanki-app .jh-loading-face img,
.jihanki-app .jh-loading-face svg { width: 100%; height: 100%; object-fit: contain; }

@keyframes jh-bob {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-7px); }
}

/* The eyes already hold still under reduced motion (see jihanki-eyes.js); the
   bob has to be told separately. */
@media (prefers-reduced-motion: reduce) {
  .jihanki-app .jh-loading-face { animation: none; }
}

/* The timer pill's switch (see .v-stats). */
.jihanki-app .v-switch {
  /* inline-block, not inline: outside a flex parent the track would otherwise
     collapse and the switch would read as a stray blob rather than a toggle. */
  display: inline-block;
  position: relative; width: 38px; height: 22px; border-radius: 999px; flex: none;
  background: color-mix(in srgb, var(--muted) 45%, transparent);
  transition: background .15s ease;
}
.jihanki-app .v-switch::after {
  content: ""; position: absolute; top: 3px; left: 3px; width: 16px; height: 16px; border-radius: 50%;
  background: #fff; box-shadow: 0 1px 3px rgba(0,0,0,.35); transition: transform .15s ease;
}

.jihanki-app button.v-btn {
  flex: 1; font-family: var(--vfont-display); font-size: 14px; font-weight: 800; cursor: pointer;
  padding: 11px 12px; border-radius: 14px; border: 2px solid var(--card-line); background: var(--card); color: var(--ink);
  box-shadow: 0 4px 0 -1px var(--card-line); transition: transform .09s ease, box-shadow .09s ease, filter .15s ease;
}
.jihanki-app button.v-btn:hover:not(:disabled) { filter: brightness(1.03); }
.jihanki-app button.v-btn:active:not(:disabled) { transform: translateY(3px); box-shadow: 0 1px 0 -1px var(--card-line); }
.jihanki-app button.v-btn:disabled { opacity: .4; cursor: default; }
.jihanki-app button.v-btn.primary { background: var(--accent); border-color: #e0a800; color: #4a3200; box-shadow: 0 4px 0 -1px #d09c00; }
.jihanki-app button.v-btn.primary:active { box-shadow: 0 1px 0 -1px #d09c00; }
.jihanki-app button.v-btn:focus-visible { outline: 3px solid var(--pop-blue); outline-offset: 2px; }

/* ---- Stats bar: the timer pill and a jump to the instructions ----
   Lives inside the Restock/Manifest card; ordered to the card's top on desktop
   and its bottom on mobile (see the media queries at the foot of this file).
   Undo/Reset sit just above/below it in .v-actions; Solve/New are in the devbar. */
.jihanki-app .v-stats {
  display: flex; align-items: center; justify-content: space-between; gap: 8px 10px; flex-wrap: wrap;
  order: 3; margin-top: 13px; padding-top: 11px; border-top: 2px solid var(--card-line);
}
/* The whole pill is the switch's label, so the time and the icon toggle it too. */
.jihanki-app .v-stat-timer {
  display: inline-flex; align-items: center; gap: 9px; cursor: pointer;
  padding: 4px 5px 4px 11px; border-radius: 999px;
  border: 2px solid var(--card-line); background: color-mix(in srgb, var(--card-line) 22%, var(--card));
}
.jihanki-app .v-stat-timer input { position: absolute; opacity: 0; width: 0; height: 0; }
.jihanki-app .v-stat-timer .fa-stopwatch { font-size: 12px; color: var(--muted); }
.jihanki-app .v-time {
  font-family: var(--vfont-mono); font-weight: 700; font-size: 14px; letter-spacing: .02em;
  color: var(--ink); min-width: 43px; text-align: center;
}
/* A ringed track + solid knob, so it reads as a switch against the card. */
.jihanki-app .v-stat-timer .v-switch {
  width: 34px; height: 20px;
  background: color-mix(in srgb, var(--muted) 22%, transparent);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--muted) 50%, transparent);
}
.jihanki-app .v-stat-timer .v-switch::after {
  top: 3px; left: 3px; width: 14px; height: 14px;
  background: color-mix(in srgb, var(--muted) 80%, var(--card));
}
.jihanki-app .v-stat-timer input:checked + .v-switch {
  background: var(--pop-blue); box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--pop-blue) 65%, #003348);
}
.jihanki-app .v-stat-timer input:checked + .v-switch::after { background: #fff; transform: translateX(14px); }
.jihanki-app .v-stat-timer input:focus-visible + .v-switch { outline: 3px solid var(--pop-blue); outline-offset: 3px; }

.jihanki-app .v-stat-help {
  flex: none; width: 33px; height: 33px; border-radius: 50%; cursor: pointer;
  font-family: var(--vfont-display); font-weight: 800; font-size: 14px; line-height: 1;
  border: 2px solid var(--card-line); background: color-mix(in srgb, var(--card-line) 22%, var(--card));
  color: var(--pop-blue); transition: filter .15s ease;
}
.jihanki-app .v-stat-help:hover { filter: brightness(1.06); }
.jihanki-app .v-stat-help:focus-visible { outline: 3px solid var(--pop-blue); outline-offset: 2px; }
/* label kept for screen readers only — the switch reads as a plain toggle */
.jihanki-app .v-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }

/* ---- Win card: shown as a modal on solving, then inline once dismissed ---- */
.jihanki-app .v-win-overlay {
  position: fixed; inset: 0; z-index: 140; background: rgba(12, 8, 24, 0.55);
  backdrop-filter: blur(2px); animation: jihanki-win-fade .3s ease;
}
.jihanki-app .v-win-modal {
  position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 150;
  width: min(440px, calc(100vw - 28px)); max-height: calc(100vh - 32px); overflow-y: auto;
  animation: jihanki-win-in .35s cubic-bezier(.2,.9,.3,1.15);
}
.jihanki-app .v-win {
  background: var(--card); border: 3px solid var(--good); border-radius: 20px;
  padding: 15px 15px 0; box-shadow: 0 24px 50px -18px rgba(0,0,0,.55); overflow: hidden;
}
.jihanki-app .v-win.inline { border-width: 2px; box-shadow: var(--card-shadow); }

.jihanki-app .v-win-head { display: flex; align-items: center; gap: 11px; }
.jihanki-app .v-win-prod { flex: none; width: 52px; height: 52px; display: block; }
.jihanki-app .v-win-prod .prod { width: 100%; height: 100%; cursor: default; }
.jihanki-app .v-win-prod .prod .pe { font-size: 26px; }
.jihanki-app .v-win-prod .prod .pb { height: 13px; }
.jihanki-app .v-win-msg {
  flex: 1; margin: 0; text-align: center; font-family: var(--vfont-display); font-weight: 800;
  font-size: 16px; color: var(--ink);
}
.jihanki-app .v-win-btn {
  flex: none; display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  font-family: var(--vfont-display); font-weight: 800; font-size: 12.5px;
  padding: 7px 12px; border-radius: 11px; border: 2px solid #1a9c60;
  background: var(--good); color: #04220f;
  transition: filter .15s ease, transform .09s ease;
}
.jihanki-app .v-win-btn:hover { filter: brightness(1.06); }
.jihanki-app .v-win-btn:active { transform: translateY(1px); }
.jihanki-app .v-win-btn.icon { padding: 7px 10px; background: var(--card); border-color: var(--card-line); color: var(--muted); }
.jihanki-app .v-win-btn:focus-visible { outline: 3px solid var(--pop-blue); outline-offset: 2px; }
.jihanki-app .v-win-divider { border: none; border-top: 2px solid var(--card-line); margin: 13px 0 11px; }
.jihanki-app .v-win-sub {
  margin: 0 0 9px; text-align: center; font-family: var(--vfont-display); font-weight: 800;
  font-size: 12px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted);
}
.jihanki-app .v-win-games { display: flex; flex-wrap: wrap; justify-content: center; gap: 6px; }
.jihanki-app .v-win-games a {
  display: inline-flex; align-items: center; gap: 6px; text-decoration: none;
  font-family: var(--vfont-display); font-weight: 800; font-size: 12.5px; color: var(--ink);
  padding: 6px 11px; border-radius: 11px; border: 2px solid var(--card-line);
  background: color-mix(in srgb, var(--card-line) 22%, var(--card));
  transition: filter .15s ease, transform .09s ease;
}
.jihanki-app .v-win-games a:hover { filter: brightness(1.06); color: var(--ink); }
.jihanki-app .v-win-games a:active { transform: translateY(1px); }
.jihanki-app .v-win-games img { width: 14px; height: 17px; object-fit: contain; }
.jihanki-app .v-win-games img.tint { filter: invert(68%) sepia(89%) saturate(2278%) hue-rotate(157deg) brightness(104%) contrast(101%); }
.jihanki-app .v-win-games .ico { font-size: 13px; line-height: 1; }
.jihanki-app .v-win-games .new {
  background: linear-gradient(135deg, #ff6b6b, #ff5252); color: #fff;
  padding: 1px 5px; border-radius: 9px; font-size: 9px; letter-spacing: .05em;
}
/* member pitch — a full-bleed gold footer on the card, hidden for members */
.jihanki-app .v-win-join {
  display: flex; align-items: center; gap: 10px; text-decoration: none; color: #3a2800;
  background: var(--accent); border-top: 2px solid #e0a800;
  margin: 13px -15px 0; padding: 10px 15px; font-weight: 700; font-size: 13px;
}
.jihanki-app .v-win-join span { text-decoration: underline; }
.jihanki-app .v-win-join:hover { filter: brightness(1.06); color: #3a2800; }
.jihanki-app .v-win-join-icon { width: 26px; height: 26px; object-fit: contain; flex: none; }
/* no member pitch → the card still needs a bottom edge */
.jihanki-app .v-win-games:last-child { margin-bottom: 15px; }

/* ---- First-visit intro: one of the day's products asks for a restock ----
   Rides the win modal's overlay + centred box (.v-win-overlay/.v-win-modal) and
   the how-to's .jh-bubble. The product stands above its bubble, so the tail is
   always the phone layout's upward one, centred under the character. The card is
   tinted so the white bubble still reads as a bubble inside it. */
.jihanki-app .jh-intro {
  display: flex; flex-direction: column; align-items: center; gap: 14px;
  background: color-mix(in srgb, var(--card-line) 22%, var(--card)); color: var(--ink);
  border: 3px solid var(--card-line); border-radius: 20px; padding: 18px 18px 16px;
  box-shadow: 0 24px 50px -18px rgba(0,0,0,.55);
}
.jihanki-app .jh-intro-prod { flex: none; width: 92px; height: 92px; }
.jihanki-app .jh-intro-prod .picon {
  width: 100%; height: 100%; filter: drop-shadow(0 2px 2px rgba(0,0,0,.28));
}
.jihanki-app .jh-intro .jh-bubble {
  align-self: stretch; margin: 0; text-align: center; font-size: 15px; line-height: 1.45;
}
.jihanki-app .jh-intro .jh-bubble::before {
  top: auto; left: calc(50% - 9px); bottom: 100%;
  border-width: 0 9px 11px 9px; border-color: transparent transparent var(--card-line) transparent;
}
.jihanki-app .jh-intro .jh-bubble::after {
  top: auto; left: calc(50% - 7px); bottom: calc(100% - 2px);
  border-width: 0 7px 9px 7px; border-color: transparent transparent var(--card) transparent;
}
.jihanki-app .jh-intro-actions { display: flex; gap: 10px; justify-content: center; }
.jihanki-app .jh-intro-btn {
  cursor: pointer; font-family: var(--vfont-display); font-weight: 800; font-size: 14px;
  padding: 9px 20px; border-radius: 12px; border: 2px solid #e0a800;
  background: var(--accent); color: #3a2800;
  transition: filter .15s ease, transform .09s ease;
}
.jihanki-app .jh-intro-btn.alt { background: var(--card); border-color: var(--card-line); color: var(--muted); }
.jihanki-app .jh-intro-btn:hover { filter: brightness(1.06); }
.jihanki-app .jh-intro-btn:active { transform: translateY(1px); }
.jihanki-app .jh-intro-btn:focus-visible { outline: 3px solid var(--pop-blue); outline-offset: 2px; }

@keyframes jihanki-win-fade { from { opacity: 0; } to { opacity: 1; } }
@keyframes jihanki-win-in {
  from { transform: translate(-50%, -50%) translateY(-18px) scale(.94); opacity: 0; }
  to   { transform: translate(-50%, -50%); opacity: 1; }
}

.jihanki-app .legend { display: grid; grid-template-columns: 1fr 1fr; gap: 12px 18px; font-size: 12.5px; }
.jihanki-app .legend .grp { display: flex; flex-direction: column; gap: 6px; }
.jihanki-app .legend .lh { font-family: var(--vfont-display); font-weight: 800; font-size: 11px; letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.jihanki-app .legend .row { display: flex; align-items: center; gap: 7px; }
.jihanki-app .swatch { width: 14px; height: 14px; border-radius: 5px; flex: none; box-shadow: inset 0 1px 0 rgba(255,255,255,.4); }
.jihanki-app .shp { font-family: var(--vfont-mono); font-weight: 700; font-size: 10.5px; padding: 2px 7px; border-radius: 999px; background: var(--accent); color: #4a3200; border: 1.5px solid #fff; }

/* win banner */
.jihanki-app .toast {
  position: fixed; left: 50%; bottom: 26px; transform: translateX(-50%) translateY(160%);
  background: var(--good); color: #04220f; font-family: var(--vfont-display); font-weight: 800; padding: 14px 24px; border-radius: 16px;
  border: 3px solid #fff; box-shadow: 0 18px 40px -12px rgba(0,0,0,0.5); display: flex; gap: 10px; align-items: center;
  transition: transform .45s cubic-bezier(.2,.9,.3,1.3); z-index: 60; font-size: 15px;
}
.jihanki-app .toast .jp { font-family: var(--vfont-jp); }
.jihanki-app .toast.show { transform: translateX(-50%) translateY(0); }

@media (prefers-reduced-motion: reduce) { .jihanki-app * { transition: none !important; animation: none !important; } }

/* ---- Desktop: a two-column layout inside the now full-width puzzle-container
   — the machine (and the controls beneath it) on the left, the Restock/Manifest
   card in a flexible column on its right, exactly as before but now *inside* the
   container rather than overflowing it. The pair is centred in the container, so
   the machine keeps the same relationship to the sidebar however wide the screen
   gets. The machine column is capped at --jh-machine-max instead of filling all
   the new width; the sidebar column still compresses (and its contents wrap) as
   the content column narrows. Below 1024px — mobile — the vertical stack with the
   tabbed Restock/Manifest card takes over. */
@media (min-width: 1024px) {
 /* Two-column at EVERY non-mobile width. The sidebar is never sent below the
    machine on desktop: it narrows, and gives up its fourth crate column, but it
    stays beside the puzzle — so the layout reads the same at 1246px as at 2560px
    and never flips structure as the window is dragged. (It used to go single-
    column below a 750px playable box, which made the layout non-monotonic in the
    viewport: two-column at 1245px, stacked from 1246px where the rails appear and
    take 640px, then two-column again from 1390px.)
    The narrowest box this has to work in is a non-member at exactly 1246px, where
    the rails have just appeared: ~590px, which seats a ~365px machine beside a
    three-column crate. 520px is only a backstop for something narrower than that
    arithmetic allows — an unusually wide scrollbar, a future rail size — where
    the single-column tabbed layout is better than crushing both columns. */
 @container jihanki (min-width: 520px) {
  .jihanki-app .jihanki-stack {
    /* The machine's bays are square, so its height tracks its width almost 1:1
       (a 900px-wide machine measures ~901px tall). That makes the width the
       single dial for fitting the puzzle on screen, so it is driven straight off
       the height budget: the machine grows until it has used the height the
       window actually has. What it can't exceed is its grid track — on a narrow
       desktop the 640px the ad rails take leaves less width than there is
       height, and the machine is width-limited instead. The 1200px ceiling only
       guards against a freakishly tall window; the 320px floor stops a very
       short one from shrinking the puzzle into uselessness — better to scroll
       than to squint. */
    /* --jh-top is the distance from the top of the page to the top of the
       playfield: the puzzle header plus .jihanki-app's padding. jihanki.js
       measures it and writes it onto .jihanki-app, because CSS can't read it and
       the header does NOT measure the 60px its own stylesheet asks for. The
       fallback only covers the frames before the script runs.
       --jh-avail is therefore exactly the height from there to the bottom of the
       window, less a small gutter so the puzzle stops just short of the fold
       rather than touching it. The Classic/Mini mode switcher sits under the
       machine and IS in the budget — it is how a player reaches the day's other
       board, so the machine gives up the ~56px it needs rather than pushing it
       below the fold. (The premium toolbar below it is members-only and shows no
       ads, so it rides on the room the empty rails leave.) */
    --jh-gutter: 18px;
    --jh-modes: 56px;
    --jh-avail: calc(100dvh - var(--jh-top, 105px) - var(--jh-gutter));
    --jh-fit: calc(var(--jh-avail) - var(--jh-modes));
    --jh-machine-max: clamp(320px, var(--jh-fit), 1200px);
    max-width: none;
    display: grid;
    /* The machine is served first and the card takes what's left, rather than both
       being sized independently. Any cq/vw percentage for the card is wrong at
       some width — the one here was 26cqw, which computed NARROWER (289px) on an
       ad-free 1149px window than the 30cqw it replaced (334px), the opposite of
       the intent. So: the machine track grows to --jh-machine-max (the height it
       needs to reach the fold), the card is a 1fr track that absorbs whatever
       remains down to its --jh-side-min floor, and the stack's own width caps that
       remainder at --jh-side-max so the card can't run away on an ultra-wide screen.
       Grid sizes non-flexible tracks to their growth limit before feeding the fr
       track, which is exactly the priority we want. */
    --jh-colgap: clamp(16px, 2cqw, 28px);
    /* The crate's geometry, hoisted here because the card's width is derived from
       it. --jh-tile is the biggest tile the height left in the card can afford;
       the crate is otherwise limited by the card's WIDTH, so a card sized to hold
       exactly four of them is a crate that uses all of both budgets — which is
       what stops a tall screen leaving a slab of empty card under the clues. The
       520px ceiling is the point past which the card stops reading as the
       machine's companion. The machine is still served first: it only gives up
       width it doesn't need to reach the fold. */
    --jh-cardpad: clamp(10px, calc(var(--jh-avail) * 0.017), 17px);
    --jh-tile: clamp(34px, calc(var(--jh-avail) * 0.172 - 48px), 120px);
    --jh-tile-gap: clamp(5px, calc(var(--jh-avail) * 0.0075), 7px);
    /* The card wants to be a FOUR-column crate at the tile size the height budget
       allows, and it keeps four columns wherever the window can afford them.
       --jh-side-min-4 is that four-up layout at its tightest — 270px of card is
       ~233px of content box, which still holds four ~46px tiles once the crate's
       own chrome (9px padding + 2px border a side = 22px) and the card's padding
       and 2px border a side come out. It is also the floor under --jh-side-max,
       so a short window (which wants small tiles) narrows the crate inside the
       card rather than narrowing the card itself — otherwise a 1366x768 laptop
       would shrink the card past the three-column threshold with nothing
       actually wrong with it. */
    --jh-side-min-4: 270px;
    --jh-side-min-3: 210px;   /* the same sum three tiles wide: ~173px of content */
    /* The width the machine is worth protecting down to. Above it the card is
       what gives way as the screen narrows; at it, the card gives up its fourth
       crate column (--jh-side-min slides to --jh-side-min-3, and the @container
       jihankicard rule below reads the width that produces and drops to three);
       below it there is nothing left to trade and the machine finally shrinks. */
    --jh-machine-min: 580px;
    --jh-side-max: clamp(var(--jh-side-min-4),
                         calc(4 * var(--jh-tile) + 3 * var(--jh-tile-gap) + 22px
                              + 2 * var(--jh-cardpad) + 4px),
                         520px);
    /* 100cqw is the playable box (.jihanki-bounds), i.e. the viewport less the ad
       rails — the width that actually decides this. */
    --jh-side-min: clamp(var(--jh-side-min-3),
                         calc(100cqw - var(--jh-colgap) - var(--jh-machine-min)),
                         var(--jh-side-min-4));
    width: min(100%, calc(var(--jh-machine-max) + var(--jh-colgap) + var(--jh-side-max)));
    grid-template-columns: minmax(0, var(--jh-machine-max)) minmax(var(--jh-side-min), 1fr);
    column-gap: var(--jh-colgap);
    row-gap: clamp(12px, 1.6cqw, 18px);
    justify-content: center;
    align-items: start;
  }
  /* The left column is sized by its grid track now (capped at --jh-machine-max,
     free to shrink below it on narrower desktops), so nothing needs its own cap. */
  .jihanki-app .machine,
  .jihanki-app .v-win.inline { grid-column: 1; max-width: none; }
  /* The card sits beside the machine in row 1 only; the win card falls below both.
     Pin the row explicitly rather than spanning — over-spanning (span 99)
     materialises 99 row tracks, and the row-gap between every empty pair left
     ~1400px of dead space under the puzzle. */
  /* stretch, not start: the card is the machine's full-height companion, so it
     matches the grid's height rather than stopping at its own content. .jihanki-stack
     sets align-items:start for everything else, which is what keeps the machine
     itself square. */
  .jihanki-app .tabcard { grid-column: 2; grid-row: 1; align-self: stretch; }
  /* Undo/Reset to the foot of the card: the stats bar is ordered to the top
     (below), and everything else keeps DOM order. */
  .jihanki-app .v-actions {
    order: 9;
    margin-top: auto;   /* pin to the card's foot; spare height collects above */
    padding-top: clamp(8px, calc(var(--jh-avail) * 0.0125), 12px);
    border-top: 2px solid var(--card-line);
  }
  .jihanki-app .v-actions button.v-btn { padding: clamp(8px, calc(var(--jh-avail) * 0.0115), 11px) 12px; }

  /* No tabs on desktop: Restock is shown above Manifest, both always visible,
     so the panels sit in normal flow instead of overlapping in one grid cell. */
  .jihanki-app .tabcard .vtabs { display: none; }
  .jihanki-app .tabcard .vtab-panel { grid-area: auto; }
  .jihanki-app .tabcard .vtab-panel:not(.active) { visibility: visible; }
  .jihanki-app .tabcard .vtab-panels { display: block; }
  /* The clue list used to be flex:1 with flex:1 rows, so the height the card
     gains from stretching was shared out between the six rules — which read as
     ~40px of dead space between each one. The rules now keep their natural
     height and the spare space collects at the foot of the card instead (see
     margin-top:auto on .v-actions), where it reads as padding rather than as a
     gap in the list. The crate takes what it can of it by growing. */
  .jihanki-app .tabcard #tab-manifest { margin-top: 15px; }

  /* stats bar sits at the TOP of the card on desktop */
  .jihanki-app .v-stats {
    order: -1; margin-top: 0; padding-top: 0;
    margin-bottom: clamp(8px, calc(var(--jh-avail) * 0.0135), 13px);
    padding-bottom: clamp(8px, calc(var(--jh-avail) * 0.0125), 11px);
    border-top: none; border-bottom: 2px solid var(--card-line);
  }

  /* The machine's bays are large on desktop, so its contents scale up with them.
     They key off the glass, not the viewport: --jh-fit means a wide-but-short
     window gets a small machine, and a viewport-width clamp would then draw a
     72px icon in a 122px bay. Making the glass a query container puts the
     contents in step with the bays whatever sized the machine. cq units resolve
     against the NEAREST ancestor container, so these read the glass while the
     stack's own cq units above still read .jihanki-bounds.
     (The container goes on .glass rather than .machine on purpose: containment
     makes an element a stacking context, and .machine::before — the soft burst
     behind the machine — sits at z-index -1, so it would vanish behind the red
     chassis. Nothing inside .glass uses a negative z-index.)
     100cqw is the width of the 4-wide bay grid, so a bay is ~25cqw and these
     ceilings are the old fixed sizes at the 662px machine, ~20% up on the pre
     full-width layout. */
  .jihanki-app .glass { container-type: inline-size; }
  .jihanki-app .grid .prod .pe { font-size: clamp(28px, 11.5cqw, 150px); transform: translateY(-0.9cqw); }
  .jihanki-app .grid .prod .pb { height: clamp(16px, 4.1cqw, 52px); }   /* brand wordmark */
  .jihanki-app .grid .bay .bprice { font-size: clamp(9.5px, 2.05cqw, 26px); padding: 2px 8px; } /* bay price tag */

  /* The other half of fitting on screen. --jh-fit shrinks the machine, but the
     Restock/Manifest card's height is intrinsic — sixteen crate tiles and six
     clues — so on a short window it becomes the thing that overflows instead.
     Scale the card off the height budget so it shrinks in step with the machine
     rather than in steps at arbitrary breakpoints. It keys off --jh-avail, not
     --jh-fit: the card sits beside the machine AND the toolbar, so its budget is
     the whole visible height, not what's left after the toolbar. Both are set on
     .jihanki-stack above and inherit down to here.
     The 4-wide crate still keeps a minmax min of 0 so the tiles can also shrink
     below their cap as the sidebar column narrows. */
  /* The crate fills the card's width (1fr), so the tiles are as big as the
     sidebar allows — that is what makes the card read as the machine's
     counterpart rather than a narrow strip. The height budget still has the last
     word: --jh-tile is the biggest tile the remaining height can afford, and
     capping the crate's WIDTH at four of them makes it narrow (and centre)
     on a short window instead of overflowing the card. */
  /* --jh-tile / --jh-tile-gap are set on .jihanki-stack (the card's width is
     derived from them) and inherit down to here. */
  .jihanki-app .tray {
    grid-template-columns: repeat(4, minmax(0, 1fr));
    /* 1fr columns make the crate span the card, so the tiles are as big as the
       sidebar allows. Where the height can't afford tiles that big, the surplus
       width goes into the COLUMN gaps instead — the crate still reaches both
       edges of the card, but the rows don't get taller. Row gap never grows, so
       none of this feeds back into the height. A negative result (a card too
       narrow for four --jh-tile tiles) clamps to the minimum and the 1fr tracks
       shrink the tiles instead. */
    row-gap: var(--jh-tile-gap);
    column-gap: clamp(var(--jh-tile-gap), calc((100% - 4 * var(--jh-tile)) / 3), 26px);
    /* Backstop for a card far wider than the height can fill: once even a 26px
       gap can't use up the width, stop widening and centre, so the tiles stay at
       --jh-tile instead of growing past it and pushing the card past the fold. */
    max-width: calc(4 * var(--jh-tile) + 78px + 22px);
    margin-inline: auto;
  }

  /* Four columns wherever the window allows it, three when it doesn't. This does
     not decide that — --jh-side-min does, by sliding from --jh-side-min-4 to
     --jh-side-min-3 once a 270px card would cost the machine more than
     --jh-machine-min. This just reads the card width that slide produces: a
     four-up floor is 232-246px of content box and a three-up floor is 172-186px
     (the spread is --jh-cardpad tracking the height), so 200px separates them
     with room either side. A container size query measures the content box and
     cannot read custom properties, hence the literal.
     Sixteen products in threes is six rows instead of four, so the height budget
     is re-cut for the taller crate: --jh-tile is sized for four rows, --jh-tile3
     for six. */
  @container jihankicard (max-width: 200px) {
    .jihanki-app .tray {
      --jh-tile3: clamp(30px, calc(var(--jh-avail) * 0.115 - 32px), 90px);
      grid-template-columns: repeat(3, minmax(0, 1fr));
      column-gap: clamp(var(--jh-tile-gap), calc((100% - 3 * var(--jh-tile3)) / 2), 26px);
      max-width: calc(3 * var(--jh-tile3) + 52px + 22px);
    }
  }
  /* The card is its own query container so the crate can react to the width the
     GRID handed it (the three-column rule just above). inline-size containment is
     what keeps that safe: the card's width comes from its 1fr track, never from
     the crate inside it, so switching column count can't feed back into the
     track and oscillate. Nothing in the card uses cq units or a negative
     z-index, so becoming a container/stacking context changes nothing else. */
  .jihanki-app .v-card.tabcard { padding: var(--jh-cardpad); container: jihankicard / inline-size; }
  .jihanki-app .tabcard #tab-manifest { margin-top: clamp(8px, calc(var(--jh-avail) * 0.015), 15px); }
  .jihanki-app ol.clues { gap: clamp(2px, calc(var(--jh-avail) * 0.0035), 3px); }
  .jihanki-app ol.clues li {
    padding: clamp(4px, calc(var(--jh-avail) * 0.008), 8px) 7px;
    font-size: clamp(11.5px, calc(var(--jh-avail) * 0.0165), 13.5px);
    line-height: 1.35;
  }
  /* Same three-column state as the crate rule above, but it has to sit AFTER the
     rule it is narrowing: that one sets `padding` (the shorthand), which at equal
     specificity and later in source order would put the 7px side padding back.
     A card this narrow leaves the clues ~127px of text column, which wraps a rule
     like "Sunco products are always cheaper than chocolate bars" onto four lines;
     tightening the number gutter and the row's side padding (the count bubble is
     exactly 20px, so the track can be too) buys ~10px of it back without touching
     the vertical rhythm. */
  @container jihankicard (max-width: 200px) {
    .jihanki-app ol.clues li { grid-template-columns: 20px 1fr; column-gap: 6px; padding-inline: 5px; }
  }
 }
}

/* ---- Mobile (< 1024px): single-column tabbed layout. Enlarge the icons for
   touch/legibility on smaller screens. ---- */
@media (max-width: 1023px) {
  /* Tighter outer spacing (and a wider cap) so the machine grid and tiles can be
     larger on phones/tablets. */
  .jihanki-app .jihanki-stack { gap: clamp(8px, 1.5vw, 12px); max-width: 820px; }
  .jihanki-app .v-card { padding: 12px 12px 11px; }
  .jihanki-app .v-actions { gap: 6px; }
  .jihanki-app button.v-btn { padding: 11px 4px; font-size: 12.5px; }
  /* The machine grid stays full width; the Restock crate is what reflows to save
     vertical space — 5 tiles per row instead of 4, so its tiles are smaller and it
     takes fewer/shorter rows, leaving the grid + crate visible together. */
  .jihanki-app .tray { padding: 6px; gap: 5px; grid-template-columns: repeat(5, 1fr); }
  .jihanki-app ol.clues li { padding: 6px 6px; }

  /* Product icons enlarged in both the machine grid and the restock crate. */
  .jihanki-app .prod .pe { font-size: clamp(26px, 5.4vw, 36px); }
  /* Note icons enlarged; reclaim the side margins/padding and tighten the row gap.
     Top inset raised so the top row clears the price tag. The rows are centred, so a
     smaller row-gap drops the type (top) row down closer to the brand (bottom) row. */
  .jihanki-app .bay .marks { inset: 19px 5px 8px 5px; row-gap: 1px; }
  .jihanki-app .mk { padding: 6px 1px; }
  .jihanki-app .mk-type .picon,
  .jihanki-app .mk-brand .bicon { width: 97%; }
  /* +10% here, against the +25% the base rule gives desktop. */
  .jihanki-app .mk-type .picon,
  .jihanki-app .mk-brand .bicon,
  .jihanki-app .mk.cross::after { transform: scale(1.1); }
}
