/*
Theme Name: Joeys Retro Handhelds
Theme URI: https://joeysretrohandhelds.com
Author: Joey
Description: Custom theme for Joey's Retro Handhelds - emulation guides and handheld reviews.
Version: 0.16.149
License: GNU General Public License v2 or later
License URI: LICENSE
Text Domain: joeysretrohandhelds
*/

*,
*::before,
*::after {
    box-sizing: border-box;
}

/* Guarantees the hidden attribute always wins, full stop. Without this,
   any element that also has its OWN "display: flex/block/etc." rule (e.g.
   .jrh-winners-row, .jrh-leaderboard__rank) silently overrides the
   browser default [hidden] { display: none } - same specificity, but the
   author stylesheet always wins over the user-agent one, so the element
   stays visible regardless of the hidden attribute. This bit the paged
   list items directly: the pager's JS was correctly toggling .hidden on
   each row, the page-number text updated, but every row stayed visible on
   every page since their own display:flex rule silently won out. */
[hidden] {
    display: none !important;
}

/* Dark mode - neutrals only (page bg/text/surfaces/borders). The navy
   header, footer, and brand blue accents stay the same in both modes on
   purpose. Falls back to the OS-level preference automatically; an
   explicit choice from the header toggle (saved to localStorage, applied
   in header.php before first paint) overrides it via the theme-light/
   theme-dark class on <html>. See main.js for the toggle logic. */
:root {
    /* Brand blue - stays the same in both light and dark mode on purpose,
       so it's defined once here rather than in the mode-specific blocks
       below like the neutrals are. */
    --jrh-accent: #004aad;

    --jrh-bg: #f6f8f7;
    --jrh-surface: #ffffff;
    /* Passive filled backgrounds (code blocks, inline code, callout
       bodies) - cool light grey. Was a warm beige (#f1efe8) that read
       as dirty against the site's blue/white palette. */
    --jrh-surface-alt: #eef1f5;
    /* Hover/tap tint for interactive rows (accordion headers) - a cool
       brand-blue wash, same family as the TOC background. Deliberately
       bluer than surface-alt so highlights read as interactive rather
       than just filled. */
    --jrh-surface-hover: #eaf1f8;
    --jrh-text: #1a1a1a;
    --jrh-text-muted: #5f5e5a;
    --jrh-text-faint: #888780;
    --jrh-border: #d3d1c7;
    --jrh-toc-bg: #eaf1f8;
    --jrh-toc-border: #c9d8e8;
    --jrh-toc-link: var(--jrh-accent);
    --jrh-toc-text: var(--jrh-text-muted);
    --jrh-h3-border: #e6f1fb;
    /* Content links (.jrh-post__body a, callout links) - the brand blue
       has too little contrast against a dark page, so dark mode gets a
       lighter blue instead. */
    --jrh-link: var(--jrh-accent);
    /* Shared fixed height for the Recent Activity/Past Winners/Leaderboard
       scroll boxes - a genuine height cap (not max-height), so a column
       stays visually consistent regardless of how much content is inside
       it, with overflow-y:auto handling anything past that. Replaces the
       old approach of capping item COUNT (6 activity items, 5/page
       winners) to fake a consistent height. */
    --jrh-scroll-box-height: 520px;
}

@media (prefers-color-scheme: dark) {
    :root {
        --jrh-bg: #15171b;
        --jrh-surface: #1f2227;
        --jrh-surface-alt: #24272d;
        --jrh-surface-hover: #232b37;
        --jrh-text: #eaeaea;
        --jrh-text-muted: #a7a6a1;
        --jrh-text-faint: #8f8e89;
        --jrh-border: #34383e;
        --jrh-toc-bg: var(--jrh-accent);
        --jrh-toc-border: #6fa8dc;
        --jrh-toc-link: #ffffff;
        --jrh-toc-text: #ffffff;
        --jrh-h3-border: #22344a;
        --jrh-link: #6fa8dc;
    }
}

html.theme-light {
    --jrh-bg: #f6f8f7;
    --jrh-surface: #ffffff;
    --jrh-surface-alt: #eef1f5;
    --jrh-surface-hover: #eaf1f8;
    --jrh-text: #1a1a1a;
    --jrh-text-muted: #5f5e5a;
    --jrh-text-faint: #888780;
    --jrh-border: #d3d1c7;
    --jrh-toc-bg: #eaf1f8;
    --jrh-toc-border: #c9d8e8;
    --jrh-toc-link: var(--jrh-accent);
    --jrh-toc-text: var(--jrh-text-muted);
    --jrh-h3-border: #e6f1fb;
    --jrh-link: var(--jrh-accent);
}

html.theme-dark {
    --jrh-bg: #15171b;
    --jrh-surface: #1f2227;
    --jrh-surface-alt: #24272d;
    --jrh-surface-hover: #232b37;
    --jrh-text: #eaeaea;
    --jrh-text-muted: #a7a6a1;
    --jrh-text-faint: #8f8e89;
    --jrh-border: #34383e;
    --jrh-toc-bg: var(--jrh-accent);
    --jrh-toc-border: #6fa8dc;
    --jrh-toc-link: #ffffff;
    --jrh-toc-text: #ffffff;
    --jrh-h3-border: #22344a;
    --jrh-link: #6fa8dc;
}

html {
    scroll-behavior: smooth;
}

body {
    margin: 0;
    font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    color: var(--jrh-text);
    background: var(--jrh-bg);
}

img {
    max-width: 100%;
    height: auto;
    display: block;
}

/* iframes (YouTube embeds, etc.) weren't covered by the img reset above.
   Normally WordPress core's own block-library CSS handles responsive
   sizing for a standard Gutenberg Embed block's wrapper, but anything
   embedded outside that (a raw iframe, a GP Elements hook like
   "NitroAds Video", etc.) has no fallback constraint at all - it renders
   at its native width/height attributes, uncontained. */
iframe {
    max-width: 100%;
}

a {
    color: inherit;
}

.screen-reader-text {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip: rect(1px, 1px, 1px, 1px);
    white-space: nowrap;
}

/* Games of the Month strip */

.gotm-strip {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 8px 20px;
    background: #f3e8ff;
    font-size: 14px;
}

.gotm-strip__link {
    display: flex;
    align-items: center;
    gap: 8px;
    color: #6b21a8;
    text-decoration: none;
}

.gotm-strip__icon {
    font-size: 15px;
}

.gotm-strip__arrow {
    transition: transform 0.15s ease;
}

.gotm-strip__link:hover .gotm-strip__arrow {
    transform: translateX(3px);
}

/* Site header */

.site-header {
    background: var(--jrh-accent);
    padding: 10px 20px;
    position: sticky;
    top: 0;
    z-index: 100;
}

/* Logged-in-only: WordPress pins its own admin bar (32px desktop, 46px
   on its own <=782px breakpoint - matches WP core's own responsive
   toolbar height exactly, not an arbitrary guess) fixed at the very top
   of the viewport. This sticky header's top:0 tried to stick in that
   same spot, getting clipped underneath the admin bar once scrolled.
   Never affects real visitors - body only gets .admin-bar when logged
   in and viewing the toolbar. */
body.admin-bar .site-header {
    top: 32px;
}

/* WP's admin bar switches from fixed to absolute below this breakpoint
   (it scrolls away with the page instead of staying pinned), so there's
   no longer a fixed bar for the sticky header to clear - keeping the
   desktop 32px offset here left a permanent blank gap above the header
   once the bar had scrolled out of view. */
@media screen and (max-width: 782px) {
    body.admin-bar .site-header {
        top: 0;
    }
}

.site-header__inner {
    max-width: 1440px;
    margin: 0 auto;
    display: flex;
    align-items: center;
    justify-content: flex-start;
    gap: 20px;
    flex-wrap: wrap;
    position: relative;
}

.site-header__brand-row {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-right: auto;
}

.site-header__logo {
    display: flex;
    align-items: center;
}

.site-header__logo-image {
    display: block;
    height: 44px;
    width: auto;
}

.site-header__nav-toggle {
    display: none;
}

.site-header__hamburger {
    display: none;
    flex-direction: column;
    justify-content: center;
    gap: 5px;
    width: 28px;
    height: 28px;
    padding: 0;
    background: none;
    border: none;
    cursor: pointer;
}

.site-header__hamburger span {
    display: block;
    width: 100%;
    height: 2px;
    background: #ffffff;
}

.site-header__menu {
    display: flex;
    align-items: center;
    gap: 20px;
}

.site-header__search-panel {
    display: flex;
}

/* Dark mode toggle - always visible (unlike the search icon, which only
   shows below the mobile breakpoint), sitting to the right of wherever
   search ends up: the inline search form on desktop, the search icon on
   mobile. See main.js for the click behavior and icon swap. */
.site-header__theme-toggle {
    display: flex;
    align-items: center;
    font-size: 22px;
    padding: 0;
    background: none;
    border: none;
    color: #ffffff;
    cursor: pointer;
}

/* Two copies of the toggle exist (see template-parts/theme-toggle.php) -
   the menu one is hidden by default and only shown inside the mobile
   hamburger dropdown; the inline one is the reverse (see the mobile media
   query further down). */
.site-header__theme-toggle--menu {
    display: none;
}

.site-header__nav-list {
    list-style: none;
    display: flex;
    gap: 10px;
    margin: 0;
    padding: 0;
}

.site-header__nav-list > li {
    position: relative;
}

.site-header__nav-list > li > a {
    display: block;
    color: #ffffff;
    text-decoration: none;
    font-size: 16px;
    font-weight: 600;
    padding: 8px 6px;
    border-radius: 6px;
}

.site-header__nav-list > li > a:hover {
    background: #185fa5;
}

.site-header__nav-list .sub-menu {
    display: none;
    list-style: none;
    position: absolute;
    top: 100%;
    left: 0;
    z-index: 50;
    min-width: 190px;
    margin: 0;
    padding: 8px 0;
    background: var(--jrh-accent);
    border-radius: 0 0 8px 8px;
    box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
}

/* Desktop-only - on mobile this collided with the JS-driven drill-down
   below (a tapped link still gets :focus-within, popping this dropdown at
   the same time main.js opens the full-panel submenu). */
@media (min-width: 1101px) {
    .site-header__nav-list > li.menu-item-has-children:hover > .sub-menu,
    .site-header__nav-list > li.menu-item-has-children:focus-within > .sub-menu {
        display: block;
    }

}

