/* CobyPlay loading states.
 *
 * The detail page and the drawer both paint stored catalogue data first and fill the Steam block
 * in afterwards (~4s on a cache miss). Without this, a reader sees a real-looking value replaced
 * by a different real-looking value — 總評論 963,983 篇 becoming 繁中評論 6,574 篇, or an English
 * synopsis becoming a Chinese one. A skeleton says "not yet" instead of asserting something wrong.
 *
 * Kept in its own stylesheet rather than in index.html so a regenerated prototype cannot drop it.
 */

@keyframes cp-shimmer {
  0%   { background-position: 150% 0; }
  100% { background-position: -150% 0; }
}

@keyframes cp-fade-in {
  from { opacity: 0; transform: translateY(2px); }
  to   { opacity: 1; transform: none; }
}

/* Inline placeholder standing in for text that has not arrived. color:transparent keeps any
   leftover text invisible without having to clear it, so a failed fetch can restore it. */
.cp-skel {
  display: inline-block;
  min-width: 2.5em;
  height: 0.95em;
  vertical-align: middle;
  border-radius: 4px;
  color: transparent !important;
  background-image: linear-gradient(
    90deg,
    rgba(154, 164, 187, 0.10) 20%,
    rgba(154, 164, 187, 0.30) 40%,
    rgba(154, 164, 187, 0.10) 60%
  );
  background-size: 250% 100%;
  animation: cp-shimmer 1.5s ease-in-out infinite;
}

/* Multi-line block, for a paragraph-shaped hole (the synopsis, the 口碑重點 note). */
.cp-skel-line {
  display: block;
  height: 0.8em;
  margin: 0.5em 0;
}
.cp-skel-line:last-child { width: 62%; }

/* Card-shaped placeholder for the result grid and 相近遊戲. */
.cp-skel-card {
  min-height: 172px;
  border: 1px solid var(--line, rgba(183, 198, 255, .14));
  border-radius: 11px;
  background-image: linear-gradient(
    90deg,
    rgba(154, 164, 187, 0.07) 20%,
    rgba(154, 164, 187, 0.18) 40%,
    rgba(154, 164, 187, 0.07) 60%
  );
  background-size: 250% 100%;
  animation: cp-shimmer 1.6s ease-in-out infinite;
}

/* Applied when real content lands, so it arrives rather than snapping in. */
.cp-in { animation: cp-fade-in 0.26s ease-out both; }

/* A section whose contents are being replaced: dim slightly, keep layout. */
.cp-busy { opacity: 0.55; transition: opacity 0.2s ease-out; }

@media (prefers-reduced-motion: reduce) {
  .cp-skel, .cp-skel-card { animation: none; }
  .cp-in { animation: none; }
}

/* ── 篩選 sidebar: no vertical scroll ───────────────────────────────────────────────────────
 *
 * Width and column count are left exactly as designed — 264px, single column. Only the height is
 * addressed here.
 *
 * Two separate causes. First, the panel is capped with max-height:calc(100dvh - 36px), and dvh
 * measures ~10% larger than the real viewport, so instead of scrolling it overhung the bottom of
 * the screen — the same unit problem that hid the drawer's 查看完整資料 button. Second, six blocks
 * of the design's default padding came to ~836px, more than a laptop has to give.
 *
 * !important is deliberate: this sheet is injected before the prototype's own <style>, and the
 * prototype sets max-height from a longer selector, so it wins on both specificity and order.
 * Body-prefixed selectors alone changed nothing — the measurement caught that.
 */
/* ── Everything below the next comment is DESKTOP ONLY ──────────────────────────────────────
 *
 * Under 901px the design turns .filters into a bottom-sheet drawer: position:fixed, translated
 * off-screen, visibility:hidden, revealed by .mobile-open when 篩選 is tapped. These overrides were
 * written unscoped, and `position: static !important` beat that fixed positioning — so on a phone
 * the sheet stopped being a sheet and stayed where the transform and visibility had left it, i.e.
 * a sliver at the edge of the screen. 進階篩選 was simply gone on mobile.
 *
 * The height problem they solve only exists on the desktop layout (a tall sticky column beside the
 * results), so the fix is to say so rather than to weaken the rules.
 */
