/* The hop pad (DESIGN §15.2, UIKIT §2.4, §3, §4, §7, §18, §24, §32; boards toolbar-states, hero-desktop,
   catalog-desktop, catalog-mobile, hero-mobile). Always a CSS body with visible DOM text: kit.css draws
   every component (.k-pad, .k-dock__body, .k-key, .k-chip, .k-lcd, .k-sheet, .k-tip, .k-scrim); this file
   places them per mode. The GPU "hop-pad" surface (portal/src/toolbar/hop-pad.js) paints only the goo
   outside the body. Modes: lg (>= 1200) and md (760-1199) pads in the page (sticky at top 10 when stuck),
   phones (< 760, or a short coarse landscape) = the bottom dock + the header keys + the footer Privacy.
   JS state (portal/src/toolbar/pad.js): .is-stuck (stuck at top 10: the scrim, the GPU strands, clipTop),
   .is-docked (stuck, or within 76 px of it: the stuck layout, the cluster inside the pad), .is-lcdkey (the LCD key
   stands in for the LCD; it stays while the search sheet is open), .is-lcdheld (the LCD holds focus where the layout
   wants the LCD key: it stays visible until focus leaves it), .is-hidden (the dock is away), data-mode="lg|md|phone",
   kit classes .k-dock / .k-dock__body; #portal-header.is-hub (lg / md until the pad docks: the cluster rides the hub's
   top right); #motion-more.is-open (its menu is open: the key shows).
   Owner: toolbar (design/CONTRACTS.md). Loaded by site/index.html; portal/build.sh versions it. */

/* ---- the pad in the page: in flow under the hub (the new hub docks it 104 px into its bottom, §11.2),
   sticky at top 10 / left 36 / right W - 36 (pin §18.4) ---------------------------------------------- */
#catalog-sticky-sentinel { position: relative; height: 1px; margin: 18px 0 -1px; pointer-events: none; }
#portal-page:has(.hub-featured-window) #catalog-sticky-sentinel { margin-top: -104px; }
/* pad.js keepPage(): while a chip or a query is on, the page keeps the height its results view needs (the grid's
   heading on its line under the stuck pad / the sheet, a screen below it): an invisible box from the sentinel down
   (--hop-hold px), so a grid that shrinks as someone types never clamps the scroll position: the stuck pad stays
   stuck and the field in place. A longer grid makes it moot; it goes when the filter is cleared. */
