/* Featured cards (home): cover band in the category's tone with the app
   icon floating over it, then chip + name + developer + highlight below.
   The whole card is the link — no separate call-to-action button. */

/* Fixed 4-up row (the view caps the section at 4 items). */
.featured-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 16px;
}

.featured-card {
    display: flex;
    flex-direction: column;
    min-width: 0;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-card);
    overflow: hidden;
    transition: border-color 140ms ease, box-shadow 140ms ease;
}

.featured-card:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-raised);
}

.featured-card:hover .featured-name {
    color: var(--accent);
}

.featured-cover {
    display: flex;
    align-items: center;
    justify-content: center;
    aspect-ratio: 16 / 8;
    background: var(--tone-bg, var(--surface-2));
}

.featured-cover-icon {
    width: 72px;
    height: 72px;
    border-radius: 18px;
    object-fit: cover;
    background: var(--surface);
    box-shadow: var(--shadow-raised);
}

.featured-body {
    display: flex;
    flex-direction: column;
    gap: 5px;
    padding: 16px 18px 18px;
}

.featured-body .cat-chip {
    margin-bottom: 4px;
}

.featured-name {
    font-size: 16.5px;
    font-weight: 700;
    letter-spacing: -0.01em;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
    transition: color 140ms ease;
}

.featured-dev {
    font-size: 13px;
    color: var(--text-muted);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.featured-highlight {
    margin-top: 6px;
    font-size: 13.5px;
    line-height: 1.55;
    color: var(--text-muted);
    display: -webkit-box;
    -webkit-line-clamp: 3;
    line-clamp: 3;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

/* Responsive ---------------------------------------------------------------- */

@media (max-width: 1000px) {
    .featured-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 560px) {
    .featured-grid {
        grid-template-columns: 1fr;
    }

    .featured-cover {
        aspect-ratio: 16 / 6;
    }
}

/* ─────────────────────────────────────────────────────────────────────────
   PremiBox hero (home, first section).

   Built on the tokens in core.css — no literal colours outside the phone
   mock-up and the two decorative orbs, where the alpha is doing the work.
   That is what lets the whole section follow the light/dark switch, and lets
   a theme (assets/themes/{name}/css/home.css) restyle it by redefining
   tokens alone rather than by overriding every rule below.
   ───────────────────────────────────────────────────────────────────────── */

.pb-hero {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1.15fr) minmax(0, 0.85fr);
    align-items: center;
    gap: 40px;
    margin: 4px 0 34px;
    padding: 36px 40px;
    overflow: hidden;
    border: 1px solid var(--accent-soft-border);
    border-radius: var(--radius-lg);
    /* Two stops, both token-derived: the tint reads as brand in light mode and
       as a lifted panel in dark, without needing a second set of rules. */
    background:
        radial-gradient(120% 140% at 88% 8%, var(--accent-soft) 0%, transparent 58%),
        linear-gradient(160deg, var(--surface) 0%, var(--surface-2) 100%);
    box-shadow: var(--shadow-card);
}

/* A faint grid fading out from the top-right corner — same trick as the video
   poster's mesh, but drawn from the --border token so it follows the theme.
   It gives the tinted corner a sense of surface instead of a flat wash. */
.pb-hero::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 0;
    pointer-events: none;
    background-image:
        linear-gradient(var(--border) 1px, transparent 1px),
        linear-gradient(90deg, var(--border) 1px, transparent 1px);
    background-size: 28px 28px;
    opacity: 0.55;
    -webkit-mask-image: radial-gradient(70% 85% at 82% 6%, #000 0%, transparent 72%);
    mask-image: radial-gradient(70% 85% at 82% 6%, #000 0%, transparent 72%);
}

.pb-hero-copy {
    position: relative;
    z-index: 1;
    min-width: 0;
}

/* The headline is the product itself: the brand mark in an app-icon tile
   beside the wordmark. An icon-plus-name lockup is how every phone already
   presents an app, so it answers "what is this?" before a word is read. */
.pb-hero-title {
    display: flex;
    align-items: center;
    gap: 16px;
    margin: 0;
}

.pb-hero-logo {
    flex: none;
    display: grid;
    place-items: center;
    width: 64px;
    height: 64px;
    border: 1px solid var(--accent-soft-border);
    border-radius: 18px;
    background: var(--surface);
    box-shadow: var(--shadow-raised);
}

.pb-hero-logo img {
    display: block;
    width: 40px;
    height: 40px;
}

.pb-hero-word {
    font-size: clamp(36px, 4.4vw, 50px);
    font-weight: 800;
    line-height: 1;
    letter-spacing: -0.03em;
    color: var(--text);
}

/* "Box" carries the accent — colour alone, no marker: at wordmark size a
   highlight bar read as a text-selection artifact rather than emphasis. */
.pb-hero-title em {
    font-style: normal;
    color: var(--accent);
}

/* Three one-liners straight under the name — with the badge and tagline
   gone, these rows are the whole pitch, so they sit where a tagline would.
   Each row keys its own colour so the three promises separate at a glance;
   the soft tokens double as the (invisible-blend) border so the green and
   red chips need no border token of their own. */
.pb-hero-points {
    display: grid;
    gap: 15px;
    margin: 26px 0 0;
    padding: 0;
    list-style: none;
}

.pb-hero-points li {
    display: flex;
    align-items: flex-start;
    gap: 12px;
    min-width: 0;
}

.pb-point-icon {
    flex: none;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border: 1px solid var(--accent-soft-border);
    border-radius: 11px;
    background: var(--accent-soft);
    color: var(--accent);
}

.pb-hero-points li:nth-child(2) .pb-point-icon {
    border-color: var(--green-soft);
    background: var(--green-soft);
    color: var(--green);
}

/* The heart is red because hearts are — danger tokens borrowed purely as a
   colour, nothing alarming meant. It also echoes the red brand mark above. */
.pb-hero-points li:nth-child(3) .pb-point-icon {
    border-color: var(--danger-soft);
    background: var(--danger-soft);
    color: var(--danger);
}

.pb-point-icon svg {
    width: 18px;
    height: 18px;
}

.pb-point-text {
    display: block;
    min-width: 0;
}

.pb-point-text strong {
    display: block;
    font-size: 14.5px;
    font-weight: 680;
    color: var(--text);
}

.pb-point-text span {
    display: block;
    margin-top: 2px;
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-muted);
}

/* The one phrase the whole pitch hangs on — colour only, same weight as the
   line around it: the emphasis is the hue change, not extra ink. */
.pb-point-text span em {
    font-style: normal;
    color: var(--accent);
}

.pb-hero-actions {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px 12px;
    margin-top: 24px;
}

/* Its own class rather than .btn: this is the largest target on the page and
   wants a lift the shared buttons deliberately do not have. */
.pb-hero-cta {
    display: inline-flex;
    align-items: center;
    gap: 10px;
    padding: 14px 26px;
    border-radius: var(--radius-sm);
    background: var(--accent);
    color: #fff;
    font-size: 15.5px;
    font-weight: 680;
    text-decoration: none;
    box-shadow: var(--shadow-raised);
    transition: background 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
}

.pb-hero-cta svg {
    width: 19px;
    height: 19px;
}

.pb-hero-cta:hover {
    background: var(--accent-hover);
    transform: translateY(-1px);
    box-shadow: var(--shadow-pop);
}

.pb-hero-cta:active {
    transform: translateY(0);
}

/* White on the DARK palette's --accent (#948af0) is about 2.8:1 — under AA on
   the biggest target on the page. core.css already solves this for .btn-primary
   by flipping the label to the page's darkest ink; this is the same fix, and it
   needs BOTH selector forms because the theme has three states: an explicit
   choice stamps data-theme, and the default "system" setting stamps nothing. */
[data-theme="dark"] .pb-hero-cta {
    color: #0f1115;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .pb-hero-cta {
        color: #0f1115;
    }
}