@media (min-width: 901px) {
  body .filters {
    /* vh, not dvh: the dynamic unit over-reports and lets the panel overhang the viewport. */
    max-height: calc(100vh - 36px) !important;
  }
}

/* Reclaim vertical space without touching the width: six blocks multiply every spare pixel.
   Padding only — chip sizes and font sizes are left alone so nothing gets harder to read.
   Left unscoped deliberately: tighter blocks help the mobile sheet too, and padding cannot
   break its positioning. */
body .filters > .filter-block {
  margin: 4px 0 !important;
  padding: 10px 10px 11px !important;
}

body .filters > .filter-block > .filter-title {
  margin-bottom: 7px !important;
}

/* Sticky only when it genuinely fits — decided by measurement, not a breakpoint.
 *
 * A media query on height is a guess: the answer depends on the viewport AND the content AND the
 * user's zoom and font size, and it changes again every time a filter is added. So the safe state is
 * the default (grow with the page, never an inner scrollbar) and the data layer adds .cp-fits after
 * measuring, which restores the design's sticky panel on screens that can hold it.
 *
 * This also sidesteps the dvh problem for good: the cap is only ever applied when the content is
 * known to be shorter than the real viewport.
 */
@media (min-width: 901px) {
  body .filters {
    position: static !important;
    max-height: none !important;
    overflow: visible !important;
  }

  body .filters.cp-fits {
    position: sticky !important;
    top: 18px !important;
    max-height: calc(100vh - 36px) !important;
    overflow: visible !important;   /* it fits, so there is nothing to scroll */
  }
}

/* ── 遊戲影片 on the card ────────────────────────────────────────────────────────────────────
 *
 * The trailer used to be reachable only two clicks deep, inside the drawer. It sits on the cover
 * now: watching 30 seconds of footage is how anyone actually decides on a game, so it belongs
 * where the eye already is.
 *
 * BOTTOM right, not top right. It shared the top-right corner with the .tag badge, and the only
 * thing keeping them apart was an inline `right:50px` the renderer put on the badge whenever a
 * card had both — so 特別促銷 sat 10px from the edge on a card without a trailer and 50px in on a
 * card with one, and a grid of mixed cards had its badges wandering. Opposite corners need no
 * such arrangement, and the badge went back to one position.
 *
 * The selector is `.cover > .cp-play` rather than `.cp-play` to win against the design's
 * `.cover > * { position: relative; z-index: 1 }`. That rule is the same specificity and lives in
 * index.html's inline <style>, which loads after this file, so a bare `.cp-play` lost every
 * declaration below that it shares with it: the button computed to position:relative and fell out
 * of the corner into the flow, halfway down the cover's left side. It had never once rendered
 * where this file says it does — which is also why the badge was stepping aside for nothing.
 */
.cover > .cp-play {
  position: absolute;
  z-index: 3;
  bottom: 8px;
  right: 8px;
  display: grid;
  place-items: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border: 1px solid rgba(255, 255, 255, .28);
  border-radius: 50%;
  color: #071622;
  background: rgba(46, 230, 214, .92);
  font: 700 12px/1 "Space Grotesk", sans-serif;
  cursor: pointer;
  opacity: .92;
  box-shadow: 0 4px 14px rgba(3, 8, 20, .45);
  transition: transform .18s ease, opacity .18s ease, background .18s ease;
}

.cp-play:hover,
.cp-play:focus-visible {
  transform: scale(1.08);
  opacity: 1;
  background: #4ff2df;
}