/* Centers the nav between the logo and the search/toggle group using
   normal in-flow flexbox instead of absolute positioning - a previous
   version centered the nav on the header's true midpoint via
   position:absolute, which overlapped the logo whenever the logo/search
   groups were asymmetric widths (e.g. a wide wordmark logo), since
   absolute positioning doesn't reserve space for itself. flex: 1 here
   reserves the actual leftover space between the two side groups and
   centers within *that*, so it can never overlap either side regardless
   of how wide the logo or search bar are - the tradeoff is it's centered
   relative to the gap, not the full header, which is only visually
   different when the two side groups are very unequal widths.

   Breakpoint is 1100px (not 780px) so the desktop nav only shows once
   there's real breathing room - a previous attempt at cramming the full
   nav + search into ~1000px fit with 0px of margin, which is too fragile
   (a scrollbar or minor font-metric difference tips it back into
   wrapping). Below this, the hamburger mobile layout kicks in instead,
   matching how most media sites handle mid-range widths. */
@media (min-width: 1150px) {
    .site-header__brand-row {
        margin-right: 0;
    }

    .site-header__menu {
        flex: 1;
        display: flex;
        justify-content: center;
    }
}

.site-header__nav-list .sub-menu a {
    display: block;
    color: #ffffff;
    text-decoration: none;
    padding: 8px 16px;
    white-space: nowrap;
    font-size: 16px;
}

.site-header__nav-list .sub-menu a:hover {
    background: #185fa5;
    text-decoration: none;
}

/* Both inserted into every submenu by main.js regardless of viewport width
   (only the mobile drill-down CSS below actually shows them) - hidden by
   default so they don't leak into the desktop hover dropdown as unstyled
   browser-default buttons/links. */
.site-header__submenu-back,
.site-header__submenu-view-all {
    display: none;
}

.search-form {
    display: flex;
    align-items: center;
    min-width: 0;
}

.search-form__field-wrap {
    position: relative;
    width: 220px;
    min-width: 0;
}

.search-form .search-field {
    width: 100%;
    min-width: 0;
    padding: 8px 14px;
    border-radius: 999px;
    border: none;
    font-size: 15px;
}

/* Live-search-while-typing results dropdown - populated by main.js from
   the WP REST API's built-in /wp/v2/search endpoint, no custom backend
   endpoint needed. Uses the dark-mode surface variables since it's a
   popover card, independent of whatever background the search field
   itself sits on (navy header vs. light 404 page). */
.jrh-search-results {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 6px;
    background: var(--jrh-surface);
    border: 1px solid var(--jrh-border);
    border-radius: 8px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2);
    max-height: 320px;
    overflow-y: auto;
    z-index: 200;
}

.jrh-search-results[hidden] {
    display: none;
}

.jrh-search-results__item {
    display: block;
    padding: 10px 14px;
    color: var(--jrh-text);
    text-decoration: none;
    font-size: 14px;
    border-bottom: 1px solid var(--jrh-border);
}

.jrh-search-results__item:last-child {
    border-bottom: none;
}

.jrh-search-results__item:hover {
    background: var(--jrh-toc-bg);
}

.jrh-search-results__empty {
    padding: 10px 14px;
    margin: 0;
    color: var(--jrh-text-muted);
    font-size: 14px;
}

.jrh-search-results__more {
    padding: 8px 14px;
    margin: 0;
    color: var(--jrh-text-muted);
    font-size: 12px;
    font-style: italic;
    border-top: 1px solid var(--jrh-border);
    cursor: default;
}

/* Mobile nav - checkbox-driven collapse, no JS needed. Below this width the
   hamburger label toggles a hidden checkbox, which shows/hides the nav +
   search menu stacked under the header. Submenus lose their hover-only
   behavior here and just stay expanded, since touch has no hover state. */
@media (max-width: 1100px) {
    .site-header__hamburger {
        display: flex;
    }

    /* The logo never shrinks (flex-shrink:0 on brand-row) since it's a
       wide ~5:1 wordmark, so on mobile it needs to just be physically
       smaller to leave any real room for the search bar next to it -
       otherwise search gets squeezed down to a sliver (or wraps to its
       own row) regardless of how well it shrinks. */
    .site-header__logo-image {
        height: 32px;
    }

    .site-header__menu {
        display: none;
        flex-direction: column;
        align-items: stretch;
        width: 100%;
        gap: 14px;
        margin-top: 14px;
    }

    .site-header__nav-toggle:checked ~ .site-header__menu {
        display: flex;
    }

    .site-header__nav-list {
        flex-direction: column;
        gap: 2px;
        position: relative;
    }

    /* Drill-down submenu - tapping a top-level item with subcategories
       (instead of navigating) slides in a full-width panel listing just
       those subcategories, via the .is-open class toggled in main.js.
       A "back" row at the top of the panel (also inserted by main.js)
       returns to the top-level list. */
    .site-header__nav-list > li.menu-item-has-children > a {
        display: flex;
        align-items: center;
        justify-content: space-between;
    }

    .site-header__nav-list > li.menu-item-has-children > a::after {
        content: "\203A";
        font-size: 20px;
        line-height: 1;
        margin-left: 8px;
    }

    .site-header__nav-list .sub-menu {
        display: none;
    }

    /* Other top-level categories fully hide while one is drilled into, so
       the panel's own content height (not a fixed overlay box) determines
       how tall the menu is - no leftover list showing behind a shorter
       panel, and no clipping behind a taller one. */
    .site-header__nav-list:has(> li.menu-item-has-children.is-open) > li:not(.is-open) {
        display: none;
    }

    .site-header__nav-list > li.menu-item-has-children.is-open > a {
        display: none;
    }

    .site-header__nav-list > li.menu-item-has-children.is-open > .sub-menu {
        display: block;
        position: static;
        background: var(--jrh-accent);
        box-shadow: none;
        margin: 0;
        padding: 0;
        max-height: 70vh;
        overflow-y: auto;
    }

    .site-header__nav-list .sub-menu a {
        white-space: normal;
        padding: 12px 16px;
        font-size: 16px;
    }

    .site-header__submenu-back,
    .site-header__submenu-view-all {
        display: block;
    }

    .site-header__submenu-back {
        border-bottom: 1px solid rgba(255, 255, 255, 0.2);
        margin-bottom: 4px;
    }

    .site-header__submenu-back-btn {
        display: flex;
        align-items: center;
        gap: 8px;
        width: 100%;
        background: none;
        border: none;
        color: #ffffff;
        font-size: 14px;
        font-weight: 600;
        text-align: left;
        padding: 8px 16px;
        cursor: pointer;
    }

    .site-header__submenu-view-all-link {
        display: block;
        color: #ffffff;
        font-size: 13px;
        font-weight: 600;
        text-decoration: underline;
        padding: 8px 16px 12px;
        border-bottom: 1px solid rgba(255, 255, 255, 0.2);
        margin-bottom: 4px;
    }

    /* Search bar is always visible on mobile now (no icon-tap-to-reveal
       step), so it just claims the row's free space directly - flex:1 on
       the same line as the logo/hamburger, instead of pushing a second
       row in below it. order:2 here (and order:3/4 below on the
       toggle/menu) is a mobile-only visual reorder - DOM order is
       untouched so desktop's nav-then-search-then-toggle sequence is
       unaffected. Needed because .site-header__menu is width:100% when
       open, forcing a flex-wrap line break; without this, search/toggle
       (which come after it in the markup) would get wrapped below it. */
    /* margin-left: auto pushes the search box (and theme toggle/menu after
       it, via order) flush to the right edge as a group. A fixed 220px
       width wrapped to its own row on narrow phones once logo + hamburger
       + search no longer fit on one line - flex-shrink (via min-width:0
       on the panel, width:100% on the field-wrap) lets it shrink instead
       of wrapping, while max-width still caps it at 220px on wider
       screens where there's room. brand-row's own logo/hamburger don't
       shrink (flex-shrink:0), so the search box is what gives first. */
    .site-header__brand-row {
        flex-shrink: 0;
    }

    /* flex-basis: 0 (the "0" in "1 1 0") is the fix that actually matters
       here: flexbox decides which line an item wraps to using its
       flex-basis, not its post-shrink size - a 220px basis was getting
       wrapped to its own line whenever remaining space was < 220px, even
       though flex-shrink + min-width:0 could have fit it fine on the
       same line if given the chance. Starting from a 0 basis means it
       always "fits" during line assignment, then grows/is capped by
       max-width once it's confirmed to be on this line. */
    .site-header__search-panel {
        display: flex;
        order: 2;
        flex: 1 1 0%;
        min-width: 0;
        max-width: 220px;
        margin-left: auto;
    }

    .site-header__theme-toggle--inline {
        display: none;
    }

    /* Shown instead, as the last item inside the (flex-direction: column)
       mobile dropdown - a plain left-aligned line below the nav links. */
    .site-header__theme-toggle--menu {
        display: flex;
        align-items: center;
        justify-content: flex-start;
        gap: 8px;
        font-size: 18px;
    }

    .site-header__menu {
        order: 4;
    }

    .search-form__field-wrap {
        width: 100%;
    }
}

/* Below ~400px (e.g. the Galaxy Z Fold's cover screen, ~320px), give the
   logo even less room so there's more left for search to stay on the
   same row - it must never wrap to its own line. */
@media (max-width: 400px) {
    .site-header__logo-image {
        height: 24px;
    }
}

/* Homepage */

.homepage {
    max-width: 1440px;
    margin: 0 auto;
    padding: 24px 20px 40px;
}

.hero {
    display: flex;
    flex-direction: column;
    text-decoration: none;
}

/* padding-top percentage instead of aspect-ratio: Chromium has a known
   rendering bug where aspect-ratio miscalculates box height under
   fractional Windows display scaling (e.g. 250%), sometimes blowing the
   box up dramatically. The padding-top hack (height:0 + padding-top as a
   % of width) has been reliable for over a decade without that issue. */
.hero__image {
    position: relative;
    height: 0;
    padding-top: 56.25%;
    border-radius: 12px;
    background-color: #e24b4a;
    background-size: cover;
    background-position: center;
    overflow: hidden;
}

.hero__badge {
    position: absolute;
    bottom: 16px;
    left: 16px;
    background: #ffffff;
    color: #791f1f;
    font-size: 13px;
    font-weight: 700;
    padding: 4px 11px;
    border-radius: 999px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}

.hero__title {
    margin: 14px 0 0;
    color: var(--jrh-text);
    font-size: 32px;
    font-weight: 800;
}

.jrh-hero-row {
    display: flex;
    gap: 24px;
    align-items: stretch;
}