/* The quiet twin beside the CTA — same height, surface fill, accent play
   glyph. It opens the same lightbox as the phone poster; two ways in because
   the poster sits in the other column and below the fold on a phone. */
.pb-hero-ghost {
    display: inline-flex;
    align-items: center;
    gap: 9px;
    padding: 13px 20px;
    border: 1px solid var(--border-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: 14.5px;
    font-weight: 640;
    cursor: pointer;
    transition: border-color 0.15s ease, background 0.15s ease, color 0.15s ease;
}

.pb-hero-ghost svg {
    width: 16px;
    height: 16px;
    color: var(--accent);
}

.pb-hero-ghost:hover {
    border-color: var(--accent-soft-border);
    background: var(--accent-soft);
    color: var(--accent);
}

/* ── the phone ────────────────────────────────────────────────────────── */

.pb-hero-art {
    position: relative;
    display: grid;
    place-items: center;
    min-width: 0;
}

/* A slowly turning dashed orbit behind the phone. Decoration in the same
   family as the orbs — token-coloured, pointer-events off via being a
   pseudo-element under the phone — but with a shape, which is what makes the
   column read as staged rather than as a phone floating in fog. */
.pb-hero-art::before {
    content: "";
    position: absolute;
    z-index: 0;
    width: 330px;
    height: 330px;
    border: 1.5px dashed var(--accent-soft-border);
    border-radius: 50%;
    animation: pb-orbit 80s linear infinite;
}

.pb-phone {
    position: relative;
    z-index: 1;
    width: 236px;
    padding: 11px;
    border: 1px solid var(--border-strong);
    border-radius: 30px;
    background: var(--surface);
    box-shadow: var(--shadow-pop);
}

@keyframes pb-orbit {
    to { transform: rotate(360deg); }
}

.pb-phone-notch {
    position: absolute;
    top: 11px;
    left: 50%;
    transform: translateX(-50%);
    width: 62px;
    height: 5px;
    border-radius: 999px;
    background: var(--border-strong);
}

/* Fixed dark inside, in both themes: it is a phone screen with a game behind
   the menu, and a screen that turned white in light mode would stop reading
   as one. */
/* The screen is exactly the recording's aspect (1080x1920 since the 2026-09-04
   overview cut; it was 1080x2316 before), so the video fills it edge to edge
   with no letterbox and no cropping. A ratio that differs from the file would
   put black bars inside a phone frame, which reads as a broken embed. */
.pb-phone-screen {
    position: relative;
    overflow: hidden;
    border-radius: 21px;
    background: #0c0e13;
    aspect-ratio: 1080 / 1920;
}

/* ── the player, over the page ────────────────────────────────────────────
   The chrome deliberately matches the screenshot lightbox in app-detail.css —
   same scrim, same 40px round close button, same theme-independent black and
   white. It is NOT shared code: app-detail.css does not load on this page, and
   lifting those ~25 lines into core.css would mean editing a working overlay on
   another page for no visible gain. If a third one ever appears, that is the
   moment to hoist it. */
.pb-lightbox {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 24px;
    background: rgba(0, 0, 0, 0.88);
}

.pb-lightbox[hidden] {
    display: none;
}

.pb-lightbox-panel {
    position: relative;
    display: flex;
}

/* The recording is portrait (1080x1920), so HEIGHT is the constraint on a
   desktop and width is the constraint on a phone. Capping both lets one rule
   cover the two cases without a media query. */
.pb-lightbox-video {
    display: block;
    /* Sized from the ratio, NOT from the file. With preload="none" the browser
       has no intrinsic size at the moment the panel opens, so it lays the
       element out at the 300x150 default and then jumps to portrait when the
       metadata lands — a jolt at exactly the moment somebody is looking. This
       is deterministic: take whichever of the two caps binds first. */
    aspect-ratio: 1080 / 1920;
    width: min(92vw, calc(88vh * 1080 / 1920));
    height: auto;
    border-radius: var(--radius);
    background: #0c0e13;
    box-shadow: 0 24px 70px rgba(0, 0, 0, 0.6);
}

/* Anchored to the VIEWPORT, not to the panel. Sat above the panel it was two
   pixels from the top edge on a 900px-tall window — the video is 88vh, so
   whatever is left over is split between above and below and there is never
   reliably room. `fixed` inside an untransformed ancestor is the viewport, so
   this lands in the corner at every size. Same 16px corner the screenshot
   lightbox uses. */
.pb-lightbox-close {
    position: fixed;
    top: 16px;
    right: 16px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    border: 0;
    border-radius: 999px;
    background: rgba(255, 255, 255, 0.12);
    color: #fff;
    cursor: pointer;
    transition: background-color 140ms ease;
}

.pb-lightbox-close:hover {
    background: rgba(255, 255, 255, 0.24);
}

.pb-lightbox-close:focus-visible {
    outline: 2px solid #fff;
    outline-offset: 2px;
}

.pb-lightbox-close svg {
    width: 20px;
    height: 20px;
}

/* ── the poster, in HTML ──────────────────────────────────────────────────
   A button, not a decorated div — it is the only way to start the video, so it
   has to be focusable and it has to announce itself. Absolute over the whole
   screen so the entire phone is the target, not a small play icon.

   Fixed dark in both themes: it stands in for a phone screen, and one that
   turned white in light mode would stop reading as one. */
.pb-video-cover {
    position: absolute;
    inset: 0;
    display: grid;
    grid-template-rows: auto 1fr auto;
    justify-items: center;
    gap: 8px;
    padding: 14px 11px 12px;
    border: 0;
    border-radius: 21px;
    overflow: hidden;
    cursor: pointer;
    text-align: left;
    background: linear-gradient(180deg, #171a26 0%, #0b0d12 100%);
}

.pb-video-cover[hidden] {
    display: none;
}

.pb-video-cover:focus-visible {
    outline: 2px solid #fff;
    outline-offset: -4px;
}

/* The app itself, blurred, behind everything — a real screenshot of the mod
   screen (580x1244, a touch taller than the frame's 1080/1920 ratio, so `cover`
   trims a sliver top and bottom). Blurred because at 230px it would be an illegible wall of
   UI; softened it reads as "the app is behind this play button", which is a
   truer promise than the old gradient blobs. Oversized (inset -8%) so the
   blur never samples past the edge into a transparent fringe; brightness
   drops it far enough for the white play button and the steps to sit on.
   The drift keeps the slow Ken Burns the gradients had. */
.pb-video-bg {
    position: absolute;
    inset: -8%;
    background: url("../../img/premibox-poster-bg.jpg") center / cover no-repeat;
    filter: blur(9px) brightness(0.72) saturate(1.1);
    animation: pb-drift 18s ease-in-out infinite alternate;
}

/* A faint grid, faded out at the edges — it gives the flat gradient a sense of
   depth and reads as "a screen" rather than "a coloured box". */
.pb-video-mesh {
    position: absolute;
    inset: 0;
    background-image:
        linear-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, 0.055) 1px, transparent 1px);
    background-size: 24px 24px;
    -webkit-mask-image: radial-gradient(72% 52% at 50% 40%, #000 0%, transparent 80%);
    mask-image: radial-gradient(72% 52% at 50% 40%, #000 0%, transparent 80%);
}

.pb-video-tag {
    position: relative;
    justify-self: start;
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 4px 10px 4px 8px;
    border: 1px solid rgba(255, 255, 255, 0.2);
    border-radius: 999px;
    background: rgba(10, 12, 17, 0.55);
    color: rgba(255, 255, 255, 0.88);
    font-size: 9.5px;
    font-weight: 700;
    letter-spacing: 0.1em;
    text-transform: uppercase;
}

.pb-video-tag i {
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: #45c67e;
    box-shadow: 0 0 8px rgba(69, 198, 126, 0.9);
    animation: pb-blink 2s ease-in-out infinite;
}

.pb-video-mid {
    position: relative;
    align-self: center;
    display: grid;
    justify-items: center;
    gap: 12px;
}

.pb-video-play {
    position: relative;
    display: grid;
    place-items: center;
    width: 66px;
    height: 66px;
    border-radius: 50%;
    background: #fff;
    color: #14161d;
    /* The glow is a box-shadow rather than a blurred pseudo-element behind the
       circle: a negative z-index child would be painted behind this cover's own
       background — inside .pb-phone's stacking context — and vanish. */
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.35),
        0 0 32px 2px rgba(109, 93, 240, 0.6),
        0 14px 34px rgba(0, 0, 0, 0.5);
    transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.pb-video-play svg {
    width: 27px;
    height: 27px;
    /* The glyph's mass sits left of centre; nudge it so it looks centred. */
    margin-left: 3px;
}

/* The ping. Expands THROUGH the white face and fades — a sonar ring rather than
   a halo, which is what makes it read at this size. */
.pb-video-play::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 2px solid rgba(255, 255, 255, 0.55);
    animation: pb-ping 2.6s cubic-bezier(0.2, 0.6, 0.3, 1) infinite;
}

.pb-video-cover:hover .pb-video-play,
.pb-video-cover:focus-visible .pb-video-play {
    transform: scale(1.07);
    box-shadow:
        0 0 0 1px rgba(255, 255, 255, 0.5),
        0 0 44px 4px rgba(109, 93, 240, 0.75),
        0 18px 40px rgba(0, 0, 0, 0.55);
}

.pb-video-time {
    padding: 3px 9px;
    border-radius: 999px;
    background: rgba(10, 12, 17, 0.5);
    color: rgba(255, 255, 255, 0.85);
    font-size: 11px;
    font-weight: 600;
    font-variant-numeric: tabular-nums;
}

/* The three steps. This is the part that makes the poster worth looking at
   when nobody presses play. */
.pb-video-steps {
    position: relative;
    align-self: end;
    display: grid;
    gap: 7px;
    width: 100%;
    padding: 10px 11px;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 13px;
    background: rgba(9, 11, 16, 0.62);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
}

.pb-step {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
    line-height: 1.25;
    color: rgba(255, 255, 255, 0.93);
}

.pb-step i {
    flex: none;
    display: grid;
    place-items: center;
    width: 17px;
    height: 17px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.16);
    color: #fff;
    font-size: 9px;
    font-weight: 800;
    font-style: normal;
    font-variant-numeric: tabular-nums;
}