#catalog-sticky-sentinel::after {
  content: ""; position: absolute; left: 0; top: 0; width: 1px; height: var(--hop-hold, 0px);
  visibility: hidden; pointer-events: none; overflow-anchor: none;
}
.hop-pad {
  --hp-left: max(36px, calc(env(safe-area-inset-left, 0px) + 16px));
  --hp-right: max(36px, calc(env(safe-area-inset-right, 0px) + 16px));
  position: sticky; z-index: var(--z-catalog-toolbar, 5); top: 10px;
  margin: 0 var(--hp-right) 34px var(--hp-left);
  pointer-events: none;
}
.hop-pad__body { pointer-events: auto; }
/* the row sits 1 px over the face's centre (the lip is below it; design/src/parts.js K.gadget) */
.hop-pad__body.k-pad { padding-bottom: 2px; }
/* DESIGN §14: while stuck, focus and paging keep content clear of the pad (pad + gutter + scrim)... */
html:has(#catalog-toolbar.is-stuck:not(:focus-within)) { scroll-padding-top: 125px; }
/* ...but the pad's own controls sit inside that band and never move with the page, so nothing they do may scroll it:
   while focus is in the pad the band is off (typing would reveal the caret "under" it: the page jumped 87 px per key),
   and their scroll area is shifted below the band, so focusing one ("/", Tab, the x, a chip) never scrolls the page */
#catalog-toolbar.is-stuck :is(a, button, input, [tabindex]) { scroll-margin-top: -125px; scroll-margin-bottom: 125px; }

/* HOP (UIKIT §2.4): the far left, never beside search; the caption "Random game" in the hero state */
.hop-pad #hop-random { flex: none; }
.hop-pad #hop-random[aria-busy="true"] { --k-sh-glow: 0 0 18px 3px var(--k-glow); cursor: progress; }
.hop-pad #hop-random[aria-busy="true"]::after { display: none; } /* the shuffle is its busy look, no dots */
.hop-pad__caption { flex: none; margin: 0 1px 0 -1px; }
.hop-pad__div { margin-left: 0; }
/* the LED: kit.css blinks it; while the GPU paints, the GPU drives it (data-lit) so its glow agrees */
html.gpu-ready .hop-pad .k-led[data-lit] { animation: none; }
html.gpu-ready .hop-pad .k-led[data-lit="1"] {
  background: radial-gradient(circle at 36% 34%, #fff 0 1.3px, transparent 1.7px),
    radial-gradient(circle, var(--goo-5) 0 4.1px, transparent 4.6px), var(--gadget-lip);
  box-shadow: 0 0 7px 2px var(--k-glow);
}
html.gpu-ready .hop-pad .k-led[data-lit="0"] {
  background: radial-gradient(circle at 36% 34%, rgba(255, 255, 255, .4) 0 1.3px, transparent 1.7px),
    radial-gradient(circle, var(--goo-9) 0 4.1px, transparent 4.6px), var(--gadget-lip);
  box-shadow: none;
}

/* chips (UIKIT §3): the scroller fills the middle; scroll keys sit on its fades at the ends */
.hop-pad__chips { position: relative; flex: 1 1 0; min-width: 0; display: flex; align-items: center; align-self: stretch; margin: 0 -2px 0 -6px; }
.hop-pad__chips > .k-chips { flex: 1 1 auto; }
.hop-pad .k-chips-scroll { position: absolute; z-index: 1; --k-h: 32px; top: calc(50% - 19px); opacity: 0; pointer-events: none; transition: opacity 120ms linear; }
/* at rest the row just fades out at its ends (boards); the keys show while the row is hovered or focused */
.hop-pad__chips:hover .k-chips-scroll, .hop-pad__chips:focus-within .k-chips-scroll { opacity: 1; pointer-events: auto; }
.hop-pad .k-chips-scroll--left { left: 4px; }
.hop-pad .k-chips-scroll--right { right: 4px; }
/* the chevron points where the row goes (a down chevron turned a quarter) */
.hop-pad .k-chips-scroll--right > svg { transform: rotate(-90deg); }
.hop-pad .k-chips-scroll--left > svg { transform: rotate(90deg); }

/* the LCD search (UIKIT §4): 300 x 38 at the right of the chips; the LCD key stands in for it on phones
   and on the stuck md pad below 1000 px (it opens the sheet). Hidden LCD = still in the tree: the
   readout keeps announcing (never display:none) */
.hop-pad__lcd { flex: none; width: 316px; margin: 0 2px 0 6px; }
.hop-pad__lcd.has-query .k-lcd__kbd, html[data-slash="off"] .hop-pad__lcd .k-lcd__kbd { display: none; }
.hop-pad__lcdkey { display: none; flex: none; }
.hop-pad__lcdkey .k-lcd__icon { width: 24px; height: 24px; }
.hop-pad__badge { position: absolute; right: -9px; top: -9px; }
.hop-pad.is-lcdkey .hop-pad__lcdkey { display: inline-flex; }
.hop-pad.is-lcdkey:not(.is-lcdheld) .hop-pad__body > .hop-pad__lcd {
  position: absolute !important; width: 1px; height: 1px; min-height: 0; margin: -1px; padding: 0; border: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; box-shadow: none;
}
/* a field someone types in keeps its place when the layout changes under it (pad.js .is-lcdheld: the narrow md pad
   docks, an on-screen keyboard shrinks a tablet to the phone dock): the LCD key waits until focus leaves it */
.hop-pad.is-lcdkey.is-lcdheld .hop-pad__lcdkey { display: none; }

/* ---- the account cluster: the hero's top-right header while unstuck (§11.2: y 22, gap 14, right
   max(36, inset + 16)), the pad's right end while stuck; phones: #portal-header. Fixed here only until
   account-link.js / pad.js put it in #portal-header (the boot frame: the same place at scroll 0) ---------- */
#account-shell {
  position: fixed; z-index: var(--z-account-shell, 6);
  top: calc(max(22px, calc(env(safe-area-inset-top, 0px) + 12px)) + var(--nogpu-h, 0px));
  right: max(36px, calc(env(safe-area-inset-right, 0px) + 16px));
  pointer-events: auto; color: var(--text-hi); font-family: var(--f-body);
}
#account-actions { display: flex; align-items: center; gap: 14px; }
/* the cluster's entrances fill backwards only: a finished transform animation left applied would make #account-actions
   the containing block of the fixed Reduce-motion menu inside it */
.hop-pad:not(.is-docked) #account-actions { animation: hop-pad-fade 160ms linear backwards; }
#account-actions > .k-key { flex: none; }
/* keys with an icon and words (UIKIT §2.2: a 24 mono icon in a 44 px lead, label 16, padding 18; the hero header's
   Privacy is 122 wide, §11.2). The stuck pad's are compact (design/src/parts.js K.gadget: Privacy 15 px). */
#privacy-link { padding: 0 18px 0 10px; gap: 10px; }
#privacy-link::before {
  content: ""; display: block; flex: none; width: 24px; height: 24px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cg stroke-linejoin='round' stroke-linecap='round'%3E%3Cpath d='M12,3 L19.5,6 V11.5 C19.5,16.5 16,19.8 12,21.2 C8,19.8 4.5,16.5 4.5,11.5 V6 Z' fill='%23FFF4E0' stroke='%23FFF4E0' stroke-width='2.3'/%3E%3Cpath d='M8.6,12 L11,14.4 L15.6,9.4' fill='none' stroke='%233F2C6B' stroke-width='2.4'/%3E%3C/g%3E%3C/svg%3E") center / 24px 24px no-repeat;
}
#account-link { padding: 0 18px 0 10px; gap: 10px; }
/* the stuck pad's compact word keys (never the icon form: that one has no padding and no words) */
.hop-pad.is-docked #privacy-link:not(.k-key--icon) { padding: 0 13px 0 11px; gap: 6px; font-size: 15px; }
.hop-pad.is-docked #account-link:not(.k-key--icon) { padding: 0 18px 0 12px; gap: 7px; }
#account-link[aria-busy="true"]:not(.k-key--icon) { gap: 6px; }
#motion-toggle .hop-ico { display: none; }
/* "More settings" (#motion-more, the menu button of the Reduce-motion menu; DESIGN §14, §15.5): visually hidden until
   focus reaches it (Tab; or core's focus fallback after a hop: the header leads the page) or it opened its menu
   (pad.js .is-open), then a plum icon key (40, 44 coarse) left of Reduce motion (the cluster grows to the left: no key
   beside it moves). A pointer cannot reach the hidden key: right-click / long press Reduce motion */