.jrh-hero-row .hero {
    flex: 1.6;
    min-width: 0;
}

.jrh-recent-list {
    flex: 1;
    min-width: 0;
    display: flex;
    flex-direction: column;
}

.jrh-recent-list__heading {
    font-size: 14px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.03em;
    color: var(--jrh-text-muted);
    margin: 0 0 8px;
}

.jrh-recent-item {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 0;
    border-bottom: 1px solid var(--jrh-border);
    text-decoration: none;
}

.jrh-recent-item:first-child {
    padding-top: 0;
}

.jrh-recent-item:last-child {
    border-bottom: none;
}

.jrh-recent-item__body {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 0;
}

.jrh-recent-item__title {
    font-size: 21px;
    font-weight: 700;
    color: var(--jrh-text);
    line-height: 1.3;
}

.jrh-recent-item__meta {
    font-size: 12px;
    color: var(--jrh-text-muted);
}

/* A fixed pixel width was wrong here: this same class is used both in the
   wide hero-row sidebar and the much narrower guides-news-row aside
   (~1/4 of its row's width), and a fixed px value can't be right for both
   - viewport-based media queries didn't help either, since the aside can
   be narrow at ANY viewport width (it's the column that's narrow, not
   necessarily the screen). flex-basis as a percentage scales the image
   proportionally to whatever row width it actually ends up in, with
   max-width capping it from getting too large in the wide sidebar and
   min-width keeping it from disappearing in the narrow one. */
.jrh-recent-item__image {
    display: block;
    flex: 0 1 40%;
    min-width: 70px;
    max-width: 240px;
    aspect-ratio: 16 / 9;
    border-radius: 6px;
    background-color: var(--jrh-border);
    background-size: cover;
    background-position: center;
}

@media (max-width: 1100px) {
    .jrh-hero-row {
        flex-direction: column;
    }
}

.section-heading {
    display: inline-block;
    font-size: 26px;
    font-weight: 800;
    color: var(--jrh-text);
    margin: 40px 0 16px;
    padding-bottom: 10px;
    border-bottom: 3px solid var(--jrh-accent);
}

.section-heading a {
    color: inherit;
    text-decoration: none;
}

.section-heading a:hover {
    text-decoration: underline;
}

.tile-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px;
}

.tile {
    display: flex;
    flex-direction: column;
    text-decoration: none;
}

.tile__image {
    position: relative;
    height: 0;
    padding-top: 56.25%;
    border-radius: 8px;
    background-color: var(--jrh-border);
    background-size: cover;
    background-position: center;
    overflow: hidden;
}

.tile__badge {
    position: absolute;
    bottom: 10px;
    left: 10px;
    font-size: 12px;
    font-weight: 700;
    background: #ffffff;
    padding: 3px 9px;
    border-radius: 999px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}

.tile__title {
    margin: 8px 0 0;
    color: var(--jrh-text);
    font-size: 18px;
    font-weight: 600;
}