/* The last step is the payoff, so it carries the brand colour rather than
   another grey chip. */
.pb-step:last-child i {
    background: #6d5df0;
    box-shadow: 0 0 12px rgba(109, 93, 240, 0.7);
}

@keyframes pb-drift {
    from { transform: translate3d(-3%, -2%, 0) scale(1); }
    to   { transform: translate3d(3%, 3%, 0) scale(1.14); }
}

@keyframes pb-ping {
    0%   { transform: scale(1);    opacity: 0.7; }
    70%  { transform: scale(1.8);  opacity: 0; }
    100% { transform: scale(1.8);  opacity: 0; }
}

@keyframes pb-blink {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.35; }
}

/* Decorative only, and behind everything: pointer-events stay off so they can
   never eat a tap meant for the download button or the play button. */
.pb-hero-orb {
    position: absolute;
    z-index: 0;
    border-radius: 50%;
    filter: blur(48px);
    pointer-events: none;
}

.pb-hero-orb-a {
    top: -30px;
    right: -20px;
    width: 190px;
    height: 190px;
    background: rgba(91, 75, 224, 0.30);
}

.pb-hero-orb-b {
    bottom: -40px;
    left: -10px;
    width: 160px;
    height: 160px;
    background: rgba(18, 129, 61, 0.20);
}