#motion-more { flex: none; }
#motion-more:not(:focus):not(.is-open) {
  position: absolute; width: 1px; height: 1px; min-width: 0; margin: -1px; padding: 0; border: 0; overflow: hidden;
  clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; box-shadow: none;
}
/* icon keys (md, the stuck pad, phones): the words stay the accessible name (label in name) + a tooltip; the
   shield / user icon sits centred in the 40 (44 coarse) px key */
#privacy-link.k-key--icon, #account-link.k-key--icon { width: var(--k-h); padding: 0; gap: 0; justify-content: center; }
#privacy-link.k-key--icon { font-size: 0; }
#account-link.k-key--icon .hop-pad__label, #motion-toggle.k-key--icon .hop-pad__label {
  position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}
#motion-toggle.k-key--icon .k-switch__track { display: none; }
#motion-toggle.k-key--icon .hop-ico--rest { display: block; }
#motion-toggle.k-key--icon[aria-checked="true"] .hop-ico--rest { display: none; }
#motion-toggle.k-key--icon[aria-checked="true"] .hop-ico--on { display: block; }
/* #account-status: "Passkey canceled." and sign-in errors, a cream note under the account key */
.hop-pad__status {
  position: absolute; z-index: 2; top: calc(100% + 14px); right: 0; box-sizing: border-box;
  width: max-content; max-width: min(300px, calc(100vw - 32px)); margin: 0; padding: 8px 12px 9px;
  border: 2.4px solid var(--ink); border-radius: var(--r-tooltip); background: var(--cream);
  color: var(--text-ink); font: 600 14px/1.3 var(--f-body); box-shadow: 0 3px 0 var(--ink); pointer-events: auto;
}