/* The glyph is nudged right because a triangle's optical centre is left of its bounding box. */
.cp-play span {
  margin-left: 2px;
  pointer-events: none;
}

/* Touch targets: 34px is under the 44px guidance, and on a phone this button sits on top of the
   card's own tap area, so it gets the larger size where fingers are involved. */
@media (max-width: 900px) {
  .cp-play { width: 40px; height: 40px; opacity: 1; }
}

/* ---- language picker ---------------------------------------------------------------------
   Injected before </header> by cobyplay_console_router, so it exists on every page and survives
   a design regeneration of index.html. Styled after .sort, the other select on the site. */
.cp-lang {
  margin-left: auto;
  min-height: 34px;
  padding: 0 9px;
  border: 1px solid var(--line, rgba(183, 198, 255, .14));
  border-radius: 7px;
  color: #c6cee1;
  background: #151a31;
  outline: 0;
  font: 12px "Noto Sans TC", sans-serif;
  cursor: pointer;
}

.cp-lang:hover { color: #fff; border-color: rgba(146, 126, 255, .6); }

/* The header is a flex row whose nav has flex:1, so margin-left:auto is enough on desktop. On
   mobile the nav is display:none and the picker would otherwise sit against the wordmark. */
@media (max-width: 900px) {
  .cp-lang { margin-left: 12px; min-height: 38px; }
}

/* ---- pagination ---------------------------------------------------------------------------
 *
 * Built by the data layer (ensurePager in cobyplay-api.js), so a regenerated index.html cannot drop
 * it — the same reason 難度與步調 and 評論數 are built there.
 *
 * Chips, not a bespoke control: the numbers borrow .filter-chip's shape and the current page borrows
 * .filter-chip.selected's cyan fill, so "the thing that is on" looks the same everywhere on the page.
 */
.cp-pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 7px;
  /* margin and flex are RESET, not merely set. index.html styles the header with a bare type selector
     — `nav { margin-left:70px; flex:1 }` — which also lands on this element, and it pushed the whole
     pager 70px right of the grid it belongs under. The class out-specifies the type selector, so
     stating both properties here is what wins; leaving margin-left unmentioned inherited the header's. */
  margin: 22px 0 0;
  flex: 0 1 auto;
  padding-top: 18px;
  border-top: 1px solid var(--line, rgba(183, 198, 255, .14));
}

.cp-pager[hidden] { display: none; }   /* a single page needs no pager at all */

.cp-page-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 6px;
}

.cp-page,
.cp-page-arrow {
  min-width: 36px;
  min-height: 36px;
  padding: 0 9px;
  border: 1px solid var(--line, rgba(183, 198, 255, .14));
  border-radius: 8px;
  color: #c8d0e8;
  background: rgba(23, 29, 53, .5);
  font: 600 12px "Space Grotesk", sans-serif;
  cursor: pointer;
  transition: color .18s ease, border-color .18s ease, background .18s ease;
}

.cp-page:hover:not(.is-current),
.cp-page-arrow:hover:not(:disabled) {
  color: #fff;
  border-color: rgba(146, 126, 255, .6);
}