/* ── responsive ───────────────────────────────────────────────────────── */

@media (max-width: 900px) {
    .pb-hero {
        grid-template-columns: 1fr;
        gap: 24px;
        padding: 28px 24px 24px;
    }

    /* The phone goes under everything, the feature strip right after the
       buttons: stacked, the strip is a summary and the phone is a demo, and
       a demo below a summary reads in the right order. */
    .pb-hero-art {
        order: 2;
    }

    .pb-phone {
        width: 224px;
    }

    .pb-hero-art::before {
        width: 290px;
        height: 290px;
    }
}

@media (max-width: 560px) {
    .pb-hero {
        padding: 24px 18px 20px;
        border-radius: var(--radius);
    }

    .pb-hero-title {
        gap: 12px;
    }

    .pb-hero-logo {
        width: 52px;
        height: 52px;
        border-radius: 15px;
    }

    .pb-hero-logo img {
        width: 33px;
        height: 33px;
    }

    .pb-hero-word {
        font-size: 34px;
    }

    .pb-hero-cta,
    .pb-hero-ghost {
        width: 100%;
        justify-content: center;
    }

    /* Hidden on a phone — ~500px of scroll for a poster whose only job is
       opening the video, and the "Xem video hướng dẫn cài" button right
       above already opens the same lightbox. The video itself is NOT lost
       to phone visitors; only the second doorway is. */
    .pb-hero-art {
        display: none;
    }
}