.tile--guide .tile__badge { color: #185fa5; }
.tile--review .tile__badge { color: #3b6d11; }
.tile--news .tile__badge { color: #854f0b; }
.tile--event .tile__badge { color: #712bad; }
.tile--event .tile__image { background-color: #f3e8ff; }

@media (min-width: 640px) {
    .tile-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

/* Featured carousel - paged 4-at-a-time using native scroll-snap, no JS
   library dependency. Each .jrh-carousel__page is a full-width grid so a
   "page" scrolls as one unit rather than one tile at a time; main.js just
   drives scrollTo() for the arrow buttons/dots and reads scrollLeft to
   keep them in sync with touch-swipe scrolling. */
.jrh-carousel {
    position: relative;
}

.jrh-carousel__track {
    display: flex;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
}

.jrh-carousel__track::-webkit-scrollbar {
    display: none;
}

.jrh-carousel__page {
    flex: 0 0 100%;
    scroll-snap-align: start;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px;
}

@media (min-width: 640px) {
    .jrh-carousel__page {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

.jrh-carousel__btn {
    position: absolute;
    top: 40%;
    transform: translateY(-50%);
    width: 36px;
    height: 36px;
    border-radius: 50%;
    border: none;
    background: var(--jrh-accent);
    color: #ffffff;
    font-size: 20px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
    z-index: 5;
}

.jrh-carousel__btn--prev {
    left: -18px;
}

.jrh-carousel__btn--next {
    right: -18px;
}

.jrh-carousel__btn:disabled {
    opacity: 0.35;
    cursor: default;
}

.jrh-carousel__dots {
    display: flex;
    justify-content: center;
    gap: 8px;
    margin-top: 16px;
}

.jrh-carousel__dot {
    width: 8px;
    height: 8px;
    border-radius: 50%;
    border: none;
    background: var(--jrh-border);
    padding: 0;
    cursor: pointer;
}

.jrh-carousel__dot--active {
    background: var(--jrh-accent);
}

@media (max-width: 480px) {
    .jrh-carousel__btn {
        display: none;
    }
}

/* Guides + Latest News row - Guides takes 3 of the row's 4 "columns" as a
   3x3 tile grid, Latest News takes the remaining 1 as a vertical list
   (reusing .jrh-recent-list/.jrh-recent-item from the hero row), matching
   the same 3-rows-tall footprint. */
.jrh-guides-news-row {
    display: flex;
    gap: 24px;
    align-items: flex-start;
}

.jrh-guides-news-row__main {
    flex: 3;
    min-width: 0;
}

.jrh-guides-news-row__aside {
    flex: 1;
    min-width: 0;
}

.jrh-guides-news-row__aside .jrh-recent-item__title {
    font-size: 15px;
}

.jrh-guides-news-row__main .tile-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (min-width: 640px) {
    .jrh-guides-news-row__main .tile-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (max-width: 1100px) {
    .jrh-guides-news-row {
        flex-direction: column;
    }
}

/* Search results / category / generic archive templates */

.jrh-archive-description {
    color: var(--jrh-text);
    font-size: 18px;
    line-height: 28px;
    margin: 0 0 20px;
}

/* Author archive template */

.jrh-author-header {
    display: flex;
    align-items: center;
    gap: 16px;
    margin-bottom: 24px;
}

.jrh-author-header img {
    border-radius: 50%;
}

.jrh-author-header__title {
    margin: 0 0 4px;
}

.pagination .nav-links {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: center;
    margin: 32px 0;
}

.pagination .page-numbers {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 36px;
    height: 36px;
    padding: 0 10px;
    border-radius: 6px;
    background: var(--jrh-toc-bg);
    color: var(--jrh-text);
    text-decoration: none;
    font-size: 14px;
}

.pagination .page-numbers.current {
    background: var(--jrh-accent);
    color: #ffffff;
}

/* 404 template */

.jrh-404 {
    max-width: 1440px;
    margin: 0 auto;
    padding: 24px 20px 60px;
}

.jrh-404__message {
    color: var(--jrh-text-muted);
    font-size: 16px;
    max-width: 560px;
    margin: 0 0 20px;
}

.jrh-404__search {
    margin-bottom: 12px;
}

/* .search-form's default styling assumes it's sitting on the navy header
   background - override for use here on the light page background. */
.jrh-404__search .search-form {
    justify-content: flex-start;
}

.jrh-404__search .search-form__field-wrap {
    width: 240px;
}

.jrh-404__search .search-field {
    border: 1px solid var(--jrh-border);
}

/* Site footer */

/* GameSpot-style multi-column footer. Background is a near-black shade
   (not the brand blue used elsewhere) - estimated from a GameSpot
   screenshot reference, adjust if you have an exact hex. */
.site-footer {
    background: #0c1516;
    color: #a9b3b2;
    padding: 48px 20px 24px;
    margin-top: 40px;
}

.site-footer__inner {
    max-width: 1440px;
    margin: 0 auto;
}

.site-footer__columns {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px;
    margin-bottom: 36px;
}

@media (min-width: 700px) {
    .site-footer__columns {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (min-width: 1000px) {
    .site-footer__columns {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

.site-footer__column-title {
    font-size: 14px;
    font-weight: 700;
    margin: 0 0 14px;
}

.site-footer__column-title a {
    color: #ffffff;
    text-decoration: none;
}

.site-footer__column-title a:hover {
    text-decoration: underline;
}

.site-footer__column-list {
    list-style: none;
    margin: 0;
    padding: 0;
    display: flex;
    flex-direction: column;
    gap: 12px;
}

.site-footer__column-list a {
    color: #a9b3b2;
    text-decoration: none;
    font-size: 14px;
}

.site-footer__column-list a:hover {
    color: #ffffff;
    text-decoration: underline;
}

.site-footer__follow {
    margin-bottom: 24px;
    text-align: center;
}

.site-footer__social {
    display: flex;
    justify-content: center;
    gap: 16px;
}

.site-footer__social-link {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    height: 36px;
    border-radius: 50%;
    background: rgba(255, 255, 255, 0.08);
    color: #ffffff;
    text-decoration: none;
    transition: background-color 0.15s ease;
}

.site-footer__social-link svg {
    width: 18px;
    height: 18px;
}

.site-footer__social-link:hover {
    background: var(--jrh-accent);
}

.site-footer__bottom {
    display: flex;
    flex-direction: column;
    gap: 16px;
    padding-top: 24px;
    border-top: 1px solid rgba(255, 255, 255, 0.12);
}

@media (min-width: 640px) {
    .site-footer__bottom {
        flex-direction: row;
        align-items: center;
        justify-content: space-between;
    }
}

.site-footer__brand {
    font-size: 16px;
    font-weight: 700;
    color: #ffffff;
    margin: 0 0 4px;
}

.site-footer__tagline {
    font-size: 13px;
    line-height: 1.6;
    margin: 0;
}

.site-footer__copyright {
    font-size: 13px;
    color: #7d8887;
}

/* Guide/post template - rebuilt clean with unique "jrh-" prefixed classes
   to rule out any collision with leftover theme/plugin styles. */

.jrh-post {
    max-width: 1200px;
    margin: 0 auto;
    padding: 24px 20px 60px;
    box-sizing: border-box;
}

.jrh-post__breadcrumb {
    font-size: 13px;
    color: var(--jrh-text-faint);
    margin-bottom: 10px;
}

.jrh-post__breadcrumb a {
    color: var(--jrh-text-faint);
    text-decoration: none;
}

.jrh-post__breadcrumb a:hover {
    text-decoration: underline;
}

.jrh-post__title {
    font-size: 64px;
    font-weight: 900;
    margin: 0 0 20px;
    color: var(--jrh-text);
}

@media (max-width: 640px) {
    .jrh-post__title {
        font-size: 28px;
    }
}

@media (max-width: 400px) {
    .jrh-post__title {
        font-size: 24px;
    }
}

.jrh-post__body {
    width: 100%;
    font-size: 18px;
    line-height: 28px;
    color: var(--jrh-text);
}

.jrh-post__body p {
    font-weight: 400;
    margin: 0 0 18px;
}

/* Content links otherwise inherit color: inherit (same as body text) from
   the global "a" rule, leaving only the browser's default underline to
   signal "this is a link" - not enough on its own, and inconsistent with
   the brand-blue links used everywhere else in the theme (TOC, footer,
   callouts). Underline moves to :hover instead of always-on, matching
   those same conventions. */
.jrh-post__body a {
    color: var(--jrh-link);
    text-decoration: none;
}

.jrh-post__body a:hover {
    text-decoration: underline;
}

/* WordPress core's built-in block background-color classes
   (has-luminous-vivid-amber-background-color and the rest of the default
   palette) are fixed bright/pastel colors that don't change with dark
   mode - but this theme's text/link colors DO (near-white text, light
   blue links in dark mode), which made text on one of these light,
   theme-independent backgrounds unreadable once dark mode was on.
   Forces readable dark text/links on ANY block using one of these,
   regardless of which specific color was picked in the editor - the
   fixed-background problem is the same no matter the exact shade. */
.jrh-post__body .has-background,
.jrh-post__body .has-background * {
    color: #1a1a1a;
}

.jrh-post__body .has-background a {
    color: #004aad;
}

/* Lists otherwise fall back to the browser's default UA margin (~1em top
   and bottom), which is more top space than a paragraph has right after a
   heading - matches paragraphs' spacing instead. */
.jrh-post__body ul,
.jrh-post__body ol {
    margin: 0 0 18px;
}

/* Inline code (a bare <code>, e.g. mid-sentence). */
.jrh-post__body code {
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 0.9em;
    background: var(--jrh-surface-alt);
    border: 1px solid var(--jrh-border);
    border-radius: 4px;
    padding: 2px 6px;
}

/* Code block - WordPress's core "Code" block outputs a plain <pre><code>
   with no styling of its own. Horizontal scroll (not wrap) preserves
   whitespace-sensitive config file formatting like the Dolphin controller
   mapping snippets this is mainly used for. The inner <code> resets the
   inline-code look above since it's redundant inside an already-styled
   pre box. */
.jrh-post__body pre.wp-block-code {
    background: var(--jrh-surface-alt);
    border: 1px solid var(--jrh-border);
    border-radius: 8px;
    padding: 16px 18px;
    margin: 0 0 18px;
    overflow-x: auto;
}

.jrh-post__body pre.wp-block-code code {
    display: block;
    font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
    font-size: 14px;
    line-height: 1.6;
    color: var(--jrh-text);
    white-space: pre;
    background: none;
    border: none;
    padding: 0;
}

/* Accordion blocks (see js/accordion-block.js) - theme-native replacement
   for the Advanced Accordion plugin. Same collapsible-box visual language
   as .jrh-toc elsewhere in the theme, just stacked as multiple independent
   sections instead of one box. */
.jrh-accordion {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin: 24px 0;
}

.jrh-accordion-item {
    background: var(--jrh-surface);
    border: 1px solid var(--jrh-border);
    border-radius: 8px;
    padding: 0 18px;
    overflow: hidden;
    /* Covers the whole card (not just the summary row below), so tapping
       anything inside the expanded body - links, images, etc. - doesn't
       paint the browser's default grey/beige tap-highlight overlay. */
    -webkit-tap-highlight-color: transparent;
}

.jrh-accordion-item__summary {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 12px;
    padding: 14px 0;
    margin: 0 -18px;
    padding-left: 18px;
    padding-right: 18px;
    cursor: pointer;
    list-style: none;
    /* Mobile browsers paint a grey overlay over any tapped element by
       default - tints the whole row (including the blue chevron badge)
       grey for the duration of the tap. */
    -webkit-tap-highlight-color: transparent;
    transition: background-color 0.1s ease;
}

.jrh-accordion-item__summary:hover {
    background-color: var(--jrh-surface-hover);
}

.jrh-accordion-item__summary::-webkit-details-marker {
    display: none;
}

/* Reset the browser's default H2 styling (large font-size, block margins)
   back down to match the rest of the accordion row - the H2 tag itself is
   kept for real document heading structure, it just shouldn't look like a
   giant page heading here. */
.jrh-accordion-item__heading {
    margin: 0;
    font-size: 18px;
    font-weight: 700;
    color: var(--jrh-text);
}

/* A plain small triangle read as too subtle to signal "this expands" - a
   filled, colored circle badge around it makes the affordance obvious at
   a glance, like a plus/minus toggle button. */
.jrh-accordion-item__summary::after {
    content: "\25BE";
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 24px;
    height: 24px;
    border-radius: 50%;
    background: var(--jrh-accent);
    color: #ffffff;
    font-size: 13px;
    transition: transform 0.15s ease;
}

.jrh-accordion-item[open] > .jrh-accordion-item__summary {
    padding-bottom: 12px;
    border-bottom: 1px solid var(--jrh-border);
}

.jrh-accordion-item[open] > .jrh-accordion-item__summary::after {
    transform: rotate(180deg);
}

.jrh-accordion-item__body {
    padding: 14px 0 16px;
    font-size: 18px;
    line-height: 28px;
    color: var(--jrh-text);
}

.jrh-accordion-item__body > *:last-child {
    margin-bottom: 0;
}

.jrh-post__body img {
    border-radius: 8px;
    margin: 8px 0;
    cursor: zoom-in;
}

/* Classic WordPress alignment classes - GeneratePress used to provide these
   globally, but a custom theme has to define them itself; without them,
   "align center/left/right" set in the editor has no visible effect.
   Covers both a raw <img class="aligncenter"> (classic editor) and the
   block editor's <figure class="wp-block-image aligncenter"> wrapper. */
.jrh-post__body img.aligncenter {
    margin-left: auto;
    margin-right: auto;
}

.jrh-post__body figure.aligncenter,
.jrh-post__body .wp-block-image.aligncenter {
    display: table;
    margin-left: auto;
    margin-right: auto;
}

.jrh-post__body img.alignleft,
.jrh-post__body .wp-block-image.alignleft {
    float: left;
    margin: 8px 20px 20px 0;
}

.jrh-post__body img.alignright,
.jrh-post__body .wp-block-image.alignright {
    float: right;
    margin: 8px 0 20px 20px;
}

/* NitroPay bottom anchor ad tap-shield - main.js toggles this body class
   on while a TOC/accordion box is scrolled into the bottom band of the
   viewport, where the fixed anchor ad overlays it and was swallowing taps
   meant for them. The ad stays fully visible (impressions unaffected); it
   just can't intercept clicks while something tappable sits behind it. */
body.jrh-anchor-ad-shielded #nitropayanchor,
body.jrh-anchor-ad-shielded #nitropayanchor-close {
    pointer-events: none;
}

/* Briefly blocks taps from reaching an iframe (e.g. a YouTube embed)
   revealed by just opening a TOC/accordion box - see main.js for the
   timing. Scoped to iframes only, not the whole box, so any other
   content (links, text) stays immediately tappable. */
.jrh-toc.jrh-just-opened iframe,
.jrh-accordion-item.jrh-just-opened iframe,
.jrh-toc.jrh-just-opened .jrh-embed-facade,
.jrh-accordion-item.jrh-just-opened .jrh-embed-facade {
    pointer-events: none;
}


/* Static thumbnail standing in for a YouTube embed inside a TOC/accordion
   body - see main.js. Sized to fill whatever aspect-ratio box WP core's
   embed wrapper already established, so it drops in without affecting
   layout; the real iframe stays in the DOM (display:none) so nothing is
   re-fetched when the facade is clicked. */
.jrh-embed-facade {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    border: 0;
    margin: 0;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background-color: #000;
    background-size: cover;
    background-position: center;
    cursor: pointer;
}


.jrh-embed-facade::after {
    content: "";
    width: 68px;
    height: 48px;
    border-radius: 14px;
    background: rgba(0, 0, 0, 0.75);
}

.jrh-embed-facade::before {
    content: "";
    position: absolute;
    width: 0;
    height: 0;
    border-style: solid;
    border-width: 12px 0 12px 20px;
    border-color: transparent transparent transparent #ffffff;
}

/* Back to top button - see main.js for the scroll-triggered show/hide and
   smooth-scroll click handler. Bottom offset clears the NitroPay bottom
   anchor ad, which shows at every screen size (see the anchor's
   media query), so this needs the clearance everywhere too, not just on
   wide desktop. */
.jrh-back-to-top {
    position: fixed;
    right: 20px;
    bottom: 90px;
    z-index: 90;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    border-radius: 50%;
    background: var(--jrh-accent);
    color: #ffffff;
    border: none;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
    cursor: pointer;
    opacity: 0;
    visibility: hidden;
    transform: translateY(10px);
    transition: opacity 0.2s ease, transform 0.2s ease, visibility 0.2s;
}

.jrh-back-to-top.is-visible {
    opacity: 1;
    visibility: visible;
    transform: translateY(0);
}

/* Click-to-enlarge lightbox for post/page content images - see main.js.
   Uses the same image the post already loaded rather than fetching a
   separate full-size file. */
.jrh-lightbox {
    display: none;
    position: fixed;
    inset: 0;
    z-index: 1000;
    background: rgba(0, 0, 0, 0.85);
    align-items: center;
    justify-content: center;
    padding: 40px 20px;
    cursor: zoom-out;
}

.jrh-lightbox.is-open {
    display: flex;
}

.jrh-lightbox__image {
    max-width: 100%;
    max-height: 100%;
    width: auto;
    height: auto;
    border-radius: 8px;
    margin: 0;
    cursor: default;
}

.jrh-lightbox__close {
    position: absolute;
    top: 16px;
    right: 20px;
    background: none;
    border: none;
    color: #ffffff;
    font-size: 32px;
    line-height: 1;
    cursor: pointer;
}

.jrh-post__body figure {
    margin: 24px 0;
}

/* !important because WordPress core's own block-library CSS sets
   .wp-block-image.aligncenter/.alignleft/.alignright > figcaption to
   display: table-caption, which has higher specificity than a plain
   class+element selector and was winning over this rule. */
.jrh-post__body figcaption {
    display: none !important;
}

.jrh-post__body .wp-block-embed__wrapper {
    position: relative;
    border-radius: 8px;
    overflow: hidden;
}

/* Ribbon-style section headings - any H2 the author adds in the block
   editor automatically becomes one of these, no special class needed. */
.jrh-post__body h2 {
    background: var(--jrh-accent);
    color: #ffffff;
    font-size: 22px;
    font-weight: 700;
    padding: 12px 22px;
    margin: 20px 0 10px -20px;
    display: flex;
    width: fit-content;
}

/* H3 - lighter treatment than the ribbon H2s, but still clearly branded
   rather than left as browser defaults. Rarely used in practice (guides
   typically go H2 straight to H4). */
.jrh-post__body h3 {
    color: var(--jrh-link);
    font-size: 18px;
    font-weight: 700;
    margin: 28px 0 12px;
    padding-bottom: 6px;
    border-bottom: 2px solid var(--jrh-h3-border);
}

/* H4 - a short accent underline (width: fit-content, so it hugs the text width
   instead of stretching full-width like the H3 underline), plain body
   text color so it doesn't read as a link. Excludes the accordion's own
   heading, which is also a real H4 now but needs to look like part of
   the accordion row, not a page heading. */
.jrh-post__body h4:not(.jrh-accordion-item__heading) {
    display: block;
    width: fit-content;
    color: var(--jrh-text);
    font-size: 19px;
    font-weight: 700;
    margin: 24px 0 10px;
    padding-bottom: 6px;
    border-bottom: 4px solid var(--jrh-accent);
}

.jrh-post__body h3 a,
.jrh-post__body h4 a {
    color: inherit;
    text-decoration: none;
}

/* Offsets anchor-jumped headings so the sticky header doesn't cover them
   when a TOC link (or any #anchor) scrolls the page to a heading. */
.jrh-post__body h2,
.jrh-post__body h3,
.jrh-post__body h4 {
    scroll-margin-top: 74px;
}

/* Table of contents - built directly from the post's headings, no plugin
   dependency. See joeysretrohandhelds_toc_content() in functions.php.
   A native <details>/<summary> element handles the collapse/expand
   behavior, closed by default (no "open" attribute set), no JS needed. */
.jrh-toc {
    max-width: 480px;
    margin: 0 auto 24px;
    background: var(--jrh-toc-bg);
    border: 1px solid var(--jrh-toc-border);
    border-radius: 10px;
    padding: 10px 16px;
    text-align: left;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
}

/* Tighter bottom padding only when expanded - the collapsed summary row
   needs symmetric padding to sit vertically centered in the pill; the
   asymmetry is only wanted once there's a list ending close to the edge. */
.jrh-toc[open] {
    padding-bottom: 6px;
}

.jrh-toc__title {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    cursor: pointer;
    list-style: none;
    -webkit-tap-highlight-color: transparent;
    font-size: 15px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: var(--jrh-toc-text);
}

.jrh-toc__title::-webkit-details-marker {
    display: none;
}

.jrh-toc__title::before {
    content: "\1F4C4";
    font-size: 15px;
}

.jrh-toc__title::after {
    content: "\25BE";
    font-size: 12px;
    color: var(--jrh-toc-link);
    transition: transform 0.15s ease;
}

.jrh-toc[open] .jrh-toc__title::after {
    transform: rotate(180deg);
}

.jrh-toc[open] .jrh-toc__title {
    margin-bottom: 4px;
    padding-bottom: 12px;
    border-bottom: 1px solid var(--jrh-toc-border);
}

.jrh-toc__list {
    list-style: none;
    margin: 12px 0 0;
    padding: 0;
}

.jrh-toc__item {
    margin: 0;
}

.jrh-toc__item a {
    display: block;
    color: var(--jrh-toc-link);
    font-size: 16px;
    font-weight: 700;
    line-height: 1.3;
    text-decoration: none;
    padding: 3px 10px;
    margin: 0 -10px;
    border-radius: 6px;
    transition: background 0.1s ease;
}

.jrh-toc__item a:hover {
    background: var(--jrh-toc-border);
    text-decoration: none;
}

/* Both indented one step under H2 - not doubled for H4, since guides
   typically go straight from H2 to H4 without using H3 at all. */
.jrh-toc__item--level-3 a,
.jrh-toc__item--level-4 a {
    margin-left: 16px;
    font-size: 15px;
}

/* Related guides grid - built directly from WP_Query, no plugin dependency.
   Cards reuse the homepage's .tile styles for a consistent look. */
.jrh-related-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 28px;
}

@media (min-width: 640px) {
    .jrh-related-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}

.jrh-related-card__title {
    font-size: 13px;
    font-weight: 600;
    color: var(--jrh-text);
}

/* Callout boxes - add the class in the block editor's Group block under
   Advanced > Additional CSS class(es): "callout-tip" or "callout-warning" */
.callout-tip,
.callout-warning {
    border-radius: 10px;
    margin: 24px 0;
    overflow: hidden;
    padding: 0 0 16px;
}

.callout-tip {
    background: #faeeda;
}

.callout-warning {
    background: #fcebeb;
}

.callout-tip::before,
.callout-warning::before {
    display: inline-block;
    font-style: italic;
    font-weight: 800;
    font-size: 15px;
    letter-spacing: 0.5px;
    padding: 8px 20px;
    margin: 10px 0 10px 10px;
    clip-path: polygon(0 0, 100% 0, 92% 100%, 0% 100%);
    color: #ffffff;
}

.callout-tip::before {
    content: "TIP";
    background: #854f0b;
}

.callout-warning::before {
    content: "WARNING";
    background: #791f1f;
}

.callout-tip p,
.callout-warning p {
    padding: 0 20px;
    margin: 0;
}

.callout-tip p {
    color: #412402;
}

.callout-warning p {
    color: #501313;
}

/* Buy links callout - add the class "callout-buy" to a Group block's
   Advanced > Additional CSS class(es) field. A flat full-width gold header
   bar sits above a solid black body - put the buy links inside as a
   normal paragraph. Styled after a reference "quest log" callout: bordered
   box, bold italic header label with a small icon, decorative icon in the
   body's corner. */
.callout-buy {
    margin: 24px 0;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid #e3ae28;
}

.callout-buy::before {
    content: "Buy Link";
    display: flex;
    align-items: center;
    gap: 6px;
    background: #e3ae28;
    color: #1a1a1a;
    font-weight: 800;
    font-style: italic;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-size: 14px;
    padding: 8px 16px;
}

.callout-buy p {
    position: relative;
    background: #12203c;
    color: #ffffff;
    margin: 0;
    padding: 14px 44px 14px 16px;
    font-size: 14px;
}

.callout-buy p::after {
    content: "\1F3AE";
    position: absolute;
    right: 14px;
    bottom: 10px;
    font-size: 18px;
    opacity: 0.5;
}

.callout-buy p a {
    color: #f2c94c;
    font-weight: 600;
    text-decoration: none;
}

.callout-buy p a:hover {
    text-decoration: underline;
}

/* "Looking for the X Setup Guide?" callout - auto-applied by
   joeysretrohandhelds_setup_guide_callout() in functions.php to the
   paragraph that always follows a device review's video, cross-linking to
   that device's own setup guide. Same flat-header-bar layout as
   .callout-buy, in green instead of gold to read as a different kind of
   callout (a cross-link, not a purchase link). */
.callout-setup-guide {
    margin: 24px 0;
    border-radius: 8px;
    overflow: hidden;
    border: 1px solid #3b6d11;
}

.callout-setup-guide::before {
    content: "Setup Guide";
    display: flex;
    align-items: center;
    gap: 6px;
    background: #3b6d11;
    color: #ffffff;
    font-weight: 800;
    font-style: italic;
    text-transform: uppercase;
    letter-spacing: 0.5px;
    font-size: 14px;
    padding: 8px 16px;
}

.callout-setup-guide p {
    background: var(--jrh-surface-alt);
    color: var(--jrh-text);
    margin: 0;
    padding: 14px 16px;
    font-size: 14px;
    text-align: left;
}

.callout-setup-guide p a {
    color: var(--jrh-link);
    font-weight: 700;
    text-decoration: none;
}

.callout-setup-guide p a:hover {
    text-decoration: underline;
}

/* Info pills row - add the class "info-pills" to a List block's Additional
   CSS class(es) field for a "what you'll need" style row. */
.info-pills {
    display: flex;
    gap: 10px;
    list-style: none;
    padding: 0;
    margin: 20px 0;
    flex-wrap: wrap;
}

.info-pills li {
    background: var(--jrh-accent);
    color: #ffffff;
    font-weight: 600;
    font-size: 13px;
    padding: 8px 16px;
    border-radius: 999px;
}

/* Stat box - add the class "stat-box" to a Group block for a hardware
   requirements / troubleshooting style panel. First heading inside acts as
   the title, a paragraph with class "stat-box__stats" holds the stat line,
   and a paragraph with class "stat-box__reward" holds the outcome line. */
.stat-box {
    background: var(--jrh-toc-bg);
    border: 1px solid var(--jrh-toc-border);
    border-radius: 8px;
    padding: 20px;
    margin: 24px 0;
}

.stat-box h3,
.stat-box h4 {
    margin: 0 0 8px;
    font-size: 19px;
}

.stat-box__stats {
    display: flex;
    gap: 20px;
    font-size: 13px;
    color: var(--jrh-text-muted);
    margin: 0 0 12px;
}

.stat-box__stats strong {
    color: var(--jrh-text);
}

.stat-box__reward {
    background: var(--jrh-surface);
    border-left: 4px solid #ba7517;
    padding: 10px 16px;
    font-size: 14px;
    margin: 12px 0 0;
}

.stat-box__reward strong {
    color: #854f0b;
}

.jrh-post__section-heading {
    display: inline-block;
    font-size: 26px;
    font-weight: 800;
    color: var(--jrh-text);
    margin: 40px 0 16px;
    padding-bottom: 10px;
    border-bottom: 3px solid var(--jrh-accent);
    /* Same sticky-header offset as .jrh-post__body's own anchor-jumped
       headings, so linking directly to #recent-activity etc. doesn't land
       with the heading hidden under the header. */
    scroll-margin-top: 74px;
}

/* Recent Activity/Past Winners/Club Milestones/Leaderboard headings are
   each wrapped in a self-link (href="#<id>") so this page's sections can
   be linked to directly - same look as the existing .section-heading a
   convention (inherit color, underline only on hover, not an
   "obviously-a-link" blue). */
.jrh-post__section-heading a {
    color: inherit;
    text-decoration: none;
}

.jrh-post__section-heading a:hover {
    text-decoration: underline;
}

/* Icon-timeline layout, mocked up and picked over the original single-line
   sentence format: a connecting vertical line ties each entry together,
   with a colored icon node (checkered flag/trophy) leading each row so
   status reads before the sentence does. */
.jrh-activity-feed {
    list-style: none;
    margin: 0;
    padding: 18px 20px;
    border: 1px solid var(--jrh-border);
    border-radius: 10px;
    background: var(--jrh-surface);
    /* Fixed height + scroll rather than growing to fit however many
       activity items came back (up to 30, website_sync.py) - flex:0 0 auto
       overrides the flex:1 grow this gets from
       .jrh-history-row__activity's stretch chain further down, so the
       explicit height actually sticks instead of being grown past. */
    flex: 0 0 auto;
    height: var(--jrh-scroll-box-height);
    overflow-y: auto;
}

.jrh-activity-feed__item {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    position: relative;
    /* Bumped from 18px - with Past Winners now capped to 5/page and
       Recent Activity to 6 items, a full Game of the Month/Romhack page
       (5 rows + pager) still runs a bit taller than 6 activity items;
       this closes most of that remaining gap without needing to touch
       the item count again. */
    padding-bottom: 34px;
}

.jrh-activity-feed__item:last-child {
    padding-bottom: 0;
}

/* Connects each node to the next one below it - stops short of the last
   item since there's nothing further down to connect to. */
.jrh-activity-feed__item:not(:last-child)::before {
    content: "";
    position: absolute;
    left: 15px;
    top: 32px;
    bottom: -4px;
    width: 2px;
    background: var(--jrh-border);
}

.jrh-activity-feed__node {
    flex-shrink: 0;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 15px;
    z-index: 1;
    background: var(--jrh-surface-alt);
    border: 1px solid var(--jrh-border);
}

.jrh-activity-feed__node--mastered {
    background: #f5a623;
    border-color: #f5a623;
}

.jrh-activity-feed__thumb {
    flex-shrink: 0;
    width: 40px;
    height: 40px;
    border-radius: 8px;
    background-size: cover;
    background-position: center;
    background-color: var(--jrh-toc-border);
}

.jrh-activity-feed__body {
    /* flex-basis:0 + min-width:0 (not the default auto/content-based
       basis) - a long sentence's full un-wrapped width was what the
       browser used to decide whether it fits alongside the node/thumbnail
       on mobile, so it never did and they got pushed onto their own row
       above it. Matches the fix for the same class of bug in the header's
       search bar earlier this project. */
    flex: 1 1 0%;
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 4px;
    padding-top: 4px;
}

.jrh-activity-feed__text {
    font-size: 15px;
    font-weight: 500;
    color: var(--jrh-text);
}

.jrh-activity-feed__text strong {
    font-weight: 700;
    color: var(--jrh-text);
}

.jrh-activity-feed__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    font-size: 12.5px;
    color: var(--jrh-text-faint);
}

.jrh-activity-feed__pill {
    display: inline-flex;
    font-size: 11px;
    font-weight: 700;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--jrh-surface-alt);
    color: var(--jrh-text-muted);
}

.jrh-activity-feed__pill--first {
    background: #f5a623;
    color: #1a1a1a;
}

.jrh-activity-feed__pill--hardcore {
    background: #1a1a1a;
    color: #ffffff;
}

.jrh-activity-feed__time {
    color: var(--jrh-text-faint);
}

.jrh-post__related {
    width: 100%;
    margin-top: 20px;
}

/* Recent Activity + Past Winners side by side - Past Winners used to sit
   full-width and stacked below the activity feed, leaving a lot of empty
   space next to the (narrower) activity feed on wide screens. Stacks back
   to full width below the breakpoint since neither column has room to
   stay readable at half-width on mobile. */
.jrh-history-row {
    display: flex;
    /* stretch (the flex default) rather than flex-start, so both columns
       match the height of whichever is taller - the shorter one just
       carries extra empty space inside its own bordered frame instead of
       the two frames ending at different heights. */
    align-items: stretch;
    gap: 24px;
    margin-top: 20px;
}

.jrh-history-row__activity,
.jrh-history-row__winners {
    display: flex;
    flex-direction: column;
    flex: 1.4;
    min-width: 0;
}

.jrh-history-row__winners {
    flex: 1;
}

.jrh-history-row__activity .jrh-post__related,
.jrh-history-row__winners .jrh-picks-tabs {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    margin-top: 0;
    margin-bottom: 0;
}

.jrh-history-row__activity .jrh-activity-feed {
    /* Overrides the base .jrh-activity-feed rule back to flex:0 0 auto
       (not 1) - this selector's higher specificity would otherwise win
       and let the box grow past its fixed height again. */
    flex: 0 0 auto;
}

.jrh-history-row__winners .jrh-picks-tabs__panel {
    flex-direction: column;
    flex: 1;
    min-width: 0;
    min-height: 0;
    /* Positioning context for the absolutely-filled .jrh-paged-list
       inside - see the .jrh-paged-list absolute-fill rule for why. */
    position: relative;
}

.jrh-history-row__winners #jrh-picks-tab-gotw:checked ~ .jrh-picks-tabs__panel--gotw,
.jrh-history-row__winners #jrh-picks-tab-gotm:checked ~ .jrh-picks-tabs__panel--gotm,
.jrh-history-row__winners #jrh-picks-tab-rhotm:checked ~ .jrh-picks-tabs__panel--rhotm,
.jrh-history-row__winners #jrh-picks-tab-gots:checked ~ .jrh-picks-tabs__panel--gots {
    display: flex;
}

@media (max-width: 860px) {
    .jrh-history-row {
        flex-direction: column;
    }

    .jrh-history-row__activity,
    .jrh-history-row__winners {
        flex: none;
        width: 100%;
    }

    /* (The stacked-layout fallback for the paged-list boxes lives further
       down, AFTER the absolute-fill rule it overrides - a media query adds
       no cascade priority on its own, so at equal specificity the later
       source-order rule wins; placed up here it silently lost to the
       absolute-fill rule below it.) */
}

.jrh-post__gotm {
    display: flex;
    align-items: center;
    gap: 14px;
    background: #f3e8ff;
    border-radius: 12px;
    padding: 16px 20px;
    margin: 32px 0;
    text-decoration: none;
}

.jrh-post__gotm-icon {
    font-size: 24px;
}

.jrh-post__gotm-text-wrap {
    flex: 1;
    min-width: 0;
}

.jrh-post__gotm-title {
    display: block;
    font-size: 14px;
    font-weight: 700;
    color: #6b21a8;
}

.jrh-post__gotm-subtitle {
    display: block;
    font-size: 13px;
    color: #6b21a8;
}

.jrh-post__gotm-thumbs {
    display: flex;
    gap: 8px;
}

.jrh-post__gotm-thumb {
    width: 48px;
    height: 48px;
    border-radius: 8px;
    background-size: cover;
    background-position: center;
    background-color: rgba(107, 33, 168, 0.15);
    border: 2px solid #ffffff;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
    flex-shrink: 0;
}

@media (max-width: 480px) {
    .jrh-post__gotm-thumbs {
        display: none;
    }
}

.jrh-post__gotm-arrow {
    color: #6b21a8;
    font-size: 18px;
    flex-shrink: 0;
}

/* Play Retro Games Club page - see page-play-retro-games-club.php. Data
   comes from the jrh_current_picks option, synced from the bot-of-the-month
   Discord bot rather than queried live, so a card shows the
   .jrh-pick-card__empty fallback if that event type has never synced
   rather than erroring. */
/* Discord CTA + live countdown side by side - the countdown ticks
   client-side (js/main.js's .jrh-countdown block) from a raw ISO
   timestamp the bot computes as the soonest upcoming transition across
   all 4 event types (see next_event in website_sync.py). */
.jrh-banner-row {
    display: flex;
    flex-wrap: wrap;
    gap: 14px;
    margin: 32px 0 0;
}

.jrh-discord-banner {
    display: flex;
    align-items: center;
    gap: 12px;
    background: #5865f2;
    color: #ffffff;
    text-decoration: none;
    border-radius: 10px;
    padding: 18px 22px;
    font-size: 16px;
    font-weight: 700;
    flex: 1 1 280px;
    min-width: 0;
}

.jrh-discord-banner__icon {
    font-size: 24px;
}

.jrh-discord-banner__text-wrap {
    display: flex;
    flex-direction: column;
    gap: 2px;
    flex: 1;
    min-width: 0;
}

.jrh-discord-banner__subtitle {
    font-size: 13px;
    font-weight: 400;
    opacity: 0.85;
}

.jrh-discord-banner__arrow {
    font-size: 20px;
    transition: transform 0.15s ease;
}

.jrh-discord-banner:hover .jrh-discord-banner__arrow {
    transform: translateX(4px);
}

/* Solid orange, same as the Discord banner's solid blurple next to it -
   a fixed background/text pairing rather than theme-variable-driven,
   so it reads the same (and stays legible) in both light and dark mode
   instead of needing separate light/dark color rules. */
.jrh-countdown {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    flex: 1 1 220px;
    min-width: 0;
    background: #e8720e;
    border: 1px solid #c25f08;
    border-radius: 10px;
    padding: 18px 22px;
    text-align: center;
    text-decoration: none;
    transition: background 0.15s ease;
}

.jrh-countdown:hover {
    background: #d4680c;
}

.jrh-countdown__label {
    font-size: 16px;
    font-weight: 700;
    color: #ffffff;
}

.jrh-countdown__timer {
    font-size: 22px;
    font-weight: 800;
    color: #ffffff;
    font-variant-numeric: tabular-nums;
}

/* Horizontal cards stacked in a single column, rather than the earlier
   2x2 grid of vertical cards - with everything this section has grown to
   show (rating, completion/mastery counts, hours to beat, description,
   links, next-period info), vertical cards had gotten tall; horizontal
   layout collapses the stats into one compact row so four picks' worth
   of info can be scanned without as much scrolling. */
.jrh-picks-grid {
    display: flex;
    flex-direction: column;
    gap: 14px;
    margin: 32px 0;
}

/* Tab-based alternate view of the same picks (see
   joeysretrohandhelds_render_pick_card() in functions.php) - a pure-CSS
   radio trick, no JS: each hidden radio's :checked state toggles its
   matching panel and highlights its matching label via the general
   sibling combinator. Radios/nav/panels are siblings in DOM order (see
   the template), which is what makes the ~ combinator reach forward from
   an early radio to a later panel. */
.jrh-picks-tabs {
    margin: 40px 0 32px;
}

.jrh-picks-tabs__radio {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

/* 2x2 grid on mobile rather than flex-wrap - each pill's width used to
   be sized to its own text, so wrapping left-aligned 4 differently-sized
   pills produced a ragged "2, then 1 alone, then 1 alone" layout instead
   of anything lined up. A grid gives every tab the same cell width so
   they align into neat rows regardless of label length. Desktop has
   enough room for all 4 in a single row, so it reverts to flex there. */
.jrh-picks-tabs__nav {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 8px;
    margin-bottom: 20px;
}

@media (min-width: 700px) {
    .jrh-picks-tabs__nav {
        display: flex;
        flex-wrap: wrap;
    }
}

.jrh-picks-tabs__btn {
    /* The grid stretches every cell in a row to match its tallest sibling
       (e.g. "Romhack of the Month" wrapping to 2 lines stretches "Game of
       the Season" next to it too) - flex + centering here keeps the label
       vertically centered within that stretched height instead of stuck
       at the top. */
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    padding: 10px 18px;
    border-radius: 999px;
    border: 1px solid var(--jrh-border);
    background: var(--jrh-surface);
    color: var(--jrh-text-muted);
    font-size: 14px;
    font-weight: 700;
    text-align: center;
    user-select: none;
}

.jrh-picks-tabs__panel {
    display: none;
}

/* Border/radius/background now live on the wrapping .jrh-paged-list
   instead (it also contains the pager below this list, same as
   Leaderboard's setup) - this is just the scrollable inner list. Bounded
   to the last 12 months bot-side (website_sync.py) AND paginated 5/page
   theme-side - both together, not one instead of the other. */
.jrh-winners-history {
    /* Fills whatever's left in .jrh-paged-list's flexible box after the
       pager (if any) takes its own space. Basis 0% + min-height:0 for
       the same reason as .jrh-paged-list itself: this list's content
       must never contribute to the column's natural height, only absorb
       leftover space and scroll within it. */
    flex: 1 1 0%;
    min-height: 0;
    overflow-y: auto;
}

.jrh-winners-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 10px;
    padding: 12px 16px;
    font-size: 14px;
    background: var(--jrh-surface);
}

.jrh-winners-row__thumb {
    flex-shrink: 0;
    width: 36px;
    height: 36px;
    border-radius: 6px;
    background-size: cover;
    background-position: center;
    background-color: var(--jrh-toc-border);
}

.jrh-winners-row__title {
    /* flex-basis:0 (not auto) - matches the fix used for the activity
       feed and header search bar earlier this project: with a
       content-based auto basis, the browser used each title's full
       un-wrapped width to decide the flex line, so longer titles wrapped
       unpredictably differently than shorter ones (hltb and the date
       landing on inconsistent lines row to row). Basis:0 + min-width:0
       lets the title's own text wrap internally at a predictable point
       instead. */
    flex: 1 1 0%;
    min-width: 0;
    font-weight: 700;
    color: var(--jrh-text);
}

.jrh-winners-row__title a {
    color: var(--jrh-text);
    text-decoration: none;
}

.jrh-winners-row__title a:hover {
    text-decoration: underline;
}

/* hltb and the date are grouped into one flex item (see the template)
   so they move together as a unit if the row ever needs to wrap, rather
   than being able to split apart onto different lines from each other. */
.jrh-winners-row__period {
    flex-shrink: 0;
    color: var(--jrh-text-muted);
    font-size: 13px;
}

/* Generic paginated-list wrapper - currently only Leaderboard's overall
   ranks (25/page). Originally Past Winners' own pattern; freed up and
   generalized when that section switched to a plain scroll list (see
   .jrh-winners-history above) and pagination moved here instead, since a
   growing leaderboard needs it more than a 12-month-bounded winners list
   does. */
/* No fixed height here on purpose. The columns that contain a paged list
   (Past Winners, Leaderboard) have a TAB ROW above the box that their
   neighbor column (Recent Activity, Club Milestones) doesn't have - so
   giving both sides' boxes the same fixed height made the tabbed side's
   box END lower than its neighbor's by exactly the tab row's height,
   which is the visible column-bottom mismatch this replaces. Instead the
   box is flexible: it fills whatever vertical space remains in its own
   column below the tabs, and the row's overall height is set by the
   OTHER column (Recent Activity's fixed-height feed / Milestones'
   natural list height) via the row's align-items:stretch chain - so both
   columns' bottoms land on the same line regardless of tabs. The inner
   list (.jrh-winners-history / .jrh-leaderboard__ranks) then shares this
   flexible space with the pager and scrolls within what's left. */
.jrh-paged-list {
    display: flex;
    flex-direction: column;
    flex: 1 1 0%;
    min-height: 0;
    border: 1px solid var(--jrh-border);
    border-radius: 10px;
    overflow: hidden;
    background: var(--jrh-surface);
}

/* Absolute-fill inside the side-by-side rows: the box is taken OUT of the
   layout's height computation entirely and just paints over its panel's
   area, so its content (25 leaderboard rows, a year of winners) CANNOT
   stretch the row taller than the neighbor column - the neighbor (Recent
   Activity's fixed-height feed / Club Milestones' natural list) alone
   defines the row height, the tabbed column stretches to match, the panel
   fills the leftover below the tabs, and this box fills the panel. Plain
   flex zero-basis was tried first and failed: a 0% basis resolving
   against an indefinite column height falls back to content size, letting
   the full list drive the row height after all (the actual cause of the
   Leaderboard column running ~900px past Milestones). Position:absolute
   has no such fallback - contribution is zero, guaranteed. */
.jrh-history-row__winners .jrh-picks-tabs__panel .jrh-paged-list,
.jrh-club-row__leaderboard .jrh-leaderboard__panel .jrh-paged-list {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
}

.jrh-paged-list__pager {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 16px;
    padding: 14px 16px;
    border-top: 1px solid var(--jrh-border);
    background: var(--jrh-surface);
}

.jrh-paged-list__pager-prev,
.jrh-paged-list__pager-next {
    cursor: pointer;
    padding: 6px 14px;
    border-radius: 6px;
    border: 1px solid var(--jrh-border);
    background: var(--jrh-surface);
    color: var(--jrh-text);
    font-size: 13px;
    font-weight: 700;
}

.jrh-paged-list__pager-prev:disabled,
.jrh-paged-list__pager-next:disabled {
    opacity: 0.4;
    cursor: not-allowed;
}

.jrh-paged-list__pager-status {
    color: var(--jrh-text-muted);
    font-size: 13px;
}

#jrh-picks-tab-gotw:checked ~ .jrh-picks-tabs__nav label[for="jrh-picks-tab-gotw"],
#jrh-picks-tab-gotm:checked ~ .jrh-picks-tabs__nav label[for="jrh-picks-tab-gotm"],
#jrh-picks-tab-rhotm:checked ~ .jrh-picks-tabs__nav label[for="jrh-picks-tab-rhotm"],
#jrh-picks-tab-gots:checked ~ .jrh-picks-tabs__nav label[for="jrh-picks-tab-gots"] {
    background: var(--jrh-accent);
    border-color: var(--jrh-accent);
    color: #ffffff;
}

#jrh-picks-tab-gotw:checked ~ .jrh-picks-tabs__panel--gotw,
#jrh-picks-tab-gotm:checked ~ .jrh-picks-tabs__panel--gotm,
#jrh-picks-tab-rhotm:checked ~ .jrh-picks-tabs__panel--rhotm,
#jrh-picks-tab-gots:checked ~ .jrh-picks-tabs__panel--gots {
    display: block;
}

.jrh-pick-card {
    background: var(--jrh-surface);
    border: 1px solid var(--jrh-border);
    border-radius: 10px;
    overflow: hidden;
    display: flex;
}

/* Widened from 168px - the row got taller once the label moved above
   the title and the 2-full-sentence description added more height, and
   the narrower thumbnail was left looking noticeably taller than wide
   as a result. There's plenty of spare width in the card to give it
   back some breathing room instead of trimming the row's height. */
.jrh-pick-card__image {
    flex: 0 0 240px;
    background-color: var(--jrh-toc-border);
    background-size: cover;
    background-position: center;
}

.jrh-pick-card__body {
    flex: 1;
    min-width: 0;
    padding: 14px 18px;
    display: flex;
    flex-direction: column;
    gap: 6px;
}

/* Sits above the title now rather than overlaid on the thumbnail - a
   plain inline badge, so no absolute positioning/anchoring is needed
   (this also means the "hasn't synced yet" fallback, which never had a
   thumbnail to overlay in the first place, needs no special-case CSS of
   its own anymore - it's the same element in the same place either
   way). */
.jrh-pick-card__labels {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
}

.jrh-pick-card__label {
    display: inline-flex;
    font-size: 11px;
    font-weight: 800;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    color: #ffffff;
    background: var(--jrh-accent);
    padding: 4px 10px;
    border-radius: 999px;
}

/* Same solid orange as the countdown card, so "next transition" info reads
   consistently as one visual language across the page. */
.jrh-pick-card__label--next {
    background: #e8720e;
}

.jrh-pick-card__top-row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 8px;
}

.jrh-pick-card__title {
    font-size: 18px;
    font-weight: 800;
    margin: 0;
    color: var(--jrh-text);
}

/* Rating/completion/mastery/hours-to-beat collapse into one row instead
   of four separate stacked lines - the main space-saving move that makes
   the horizontal layout worth it. */
.jrh-pick-card__stat-row {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    font-size: 14px;
    color: var(--jrh-text);
}

.jrh-pick-card__stat {
    display: flex;
    align-items: center;
    gap: 5px;
    white-space: nowrap;
}

.jrh-pick-card__stat--rating {
    color: var(--jrh-text);
    font-weight: 700;
}

.jrh-pick-card__rating-star {
    color: #f5a623;
}

/* Truncated to its first 2 complete sentences server-side (see
   joeysretrohandhelds_first_sentences() in functions.php), not by line
   count - no line-clamp needed here, the full 2 sentences just wrap
   naturally to however many lines they need. */
.jrh-pick-card__description {
    font-size: 14.5px;
    line-height: 1.5;
    color: var(--jrh-text);
    margin: 2px 0 0;
}

.jrh-pick-card__bottom-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin-top: auto;
    padding-top: 6px;
}

.jrh-pick-card__links {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
}

.jrh-pick-card__link {
    font-size: 13.5px;
    font-weight: 700;
    color: var(--jrh-link);
    background: transparent;
    padding: 5px 12px;
    border: 1px solid var(--jrh-border);
    border-radius: 999px;
    text-decoration: none;
}

.jrh-pick-card__link:hover {
    background: var(--jrh-surface-alt);
}

.jrh-pick-card__empty {
    font-size: 14px;
    color: var(--jrh-text-muted);
    margin: 12px 0 0;
}

@media (max-width: 620px) {
    .jrh-pick-card {
        flex-direction: column;
    }

    .jrh-pick-card__image {
        flex-basis: auto;
        aspect-ratio: 16 / 9;
    }
}

.jrh-picks-updated {
    font-size: 12px;
    color: var(--jrh-text-faint);
    margin-top: 20px;
}

/* Club Milestones + Leaderboard - a second side-by-side row below Recent
   Activity/Past Winners. Milestones is always a short, naturally-sized
   list (one row per stat the club tracks, currently 9) - rather than
   giving Leaderboard its own independent fixed height (which could easily
   run taller than Milestones now that it can hold up to 25 rows/page,
   leaving Milestones stretched with awkward blank space below its real
   content), Leaderboard's box height is set in JS to match Milestones'
   own natural rendered height exactly (see js/main.js's
   "match leaderboard height to milestones" block). This CSS just makes
   sure Milestones itself never stretches beyond its natural size. */
.jrh-club-row {
    display: flex;
    align-items: stretch;
    gap: 24px;
    margin-top: 32px;
}

.jrh-club-row__milestones,
.jrh-club-row__leaderboard {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

.jrh-club-row__milestones .jrh-milestones,
.jrh-club-row__leaderboard .jrh-leaderboard {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
}

.jrh-club-row__milestones .jrh-milestones__list {
    /* Stays its own natural height - not flex:1 - so the row's height is
       driven by Milestones' real content, and Leaderboard's box (set via
       JS to match this exact height) is what adapts, not the other way
       around. */
    flex: 0 0 auto;
}

.jrh-club-row__leaderboard .jrh-leaderboard__panel {
    flex-direction: column;
    flex: 1;
    min-width: 0;
    min-height: 0;
    /* Positioning context for the absolutely-filled .jrh-paged-list
       inside - see the .jrh-paged-list absolute-fill rule for why. */
    position: relative;
}

.jrh-club-row__leaderboard #jrh-leaderboard-month:checked ~ .jrh-leaderboard__panel--month,
.jrh-club-row__leaderboard #jrh-leaderboard-alltime:checked ~ .jrh-leaderboard__panel--alltime {
    display: flex;
}

@media (max-width: 860px) {
    .jrh-club-row {
        flex-direction: column;
    }
}

.jrh-milestones__list {
    list-style: none;
    margin: 0;
    padding: 0;
    border: 1px solid var(--jrh-border);
    border-radius: 10px;
    overflow: hidden;
    background: var(--jrh-surface);
}

.jrh-milestones__item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px 10px;
    padding: 12px 16px;
    font-size: 14px;
}

.jrh-milestones__label {
    flex: 1 1 0%;
    min-width: 0;
    font-weight: 700;
    color: var(--jrh-text);
}

.jrh-milestones__count {
    flex-shrink: 0;
    font-weight: 800;
    color: var(--jrh-text);
    font-variant-numeric: tabular-nums;
}

.jrh-milestones__next {
    flex-basis: 100%;
    font-size: 12.5px;
    color: var(--jrh-text-muted);
}

.jrh-leaderboard__radio {
    position: absolute;
    opacity: 0;
    pointer-events: none;
}

.jrh-leaderboard__tabs {
    display: flex;
    gap: 8px;
    margin-bottom: 14px;
}

.jrh-leaderboard__tab {
    cursor: pointer;
    padding: 8px 16px;
    border-radius: 999px;
    border: 1px solid var(--jrh-border);
    background: var(--jrh-surface);
    color: var(--jrh-text-muted);
    font-size: 13px;
    font-weight: 700;
    user-select: none;
}

#jrh-leaderboard-month:checked ~ .jrh-leaderboard__tabs label[for="jrh-leaderboard-month"],
#jrh-leaderboard-alltime:checked ~ .jrh-leaderboard__tabs label[for="jrh-leaderboard-alltime"] {
    background: var(--jrh-accent);
    border-color: var(--jrh-accent);
    color: #ffffff;
}

.jrh-leaderboard__panel {
    display: none;
}

#jrh-leaderboard-month:checked ~ .jrh-leaderboard__panel--month,
#jrh-leaderboard-alltime:checked ~ .jrh-leaderboard__panel--alltime {
    display: block;
}

.jrh-leaderboard__ranks {
    /* Border/radius/background live on the wrapping .jrh-paged-list - this
       list just fills whatever's left in that flexible box after the
       pager (if any) takes its own space. Basis 0% + min-height:0 so the
       ranks' content (up to 25 rows/page) never contributes to the club
       row's natural height - Milestones' natural list height is what
       drives that row, and this list scrolls within its share. */
    list-style: none;
    margin: 0;
    padding: 0;
    flex: 1 1 0%;
    min-height: 0;
    overflow-y: auto;
}

/* Stacked layout: there's no side-by-side neighbor column to define a row
   height anymore, so the absolute-fill paged-list boxes (whose panels get
   their height purely from the neighbor-driven stretch chain) would sit
   in a 0-height panel and vanish. Back to normal flow with the fixed
   scroll-box height - column-bottom alignment is meaningless when stacked
   anyway. This block MUST come after the absolute-fill rule and the base
   .jrh-winners-history/.jrh-leaderboard__ranks rules in source order: a
   media query adds no cascade priority of its own, so at equal
   specificity it only wins by being later. Covers both side-by-side rows
   (they stack at the same breakpoint). */
@media (max-width: 860px) {
    .jrh-history-row__winners .jrh-picks-tabs__panel .jrh-paged-list,
    .jrh-club-row__leaderboard .jrh-leaderboard__panel .jrh-paged-list {
        position: static;
        flex: 0 0 auto;
    }

    .jrh-winners-history,
    .jrh-leaderboard__ranks {
        flex: 0 0 auto;
        height: var(--jrh-scroll-box-height);
    }
}

.jrh-leaderboard__rank {
    display: flex;
    align-items: center;
    gap: 12px;
    padding: 10px 16px;
    font-size: 14px;
}

.jrh-leaderboard__medal {
    flex-shrink: 0;
    width: 24px;
    text-align: center;
    font-size: 15px;
    font-weight: 700;
    color: var(--jrh-text-muted);
}

.jrh-leaderboard__name {
    flex: 1 1 0%;
    min-width: 0;
    font-weight: 700;
    color: var(--jrh-text);
}

.jrh-leaderboard__total {
    flex-shrink: 0;
    font-weight: 800;
    color: var(--jrh-text);
    font-variant-numeric: tabular-nums;
}

/* Comments - see comments.php and joeysretrohandhelds_comment() in
   functions.php for the markup this styles. */

.jrh-comments {
    margin-top: 40px;
    max-width: 760px;
}

.jrh-comments__list,
.jrh-comments__list .children {
    list-style: none;
    margin: 0;
    padding: 0;
}

.jrh-comments__list {
    margin: 0 0 32px;
}

.jrh-comments__list .children {
    margin: 16px 0 0 52px;
}

@media (max-width: 500px) {
    .jrh-comments__list .children {
        margin-left: 24px;
    }

    .jrh-comment__avatar {
        width: 32px;
        height: 32px;
    }
}

.jrh-comment {
    margin-bottom: 20px;
}

.jrh-comment__body {
    display: flex;
    gap: 12px;
}

.jrh-comment__avatar {
    width: 40px;
    height: 40px;
    border-radius: 50%;
    flex-shrink: 0;
    object-fit: cover;
}

.jrh-comment__content {
    flex: 1;
    min-width: 0;
}

.jrh-comment__meta {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 4px;
}

.jrh-comment__author {
    font-weight: 700;
    font-size: 14px;
    color: var(--jrh-text);
}

.jrh-comment__date a {
    font-size: 12px;
    color: var(--jrh-text-faint);
    text-decoration: none;
}

.jrh-comment__date a:hover {
    text-decoration: underline;
}

.jrh-comment__pending {
    font-size: 13px;
    color: #854f0b;
    background: #faeeda;
    padding: 6px 12px;
    border-radius: 6px;
    margin: 0 0 8px;
}

.jrh-comment__text {
    font-size: 14px;
    line-height: 1.6;
    color: var(--jrh-text);
}

.jrh-comment__text p {
    margin: 0 0 10px;
}

.jrh-comment__text a {
    color: var(--jrh-link);
    text-decoration: none;
}

.jrh-comment__text a:hover {
    text-decoration: underline;
}

.jrh-comment__reply a {
    font-size: 13px;
    font-weight: 600;
    color: var(--jrh-accent);
    text-decoration: none;
}

.jrh-comment__reply a:hover {
    text-decoration: underline;
}

.jrh-comments__closed {
    color: var(--jrh-text-muted);
    font-size: 14px;
}

/* Comment form */

.jrh-comments #reply-title {
    font-size: 18px;
    font-weight: 700;
    color: var(--jrh-text);
    margin: 0 0 16px;
}

.jrh-comment-form {
    max-width: 560px;
}

.jrh-comment-form p {
    margin: 0 0 14px;
}

.jrh-comment-form label {
    display: block;
    font-size: 13px;
    font-weight: 600;
    color: var(--jrh-text-muted);
    margin-bottom: 4px;
}

.jrh-comment-form input[type="text"],
.jrh-comment-form input[type="email"],
.jrh-comment-form input[type="url"],
.jrh-comment-form__textarea {
    width: 100%;
    padding: 10px 12px;
    border: 1px solid var(--jrh-border);
    border-radius: 6px;
    font-family: inherit;
    background: var(--jrh-surface);
    color: var(--jrh-text);
    font-size: 14px;
}

.jrh-comment-form__textarea {
    resize: vertical;
}

.jrh-comment-form__submit {
    background: var(--jrh-accent);
    color: #ffffff;
    border: none;
    border-radius: 6px;
    padding: 10px 24px;
    font-size: 14px;
    font-weight: 600;
    cursor: pointer;
}

.jrh-comment-form__submit:hover {
    background: #185fa5;
}