.cp-page.is-current {
  color: #0a1521;
  border-color: var(--cyan, #2ee6d6);
  background: var(--cyan, #2ee6d6);
  font-weight: 700;
  cursor: default;
}

.cp-page-arrow { font-size: 15px; line-height: 1; }

/* At an end, kept visible rather than removed: a control that disappears takes the row's width with
   it, so the numbers would shift sideways exactly as the reader reached for one. */
.cp-page-arrow:disabled { opacity: .34; cursor: not-allowed; }

.cp-page-gap {
  padding: 0 2px;
  color: #6f7b96;
  font: 600 12px "Space Grotesk", sans-serif;
  user-select: none;
}

/* Own row, and the only thing that states the position in words — which is what a screen reader
   announces (aria-live) and what stays readable when the numbers wrap on a narrow screen. */
.cp-page-status {
  flex: 1 0 100%;
  margin: 3px 0 0;
  color: #8491aa;
  text-align: center;
  font: 500 11px "Noto Sans TC", sans-serif;
}

/* A page turn is a request. Live buttons during one invite a queue of clicks that land out of order,
   so the pager goes quiet until the cards it asked for are on screen. */
.cp-pager.is-busy { opacity: .5; pointer-events: none; }

/* Fingers: 36px is under the 44px guidance, and this is the one control on the page a reader taps
   repeatedly. Matches what the design already does to the sidebar chips below 901px. */
@media (max-width: 900px) {
  .cp-page,
  .cp-page-arrow { min-width: 44px; min-height: 44px; }
}

/* 開始推薦 with nothing to search for. The design's .enter has no disabled state because in the
   prototype an empty box was still a valid submit — it browsed. It is not one now. */
.enter:disabled {
  opacity: .45;
  cursor: not-allowed;
  transform: none;
}

.enter:disabled:hover {
  transform: none;
  background: var(--cyan, #2ee6d6);
}

/* ---- the 搜尋範圍 the AI filled in -------------------------------------------------------
   A derived condition is selected and applied, so it wears the selected chip's own styling — the
   difference is WHO chose it, which is a smaller distinction than selected/unselected and is drawn
   as such: a cyan edge on the violet chip, plus the ✦ the site uses everywhere else for something
   the AI produced. Both are additions to .selected rather than a replacement, so a chip the reader
   goes on to keep looks like every other kept chip once the marker is dropped. */
.preference-chip.cp-auto.selected,
.date-chip.cp-auto.selected,
.cp-scope-date-chip.cp-auto.selected {
  border-color: rgba(86, 231, 217, .7);
  box-shadow: 0 0 0 1px rgba(46, 230, 214, .22);
}

.preference-chip.cp-auto.selected:before {
  content: "\2726";
  color: #9ff8e9;
}

/* Why the chips are lit, in words, above the chips themselves. Not a toast: it has to be readable
   for as long as the conditions are applied, and it carries the only control that takes them off. */
.cp-auto-notice {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 8px 0 2px;
  padding: 7px 10px;
  border: 1px solid rgba(86, 231, 217, .3);
  border-radius: 9px;
  background: rgba(24, 100, 101, .14);
}

.cp-auto-notice-text {
  flex: 1 1 auto;
  min-width: 0;
  color: #bafff5;
  font: 500 11px "Noto Sans TC", sans-serif;
  line-height: 1.5;
}

.cp-auto-notice .preference-clear { flex: 0 0 auto; color: #9ff8e9; }

/* The head stays on screen when the panel is collapsed, so this tag is the whole explanation for a
   reader who never opens it. Marked out from the intent tags beside it for that reason. */
.intent-tag.cp-auto-intent {
  color: #071622;
  border-color: transparent;
  background: rgba(46, 230, 214, .82);
  font-weight: 700;
}

/* ---- right-to-left ----------------------------------------------------------------------
   Arabic and Hebrew. The stylesheets are authored LTR with physical properties, so this covers the
   parts that would otherwise read as broken: the flex rows reverse, and the text-align and border
   sides that carry meaning flip. Anything laid out with grid keeps its column order, which is
   correct — a grid of cards has no reading direction. */
[dir="rtl"] body { text-align: right; }
[dir="rtl"] header,
[dir="rtl"] nav,
[dir="rtl"] .brand,
[dir="rtl"] .results-top,
[dir="rtl"] .preference-head,
[dir="rtl"] .filter-head,
[dir="rtl"] .search-box { flex-direction: row-reverse; }
[dir="rtl"] .cp-lang { margin-left: 0; margin-right: auto; }
[dir="rtl"] .ai-summary,
[dir="rtl"] .explore-intro { border-left: 0; border-right: 2px solid rgba(46, 230, 214, .62); text-align: right; }
[dir="rtl"] .drawer { left: 0; right: auto; border-left: 0; border-right: 1px solid rgba(161, 173, 255, .26); }
[dir="rtl"] .drawer.is-open { transform: translateX(0); }
[dir="rtl"] .drawer:not(.is-open) { transform: translateX(-104%); }
/* Page 1 belongs on the right in Arabic and Hebrew, and ‹ has to point the way it means: the glyph is
   mirrored rather than swapped for ›, so 上一頁 stays 上一頁 whichever direction the row runs. */
[dir="rtl"] .cp-pager,
[dir="rtl"] .cp-page-list { flex-direction: row-reverse; }
[dir="rtl"] .cp-page-arrow { transform: scaleX(-1); }

/* ---- community -------------------------------------------------------------------------
   Discord remains the one visible primary action. The other four destinations live in a compact,
   keyboard-dismissable menu so the header stays quiet in every locale. */
.cp-community {
  position: relative;
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 7px;
  margin-left: auto;
}

.cp-community-discord,
.cp-community-toggle {
  min-height: 36px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  font: 700 12px "Noto Sans TC", sans-serif;
  text-decoration: none;
  white-space: nowrap;
}

.cp-community-discord {
  padding: 0 13px;
  color: #fff;
  border: 1px solid #7d71ff;
  background: linear-gradient(110deg, #6f63eb, #5547cf);
}

.cp-community-toggle {
  gap: 5px;
  padding: 0 10px;
  color: #cbd4e8;
  border: 1px solid var(--line, rgba(183, 198, 255, .14));
  background: rgba(23, 29, 53, .78);
  cursor: pointer;
}

.cp-community-toggle[aria-expanded="true"] { color: #fff; border-color: rgba(151,119,255,.7); }
.cp-community + .cp-lang { margin-left: 0; }

.cp-community-menu {
  position: absolute;
  z-index: 40;
  top: calc(100% + 9px);
  right: 0;
  width: 184px;
  padding: 7px;
  border: 1px solid rgba(183, 198, 255, .24);
  border-radius: 10px;
  background: #151a31;
  box-shadow: 0 18px 42px rgba(0, 0, 0, .48);
}

.cp-community-menu[hidden] { display: none; }
.cp-community-menu a {
  min-height: 40px;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 10px;
  border-radius: 7px;
  color: #d6dced;
  text-decoration: none;
  font-size: 12px;
}
.cp-community-menu a:hover,
.cp-community-menu a:focus-visible { color: #fff; background: rgba(151,119,255,.16); }
/* The brand mark, in the accent rather than in currentColor: these are five different logos and
   letting each keep its own colour turns a tidy menu into a paint chart. Fixed basis so the names
   line up whatever the glyph's own proportions are. */
.cp-community-menu svg { flex: 0 0 16px; width: 16px; height: 16px; fill: #9ff4e8; }
.cp-community-menu a:hover svg,
.cp-community-menu a:focus-visible svg { fill: #fff; }

/* The supplied lockup has enough internal detail to carry more visual weight than the original
   30px treatment. Higher specificity is intentional: index.html owns an inline .brand-logo rule
   and is regenerated by design, so this durable layer has to win without editing that source. */
header .brand .brand-logo {
  width: auto;
  height: 40px;
  min-height: 40px;
  flex: 0 0 auto;
}

@media (max-width: 900px) {
  .cp-community { margin-left: auto; }
  .cp-community-discord { min-height: 40px; padding: 0 11px; }
  .cp-community-toggle { min-width: 40px; min-height: 40px; padding: 0 8px; }
  .cp-community-toggle span { display: none; }
  .cp-community + .cp-lang { margin-left: 0; }
}

@media (max-width: 540px) {
  .cp-community { gap: 5px; }
  .cp-community-discord { padding: 0 9px; font-size: 11px; }
  header .brand .brand-logo { height: 34px; min-height: 34px; }
}

/* ---- direct-sold contextual creative ---------------------------------------------------- */
.cp-context-ad {
  position: relative;
  overflow: hidden;
  width: 100%;
  /* Lives inside .search-flow, exactly like .ai-summary. The design expands that parent to 1280px
     on desktop, so a legacy 806px cap made the sponsored sentence visibly narrower than the
     summary it belongs to. Let the shared parent be the single width authority. */
  max-width: none;
  margin: 6px auto 0;
  display: grid;
  grid-template-columns: minmax(0, 1fr) 24px;
  align-items: stretch;
  gap: 0;
  padding: 0;
  border: 1px solid rgba(255, 177, 85, .28);
  border-radius: 10px;
  color: #eee8f6;
  background: linear-gradient(105deg, rgba(66,45,30,.34), rgba(31,28,54,.54));
  text-decoration: none;
  font-size: 13px;
  line-height: 1.45;
  transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease;
}
.cp-context-ad.has-image { grid-template-columns: 64px minmax(0, 1fr) 24px; }
.cp-context-ad[hidden] { display: none; }
.cp-context-ad:hover {
  border-color: rgba(255,177,85,.74);
  box-shadow: 0 5px 14px rgba(0,0,0,.13);
}
.cp-context-ad:focus-visible { outline: 2px solid var(--orange, #ffb155); outline-offset: 3px; }
.cp-context-ad-media {
  display: block;
  align-self: center;
  min-width: 0;
  width: 64px;
  height: 64px;
  aspect-ratio: 1;
  overflow: hidden;
  background: #0d1324;
}
.cp-context-ad-media[hidden] { display: none; }
.cp-context-ad-image {
  display: block;
  width: 100%;
  height: 100%;
  min-height: 0;
  object-fit: cover;
  transition: transform .35s ease;
}
.cp-context-ad:hover .cp-context-ad-image { transform: scale(1.012); }
.cp-context-ad-body {
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: center;
  gap: 3px;
  padding: 6px 10px;
}
.cp-context-ad-label {
  padding: 1px 5px;
  border-radius: 4px;
  color: #181007;
  background: var(--orange, #ffb155);
  font: 700 9px "Noto Sans TC", sans-serif;
}
.cp-context-ad-text {
  min-width: 0;
  text-align: left;
  font-weight: 600;
  line-height: 1.5;
}
.cp-context-ad-arrow {
  align-self: center;
  display: grid;
  place-items: center;
  width: 22px;
  height: 22px;
  margin-right: 7px;
  border: 1px solid rgba(255,177,85,.4);
  border-radius: 50%;
  color: var(--orange, #ffb155);
  transition: transform .2s ease, background .2s ease;
}
.cp-context-ad:hover .cp-context-ad-arrow { transform: translate(2px, -2px); background: rgba(255,177,85,.1); }

@media (max-width: 700px) {
  .cp-context-ad { grid-template-columns: minmax(0, 1fr) 22px; font-size: 12px; }
  .cp-context-ad.has-image { grid-template-columns: 56px minmax(0, 1fr) 22px; }
  .cp-context-ad-media { grid-column: auto; width: 56px; height: 56px; aspect-ratio: 1; }
  .cp-context-ad-body { grid-column: auto; padding: 5px 6px 5px 8px; }
  .cp-context-ad-arrow { grid-column: auto; grid-row: auto; width: 20px; height: 20px; margin-right: 4px; }
}

@media (max-width: 420px) {
  .cp-context-ad { grid-template-columns: minmax(0, 1fr) 20px; }
  .cp-context-ad.has-image { grid-template-columns: 52px minmax(0, 1fr) 20px; }
  .cp-context-ad-media { width: 52px; height: 52px; }
  .cp-context-ad-body { padding: 4px 4px 4px 7px; }
  .cp-context-ad-label { font-size: 9px; }
  .cp-context-ad-arrow { width: 18px; height: 18px; margin-right: 2px; }
}

@media (prefers-reduced-motion: reduce) {
  .cp-context-ad,
  .cp-context-ad-image,
  .cp-context-ad-arrow { transition: none; }
}

/* ---- manual AdSense placements ---------------------------------------------------------- */
.cp-filter-column { min-width: 0; align-self: start; display: grid; gap: 14px; }
/* content-box so a declared width means the width a CREATIVE gets. With the default border-box the
   1px border eats into it, and `width: 250px` offers a 250x250 creative only 248px — which
   fittedSizes would then correctly, and uselessly, reject. */
.cp-display-ad { box-sizing: content-box; }
.cp-display-ad {
  position: relative;
  min-width: 0;
  display: grid;
  /* Two rows: the label owns one, the creative owns the other. It used to be one centred cell with
     the label absolutely positioned on top, which put our grey 9px word ON the advertiser's canvas
     — measured 51px inside a 1067x99 banner. Two problems with that, and neither is cosmetic:
     the advertiser paid for the whole canvas, and the label's legibility became a function of
     whatever artwork happened to be in that corner. Live examples on the same page: readable over a
     white text ad, invisible over a dark 250x250 game creative. A label nobody can read does not
     make the ad 「clearly distinguishable」, which is the entire requirement it exists to meet. */
  grid-template-rows: auto 1fr;
  justify-items: center;
  align-content: start;
  overflow: hidden;
  border: 1px solid rgba(183,198,255,.1);
  border-radius: 10px;
  background: rgba(10,14,29,.5);
}
.cp-display-ad-label {
  justify-self: start;
  padding: 3px 6px 2px;
  color: #707b94;
  font: 500 9px "Noto Sans TC", sans-serif;
  line-height: 1.2;
}
/* An empty response hides the whole wrapper, label and border included — see slotRenderEnded.
   !important because the two device-specific placements set `display` from a media query, so a
   plain [hidden] rule would lose to them and leave the box on exactly the screen it belongs to. */
/* Not a fallback: this is the state every display box starts in, and only a non-empty render takes
   it off. `hidden` alone would lose to the `display: grid` above, and to the media query that swaps
   the filter and mobile boxes on and off by device. */
.cp-display-ad[hidden] { display: none !important; }
.cp-display-ad-frame { min-width: 0; }
.cp-display-ad-filter { width: 250px; max-width: 100%; min-height: 250px; margin: 0 auto; }
.cp-display-ad-results { grid-column: 1 / -1; min-height: 90px; margin: 4px 0; }
.cp-display-ad-detail { width: 100%; min-height: 90px; margin: 38px 0 0; }
.cp-display-ad-mobile { display: none; min-height: 100px; margin: 16px 0 0; }

@media (min-width: 901px) {
  body .cp-filter-column > .filters {
    position: static !important;
    max-height: none !important;
    overflow: visible !important;
  }
  body .cp-filter-column.cp-fits { position: sticky; top: 18px; }
}

@media (max-width: 900px) {
  .cp-display-ad-filter { display: none; }
  .cp-display-ad-mobile { display: grid; }
  .cp-display-ad-results { min-height: 100px; }
  .cp-display-ad-detail { min-height: 100px; margin-top: 28px; }
}

/* Phones: the three horizontal placements run to the viewport edge.
   Measured on a 320px phone: the shell's 14px padding leaves the bottom unit 290 and the results
   unit 258, so fittedSizes drops every standard size and asks only for 234x60 — the narrowest rung
   and the thinnest demand. Bleeding to the edge gives a full 320, which is where mobile display
   demand actually is (320x50 and 320x100 are the two standard mobile banners, and they were
   already declared in GAM).

   100vw rather than cancelling a known padding, so this keeps working if the shell's padding
   changes. Side borders and the radius come off because a strip that touches both edges with a
   border down the side reads as a rendering fault rather than a deliberate full-bleed. With
   box-sizing: content-box and no side borders, the content box is exactly 100vw. */
@media (max-width: 480px) {
  .cp-display-ad-results,
  .cp-display-ad-detail,
  .cp-display-ad-mobile {
    width: 100vw;
    max-width: 100vw;
    margin-left: calc(50% - 50vw);
    margin-right: calc(50% - 50vw);
    border-left: 0;
    border-right: 0;
    border-radius: 0;
  }
  /* The label keeps the padding the border used to provide, or it sits flush against the screen. */
  .cp-display-ad-results > .cp-display-ad-label,
  .cp-display-ad-detail > .cp-display-ad-label,
  .cp-display-ad-mobile > .cp-display-ad-label { padding-left: 14px; }
}

[dir="rtl"] .cp-community { margin-left: 0; margin-right: auto; }
[dir="rtl"] .cp-community-menu { right: auto; left: 0; }
[dir="rtl"] .cp-display-ad-label { justify-self: end; }

/* A game named inside the AI 摘要 opens that game. Styled as emphasis inside the sentence rather
   than as a control: it sits in running prose, so a button's own box, font and colour are all
   wrong here and are reset back to the text around it. The underline is what says it is clickable
   — colour alone would not, and the summary already uses colour for its own accent. */
.cp-summary-game {
  appearance: none; margin: 0; padding: 0; border: 0; background: none;
  font: inherit; color: #a8fff2; cursor: pointer;
  text-decoration: underline; text-decoration-color: rgba(46, 230, 214, .45);
  text-underline-offset: 2px; text-decoration-thickness: 1px;
  transition: color .15s, text-decoration-color .15s;
}
.cp-summary-game:hover { color: #d6fffa; text-decoration-color: rgba(46, 230, 214, .9); }
.cp-summary-game:focus-visible { outline: 2px solid rgba(46, 230, 214, .8); outline-offset: 2px; border-radius: 2px; }

/* 平台 and 上線日期 promoted out of the collapsible panel — see ensureQuickScope. They are the two
   conditions a reader changes per search rather than per session, so they stay on screen while the
   other five stay behind 「設定搜尋範圍」. */
/* `hidden` does not survive the design's own `.preference-group { display: grid }` in index.html:
   the attribute carries nothing but the UA's `display: none`, and any class rule outranks it. Every
   group this layer creates starts hidden and is revealed by reconcileChips once the catalogue has
   values for it, so without this a group whose facet is empty renders as a heading with nothing
   under it. 特展 did exactly that against a catalogue that has not been rebuilt yet — caught on
   screen, not by a test, because the DOM said hidden:true the whole time. 語音 has been one empty
   facet away from the same thing since it was added. */
.preference-group[hidden] { display: none; }
/* 「需要本體：Elden Ring」 on an expansion's card. Dimmer than the genres above it and one line
   only — it is a caveat, not a selling point, and a card that shouts its requirement reads as a
   warning about the game rather than a note about what else you need. */
.cp-requires {
  margin-top: 3px;
  font-size: 11px;
  line-height: 1.3;
  color: #9aa3bd;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.cp-quick-scope { display: grid; gap: 9px; padding: 10px 3px 0; }
.cp-quick-scope .preference-group { margin: 0; }
/* One row each, scrolled rather than wrapped. Eleven release chips wrap to three lines on a phone,
   which would push the search box itself below the fold — the thing this panel sits above. */
.cp-quick-scope .preference-list { display: flex; flex-wrap: wrap; gap: 6px; }
@media (max-width: 900px) {
  .cp-quick-scope .preference-list {
    flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none;
    padding-bottom: 2px; -webkit-overflow-scrolling: touch;
  }
  .cp-quick-scope .preference-list::-webkit-scrollbar { display: none; }
  .cp-quick-scope .preference-chip,
  .cp-quick-scope .cp-scope-date-chip { flex: 0 0 auto; }
}