/* ─────────────────────────────────────────────────────────────────────────
   "What PremiBox can do" — the spec-sheet grid under the hero. Every card
   is a real switch in the app; the copy here mirrors the app's own copy.
   Chip colours cycle through the four token families so the wall of eight
   reads as a rhythm, not a repeat — same palette the hero points use.
   ───────────────────────────────────────────────────────────────────────── */

.pbf-grid {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: 14px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.pbf-card {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
    padding: 16px 17px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    box-shadow: var(--shadow-card);
    transition: border-color 140ms ease, box-shadow 140ms ease;
}

.pbf-card:hover {
    border-color: var(--border-strong);
    box-shadow: var(--shadow-raised);
}

.pbf-icon {
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    margin-bottom: 2px;
    border-radius: 11px;
    background: var(--accent-soft);
    color: var(--accent);
}

.pbf-icon svg {
    width: 19px;
    height: 19px;
}

.pbf-card:nth-child(4n + 2) .pbf-icon {
    background: var(--green-soft);
    color: var(--green);
}

.pbf-card:nth-child(4n + 3) .pbf-icon {
    background: var(--amber-soft);
    color: var(--amber);
}

.pbf-card:nth-child(4n) .pbf-icon {
    background: var(--danger-soft);
    color: var(--danger);
}

.pbf-card strong {
    font-size: 14.5px;
    font-weight: 680;
    color: var(--text);
}

.pbf-card > span:last-child {
    font-size: 13px;
    line-height: 1.5;
    color: var(--text-muted);
}

@media (max-width: 1000px) {
    .pbf-grid {
        grid-template-columns: repeat(2, 1fr);
    }
}

@media (max-width: 560px) {
    .pbf-grid {
        grid-template-columns: 1fr;
        gap: 10px;
    }

    /* Single column: the icon tucks beside the title instead of above it,
       so eight cards cost half the scroll. */
    .pbf-card {
        display: grid;
        grid-template-columns: auto 1fr;
        column-gap: 12px;
        row-gap: 2px;
        align-items: center;
        padding: 13px 15px;
    }

    .pbf-icon {
        grid-row: 1 / 3;
        margin-bottom: 0;
        width: 36px;
        height: 36px;
    }
}

@media (prefers-reduced-motion: reduce) {
    /* The poster has three looping animations — a drifting screenshot, a
       pinging ring and a blinking dot — and the hero adds a turning orbit.
       All four are decoration, so all four stop. The ping is the one that
       matters most: a ring pulsing forever next to text is exactly the
       pattern this preference exists to switch off. */
    .pb-video-bg,
    .pb-video-tag i,
    .pb-video-play::after,
    .pb-hero-art::before {
        animation: none;
    }

    /* Without the ping the play button loses its ring, so keep a static one. */
    .pb-video-play::after {
        opacity: 0.55;
    }

    .pb-hero-cta,
    .pb-hero-ghost,
    .pb-video-play,
    .pb-video-cover {
        transition: none;
    }

    .pb-hero-cta:hover,
    .pb-video-cover:hover .pb-video-play,
    .pb-video-cover:focus-visible .pb-video-play {
        transform: none;
    }
}

/* ─────────────────────────────────────────────────────────────────────────
   Home search (.hs) — the section above the PremiBox hero.

   It has to read as the FIRST thing on the page without shouting over the
   hero right below it, which is why it is a quiet plate (one flat surface,
   two soft accent blooms) carrying one loud element: the field. Everything
   else here — eyebrow, title, chips — is sized to lead the eye down to it.

   Tokens only, like the hero: the blooms and the focus ring are --accent at
   low alpha via color-mix, so a theme that redefines --accent restyles the
   whole section without touching a rule.

   THE SECTION ITSELF HAS NO BACKGROUND, NO BORDER AND NO SHAPE. All of that
   is on .hs-plate, an empty layer behind the content, because the results
   panel is absolutely positioned and hangs below the section: give .hs a
   border-radius and the panel is merely un-rounded, but give it the
   clip-path a themed skin wants (the PremiBox theme bevels every plate on
   the site) and the panel is CUT OFF at the section's edge. Shape the plate,
   never the box the content lives in.
   ───────────────────────────────────────────────────────────────────────── */

.hs {
    position: relative;
    margin: 4px 0 26px;
    padding: 34px 32px 28px;
    text-align: center;
}

.hs-plate {
    position: absolute;
    z-index: 0;
    inset: 0;
    /* Crops the blooms, which are deliberately larger than the plate and
       positioned past its edges so their falloff is never a visible circle. */
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    background: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
    box-shadow: var(--shadow-card);
}

.hs-glow {
    position: absolute;
    z-index: 0;
    pointer-events: none;
    border-radius: 50%;
}

.hs-glow-a {
    top: -40px;
    left: 8%;
    width: 320px;
    height: 320px;
    background: radial-gradient(circle, color-mix(in srgb, var(--accent) 16%, transparent) 0%, transparent 68%);
}

.hs-glow-b {
    right: 4%;
    bottom: -60px;
    width: 280px;
    height: 280px;
    background: radial-gradient(circle, color-mix(in srgb, var(--accent) 12%, transparent) 0%, transparent 66%);
}

/* NO z-index HERE, and that is not an oversight. A positioned element with a
   z-index opens a stacking context, and the results panel lives inside this
   one — its z-index would then be scoped to this box and could not lift the
   panel over the PremiBox hero below (whose .pb-hero-copy is also z-index 1,
   and wins the tie by coming later in the document). Left on auto, this box
   still paints over the plate behind it (tree order, same paint step) and the
   panel's own z-index is free to work against the rest of the page. */
.hs-inner {
    position: relative;
    max-width: 660px;
    margin: 0 auto;
}

/* The count is a fact read from the catalogue (HomeController::index), not
   copy — so it wears the live dot a status line wears, not a badge. */
.hs-eyebrow {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    margin: 0;
    padding: 5px 12px;
    border: 1px solid var(--accent-soft-border);
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent);
    font-size: 12px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
}