/* ---- stuck (lg / md): glued to the top edge; the cluster slides in from the right (240 ms glide); an
   opaque scrim under it (void-0 62 %, catches the pointer) down to the clip line: pad + 9 lip + 20 gutter.
   In gpu-paint mode the GPU paints that band under its goo and the DOM scrim only catches the pointer. */
.hop-pad.is-docked .hop-pad__caption { display: none; }
.hop-pad.is-docked #account-shell { position: static; margin-left: 4px; }
.hop-pad.is-docked #account-actions { gap: 12px; animation: hop-pad-slide 240ms var(--ease-glide) backwards; }
.hop-pad.is-docked .hop-pad__status { top: calc(100% + 18px); right: 40px; }
.hop-pad__scrim {
  display: none; position: absolute; z-index: -1; top: -10px;
  left: calc(-1 * var(--hp-left)); right: calc(-1 * var(--hp-right)); height: calc(10px + 76px + 9px + 20px);
}
.hop-pad.is-stuck .hop-pad__scrim { display: block; }
html.gpu-paint .hop-pad__scrim { background: transparent; }
@keyframes hop-pad-slide { from { opacity: 0; transform: translateX(32px); } }
@keyframes hop-pad-fade { from { opacity: 0; } }

/* ---- the pad's tooltip (UIKIT §24): placed by pad.js (fixed, above the key; below while stuck) ---- */
.hop-tip { position: fixed; z-index: var(--z-skip-link, 10); left: 0; top: 0; pointer-events: auto; }
.hop-tip::before { left: var(--hop-tip-arrow, calc(50% - 6.5px)); }

/* ---- the "More settings" menu (DESIGN §15.5, §14): Show birb + Slash key search (menuitemcheckbox items in the switch
   look), a small gadget panel under the key that opened it (pad.js places it: #motion-more, or right-click, long
   press, Shift+F10 on Reduce motion) ------------------------------------------------------------------- */
.hop-menu {
  position: fixed; z-index: var(--z-skip-link, 10); left: 0; top: 0; box-sizing: border-box;
  display: grid; gap: 4px; width: max-content; max-width: calc(100vw - 16px); padding: 10px 18px 12px 14px;
  border: 3px solid var(--ink); border-radius: var(--r-toast, 20px); background: var(--gadget-face);
  box-shadow: inset 0 3px 0 -1px var(--gadget-strip), 0 6px 0 var(--ink), 0 18px 28px rgba(0, 0, 0, .45);
  pointer-events: auto; animation: hop-pad-fade 120ms linear both;
}
.hop-menu__switch { width: 100%; }
.hop-menu__text { display: grid; gap: 2px; color: var(--text-hi); }
#motion-toggle { -webkit-touch-callout: none; }

/* ---- the search sheet (UIKIT §32): non-modal, no scrim; the LCD moves in beside Cancel and its readout onto the
   line under them (pad.js), so the field gets the LCD's whole width: about ten characters on a 320 px phone ---- */
.hop-sheet { pointer-events: auto; }
.hop-sheet__row > .hop-pad__lcd { flex: 1 1 auto; width: auto; min-width: 0; margin: 0 0 0 4px; }
.hop-sheet .k-lcd__kbd { display: none; } /* the field has focus in the sheet: no "/" keycap */
.hop-sheet__cancel { flex: none; width: 86px; }
.hop-sheet__line { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; margin: 14px 6px 0; }
.hop-sheet__line > .k-lcd__readout { align-self: auto; margin: 0; padding: 0; border: 0; }