.hs-live {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--green);
    animation: hs-pulse 2.4s ease-out infinite;
}

@keyframes hs-pulse {
    0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 55%, transparent); }
    70% { box-shadow: 0 0 0 7px transparent; }
    100% { box-shadow: 0 0 0 0 transparent; }
}

.hs-title {
    margin: 14px 0 0;
    font-size: clamp(26px, 3.4vw, 38px);
    font-weight: 800;
    line-height: 1.12;
    letter-spacing: -0.025em;
    color: var(--text);
}

.hs-sub {
    margin: 10px 0 0;
    font-size: 14.5px;
    line-height: 1.55;
    color: var(--text-muted);
}

/* The spellings the search actually handles, lifted out of the muted line.
   Weight, not colour: coloured words mid-sentence read as links. */
.hs-sub em {
    font-style: normal;
    font-weight: 600;
    color: var(--text);
}

/* Field ------------------------------------------------------------------- */

.hs-form {
    position: relative;
    margin: 22px 0 0;
}

.hs-field {
    position: relative;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 6px 6px 6px 16px;
    border: 1px solid var(--border-strong);
    border-radius: 16px;
    background: var(--surface);
    box-shadow: var(--shadow-card);
    transition: border-color 140ms ease, box-shadow 140ms ease;
}

/* Focus lives on the WRAPPER, not the input: the field is a composite (icon,
   text, clear, button), and a ring around the bare <input> would draw a box
   inside a box. The input's own outline is dropped only because this replaces
   it — never left with nothing. */
.hs-field:focus-within {
    border-color: var(--accent);
    box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 16%, transparent), var(--shadow-raised);
}

.hs-field-icon {
    flex: none;
    width: 20px;
    height: 20px;
    color: var(--text-muted);
    transition: color 140ms ease;
}

.hs-field:focus-within .hs-field-icon {
    color: var(--accent);
}

.hs-input {
    flex: 1;
    min-width: 0;
    padding: 14px 0;
    border: 0;
    background: none;
    color: var(--text);
    font: inherit;
    /* 16px minimum: below it, iOS Safari zooms the page on focus and leaves
       the visitor scrolled sideways in the middle of a word. */
    font-size: 16px;
}

.hs-input:focus {
    outline: none;
}

.hs-input::placeholder {
    color: var(--text-muted);
    opacity: 0.85;
}

/* The browser's own clear cross would sit beside ours and do half the job
   (text gone, panel still open). Ours calls close(). */
.hs-input::-webkit-search-cancel-button,
.hs-input::-webkit-search-decoration {
    -webkit-appearance: none;
    appearance: none;
}

.hs-busy {
    flex: none;
    width: 16px;
    height: 16px;
    border: 2px solid var(--border-strong);
    border-top-color: var(--accent);
    border-radius: 50%;
    animation: hs-spin 700ms linear infinite;
}

@keyframes hs-spin {
    to { transform: rotate(360deg); }
}

/* [hidden] is a UA rule on the ELEMENT, so the display below would beat it
   and leave a cross sitting in an empty field. Both toggled parts spell it
   out rather than relying on the attribute alone. */
.hs-clear[hidden],
.hs-busy[hidden],
.hs-results[hidden] {
    display: none;
}