/* ---- md (760-1199): no caption, icon keys (pad.js), a 220 px LCD in the stuck pad from 1000 px ---- */
@media (min-width: 760px) and (max-width: 1199.98px) {
  .hop-pad__caption { display: none; }
  #account-actions { gap: 12px; }
  .hop-pad.is-docked .hop-pad__lcd { width: 220px; }
  /* while someone searches in it (focus, or a query kept) it opens up to 300 px, the chips give way: with the readout
     back beside the query the field keeps about ten characters in view (220 px left it 38) */
  .hop-pad.is-docked .hop-pad__lcd:is(:focus-within, .has-query) { width: 300px; }
  .hop-pad.is-docked .hop-pad__lcd .k-lcd__kbd { display: none; }
  /* the 220 px LCD: at rest "Search games" gets the room and the readout is a live region only (never display:none;
     the grid below shows the games); while a query is typed the count shows again */
  .hop-pad.is-docked .hop-pad__lcd:not(.has-query) .k-lcd__readout {
    position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; border: 0; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
  }
}
@media (pointer: coarse) {
  .hop-pad .k-chips-scroll { --k-h: 44px; top: calc(50% - 25px); }
}

/* ---- phones: the dock (DESIGN §15.2, board toolbar-states 06): one 64 px row at the bottom, x 12 ... W - 12,
   bottom max(12, inset + 6); LCD key 44 · chips 44 (scroll + fade) · HOP 52. Its face top is
   844 - 70 - 12 = 762 on a 390 x 844 phone (pin §18.4). The header keys and the footer Privacy link. ---- */
.hop-header { display: none; }
.hop-footer { display: none; }
/* lg / md until the pad docks: pad.js puts the cluster here, at the hub's top right (§11.2: y 22, right 36), so it
   scrolls away with the hub like the phone header instead of floating fixed over the hub's text, keys and portals
   (the small portals at 1440 x 900, the md portrait column); it comes back in the pad's right end when the pad docks */