.hs-clear {
    flex: none;
    display: grid;
    place-items: center;
    width: 30px;
    height: 30px;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--surface-2);
    color: var(--text-muted);
    cursor: pointer;
    transition: background-color 140ms ease, color 140ms ease;
}

.hs-clear:hover {
    background: var(--border);
    color: var(--text);
}

.hs-clear svg {
    width: 14px;
    height: 14px;
}

.hs-submit {
    flex: none;
    display: flex;
    align-items: center;
    justify-content: center;
    height: 44px;
    padding: 0 22px;
    border: 0;
    border-radius: 12px;
    background: var(--accent);
    color: #ffffff;
    font: inherit;
    font-size: 14.5px;
    font-weight: 700;
    cursor: pointer;
    transition: background-color 140ms ease;
}

.hs-submit:hover {
    background: var(--accent-hover);
}

/* Dark themes lift --accent to a pale violet (or coral), where white ink
   falls under 4.5:1 — the same flip the hero CTA makes, for the same reason. */
[data-theme="dark"] .hs-submit {
    color: #14121f;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) .hs-submit {
        color: #14121f;
    }
}

/* Results ----------------------------------------------------------------- */

.hs-results {
    position: absolute;
    z-index: 30;
    top: calc(100% + 10px);
    right: 0;
    left: 0;
    text-align: left;
}

.hs-panel {
    overflow: hidden;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow-pop);
}

.hs-panel-head {
    margin: 0;
    padding: 13px 18px 9px;
    color: var(--text-muted);
    font-size: 11.5px;
    font-weight: 700;
    letter-spacing: 0.08em;
    text-transform: uppercase;
}

.hs-list {
    /* Six rows fit. The cap is for a phone in landscape, where the panel would
       otherwise run past the bottom of the screen with no way back up. */
    max-height: min(62vh, 420px);
    margin: 0;
    padding: 0 8px 8px;
    overflow-y: auto;
    list-style: none;
}

/* WIDE SCREENS: the panel steps outside the field and the games go two-up.
   The field is capped at 660px because that is a comfortable line length to
   type into — but the RESULTS are a list of games, not prose, and inheriting
   that cap left them in a single 660px column with 260px of dead space on
   either side of it inside a 1180px page.

   1024px is the floor, and it is arithmetic rather than taste: the section's
   inner width is the viewport less 40px of container padding and 64px of its
   own, so the room beside a 660px column is (vw - 764) / 2. At 1024 that is
   130px a side, which is the first width where a 120px step-out still leaves
   the panel inside the plate it hangs from. */
@media (min-width: 1024px) {
    .hs-results {
        right: -120px;
        left: -120px;
    }

    .hs-list {
        display: grid;
        grid-template-columns: 1fr 1fr;
        align-content: start;
        column-gap: 4px;
        /* Three rows of two, so the taller panel never needs to scroll. */
        max-height: none;
    }
}

/* A grid item defaults to min-width:auto, which lets a long studio name push
   its own column WIDER than the track it was given — measured: two 439px
   tracks became 511 and 494, and the right column's badge was clipped off the
   panel's edge. The row inside already knows how to ellipsize; it just has to
   be told it may. */
.hs-item {
    min-width: 0;
}

.hs-item-link {
    display: flex;
    align-items: center;
    gap: 13px;
    padding: 9px 10px;
    border-radius: 12px;
    color: var(--text);
    text-decoration: none;
    transition: background-color 120ms ease;
}

/* ONE highlight for both pointer and keyboard: the script sets is-active on
   hover as well as on arrow keys, so two rows can never be lit at once. */
.hs-item-link.is-active {
    background: var(--accent-soft);
}

.hs-item-icon {
    flex: none;
    width: 44px;
    height: 44px;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: var(--surface-2);
    object-fit: cover;
}

.hs-item-body {
    display: flex;
    flex: 1;
    flex-direction: column;
    gap: 3px;
    min-width: 0;
}

.hs-item-name {
    overflow: hidden;
    font-size: 15px;
    font-weight: 650;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The matched letters. Colour and weight, NOT the browser's yellow plate: an
   active row already has an accent-soft background, and a second filled
   rectangle inside it reads as damage. */
.hs-item-name mark {
    background: none;
    color: var(--accent);
    font-weight: 800;
}

/* A block, not a flex row: this line has to ELLIPSIZE when a long studio name
   meets a narrow phone, and text-overflow does nothing to a flex item. */
.hs-item-meta {
    min-width: 0;
    overflow: hidden;
    color: var(--text-muted);
    font-size: 12.5px;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* The separator carries its own space on BOTH sides: the row used to be a
   flex line whose gap supplied the left one, and the switch to a block for
   ellipsis took that gap away. */
.hs-item-meta span + span::before {
    content: "·";
    margin: 0 6px;
}

.hs-item-tag {
    flex: none;
    padding: 3px 9px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface-2);
    color: var(--text-muted);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 0.03em;
    text-transform: uppercase;
}

.hs-item-go {
    flex: none;
    width: 16px;
    height: 16px;
    color: var(--text-muted);
    opacity: 0;
    transition: opacity 120ms ease, transform 120ms ease;
}

.hs-item-link.is-active .hs-item-go {
    color: var(--accent);
    opacity: 1;
    transform: translateX(2px);
}

.hs-all {
    display: block;
    padding: 12px;
    border-top: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--accent);
    font-size: 13.5px;
    font-weight: 700;
    text-align: center;
    text-decoration: none;
}

.hs-all:hover {
    background: var(--accent-soft);
}

.hs-empty {
    padding: 24px 20px 20px;
    text-align: center;
}

.hs-empty-title {
    margin: 0;
    color: var(--text);
    font-size: 15px;
    font-weight: 650;
}

.hs-empty-hint {
    margin: 6px 0 16px;
    color: var(--text-muted);
    font-size: 13.5px;
}

.hs-empty .hs-all {
    margin: 0 -20px -20px;
}

/* Suggestions ------------------------------------------------------------- */

/* Outside .hs-inner, and wider than it. The 660px cap is the reading measure
   for the headline and the field; four game chips need more than that.

   A GRID, AT EVERY WIDTH, AND THAT IS THE WHOLE POINT. A wrapping flex row
   sizes each chip to its own text, so whether the four fit on one line depends
   on WHICH GAMES ARE IN THE ROW — and this row is ranked by installs, so its
   contents change every week. Tuned against the local catalogue it fitted at
   820px; the live one, whose names are longer, was still leaving the fourth
   chip alone on its own line at 830. Equal columns cannot be tuned wrong: two
   or four of them always divide four chips evenly, whatever they are called.

   position: relative (with NO z-index) is what keeps it visible: .hs-plate is
   a positioned layer, so it would otherwise paint over this static sibling and
   swallow both the chips and their clicks. */
.hs-quick {
    position: relative;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    align-items: center;
    gap: 8px;
    max-width: 840px;
    margin: 18px auto 0;
}

/* Its own full-width line above the chips, at every width. Sitting inline
   beside them would take a column from the grid. */
.hs-quick-label {
    grid-column: 1 / -1;
    color: var(--text-muted);
    font-size: 12.5px;
    font-weight: 600;
    text-align: center;
}

/* Four across as soon as there is room for four readable names: the row caps
   at 840px, so each column is then ~204px and a name gets ~159px of it. */
@media (min-width: 900px) {
    .hs-quick {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* Under ~360px two columns leave about 70px for a name, which is not a name
   any more. One full-width column instead — and the chip fills it, so there is
   no dead space beside it either. */
@media (max-width: 359px) {
    .hs-quick {
        grid-template-columns: minmax(0, 1fr);
    }
}

/* Each chip is a game, and goes straight to that game's page. The icon is
   what says so — without it a row of words beside a search field reads as
   suggested QUERIES, which is what these used to be. */
.hs-chip {
    display: flex;
    align-items: center;
    /* Centred, because a stretched cell can be far wider than "Dicero" needs
       and an icon plus six letters pinned to the left edge of a 300px pill
       reads as a mistake. */
    justify-content: center;
    gap: 7px;
    padding: 5px 12px 5px 6px;
    border: 1px solid var(--border);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text-muted);
    font-size: 12.5px;
    font-weight: 600;
    text-decoration: none;
    transition: border-color 140ms ease, color 140ms ease, background-color 140ms ease;
}

.hs-chip:hover {
    border-color: var(--accent-soft-border);
    background: var(--accent-soft);
    color: var(--accent);
}

.hs-chip-icon {
    flex: none;
    width: 20px;
    height: 20px;
    border-radius: 6px;
    object-fit: cover;
    background: var(--surface-2);
}

/* Truncate instead of dropping a long title: this row is ranked by what is
   actually being installed, and skipping the top game because its name is long
   would quietly make the ranking wrong. The column decides the width; min-width
   is what lets the name shrink below its own text inside the flex chip. */
.hs-chip-name {
    min-width: 0;
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
}

/* Responsive -------------------------------------------------------------- */

@media (max-width: 640px) {
    .hs {
        padding: 26px 18px 22px;
    }

    .hs-field {
        gap: 6px;
        padding: 6px 12px;
    }

    /* No button at all on a phone. Results come as you type and the keyboard
       has its own Search key, so it only ate room the field needed. Hidden
       rather than dropped from the markup — see the note in home.php. */
    .hs-submit {
        display: none;
    }

    .hs-item-tag {
        display: none;
    }

    .hs-results {
        top: calc(100% + 8px);
    }
}

@media (prefers-reduced-motion: reduce) {
    /* The dot's ping is decoration and stops. The spinner does NOT: it is the
       only sign the box is working, and a frozen "loading" mark is worse than
       no mark at all. It keeps turning, more slowly. */
    .hs-live {
        animation: none;
    }

    .hs-busy {
        animation-duration: 1.6s;
    }

    .hs-field,
    .hs-submit,
    .hs-chip,
    .hs-item-link,
    .hs-item-go {
        transition: none;
    }
}