.hop-header.is-hub {
  display: block; position: absolute; z-index: var(--z-account-shell, 6);
  top: max(22px, calc(env(safe-area-inset-top, 0px) + 12px)); right: max(36px, calc(env(safe-area-inset-right, 0px) + 16px));
  pointer-events: auto;
}
.hop-header.is-hub #account-shell { position: relative; top: auto; right: auto; }
@media (max-width: 759.98px), (pointer: coarse) and (max-height: 499.98px) {
  #catalog-sticky-sentinel, #portal-page:has(.hub-featured-window) #catalog-sticky-sentinel { margin: 0 0 -1px; }
  .hop-pad {
    position: fixed; top: auto; margin: 0; padding-bottom: 6px;
    left: max(12px, calc(env(safe-area-inset-left, 0px) + 8px)); right: max(12px, calc(env(safe-area-inset-right, 0px) + 8px));
    bottom: max(12px, calc(env(safe-area-inset-bottom, 0px) + 6px));
  }
  /* before the controller swaps .k-pad for .k-dock__body */
  .hop-pad__body { height: 64px; min-height: 64px; padding: 0 12px 0 10px; gap: 10px; }
  .hop-pad__body.k-dock__body { padding: 0 12px 0 10px; gap: 10px; }
  .hop-pad__caption, .hop-pad__div, .hop-pad__scrim, .hop-pad .k-chips-scroll { display: none !important; }
  .hop-pad:not(.is-lcdheld) .hop-pad__body > .hop-pad__lcd {
    position: absolute !important; width: 1px; height: 1px; min-height: 0; margin: -1px; padding: 0; border: 0; overflow: hidden;
    clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; box-shadow: none;
  }
  .hop-pad__lcdkey { display: inline-flex; order: -1; }
  /* a held field (an on-screen keyboard made a tablet's viewport a phone's while someone typed): it takes the place of
     the LCD key and the chips, right above the keyboard, until focus leaves it */
  .hop-pad.is-lcdheld .hop-pad__chips { display: none; }
  .hop-pad.is-lcdheld .hop-pad__body > .hop-pad__lcd { flex: 1 1 auto; width: auto; min-width: 0; margin: 0 4px 0 2px; }
  .hop-pad__chips { order: 0; margin: 0 -6px; }
  .hop-pad #hop-random { order: 1; --k-h: 52px; }
  .hop-pad .k-chip { --k-h: 44px; }
  .hop-pad .k-chips { padding: 5px 10px 12px; gap: 8px; }
  /* until pad.js (or account-link.js at parse end) moves it into #portal-header: the same place, fixed */
  .hop-pad #account-shell {
    position: fixed; top: max(12px, calc(env(safe-area-inset-top, 0px) + 8px)); right: max(16px, calc(env(safe-area-inset-right, 0px) + 8px));
  }
  .hop-pad #account-actions, .hop-header #account-actions { gap: 8px; }
  .hop-header #privacy-link, .hop-pad #privacy-link { display: none; } /* phones: Privacy lives in the page footer */
  /* the header keys: Reduce motion + account (44 icon keys) at the top right, on the page (they scroll with
     it, like the wordmark beside them); toasts start below them. Also before pad.js drops .is-hub (a window or a
     tablet that just became phone-sized: the header is right from the first frame, whatever the script's timing) */
  .hop-header, .hop-header.is-hub {
    display: block; position: absolute; z-index: var(--z-account-shell, 6);
    top: max(12px, calc(env(safe-area-inset-top, 0px) + 8px)); right: max(16px, calc(env(safe-area-inset-right, 0px) + 8px));
    pointer-events: auto;
  }
  .hop-header #account-shell { position: static; top: auto; right: auto; }
  html.portal-active { --k-toast-top: calc(max(12px, calc(env(safe-area-inset-top, 0px) + 8px)) + 52px); }
  /* Privacy in the footer of the page: a visible link >= 44 tall, clear of the dock */
  .hop-footer {
    display: flex; justify-content: center;
    padding: 12px 16px calc(64px + 6px + 28px + max(12px, calc(env(safe-area-inset-bottom, 0px) + 6px)));
  }
  .hop-footer #privacy-link { --k-h: 44px; pointer-events: auto; }
  html.portal-active { scroll-padding-bottom: calc(64px + 6px + 16px + max(12px, calc(env(safe-area-inset-bottom, 0px) + 6px))); }
  /* the dock hides on scroll-down: kit.css's CSS fallback (180 ms slide; a 120 ms fade under Reduce motion)
     unless the GPU loop drives the kit spring on .k-dock__body (hop-pad.js), so its drips follow */
  .hop-pad__body { transition: transform var(--t-dock-hide) var(--ease-glide), opacity var(--t-dock-hide) linear; }
  .hop-pad.is-hidden .hop-pad__body { transform: translateY(calc((100% + 30px) * var(--k-motion))); opacity: var(--k-motion); pointer-events: none; }
  html.gpu-ready[data-motion="full"] .hop-pad .hop-pad__body { transition: none; }
  html.gpu-ready[data-motion="full"] .hop-pad.is-hidden .hop-pad__body { transform: none; opacity: 1; }
  html[data-motion="reduce"] .hop-pad .hop-pad__body { transition: opacity 120ms linear !important; }
}
@media (max-width: 759.98px) and (prefers-reduced-motion: reduce), (pointer: coarse) and (max-height: 499.98px) and (prefers-reduced-motion: reduce) {
  html:not([data-motion="full"]) .hop-pad .hop-pad__body { transition: opacity 120ms linear !important; }
}

/* ---- forced colours: kit.css paints the parts in system colours; the scrim becomes a solid band ---- */
@media (forced-colors: active) {
  .hop-pad__scrim { background: Canvas; }
  .hop-menu { border-color: CanvasText; }
  #privacy-link::before { display: none; }
  .hop-pad__status { border-color: CanvasText; }
}
