/* ===== HomeKey List view ===== */
.vovo-hk-list {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

.vovo-hk-list-card {
    display: grid;
    grid-template-columns: 340px 1fr auto;
    gap: 1.25rem;
    align-items: stretch;
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 10px;
    padding: 1rem;
}

.vovo-hk-list-card-photo {
    position: relative;
    border-radius: 8px;
    overflow: hidden;
    height: 220px !important;
}

.vovo-hk-list-card-photo img {
    width: 100% !important;
    height: 100% !important;
    object-fit: cover !important;
    display: block;
}

.vovo-hk-pill {
    position: absolute;
    top: 0.6rem;
    left: 0.6rem;
    z-index: 2;
    padding: 0.25rem 0.65rem;
    border-radius: 5px;
    font-size: 0.72rem;
    font-weight: 700;
    color: #fff;
}

.vovo-hk-pill-new { background: #2563eb; }
.vovo-hk-pill-reduced { background: #dc2626; }

.vovo-hk-photo-count {
    position: absolute;
    bottom: 0.6rem;
    left: 0.6rem;
    background: rgba(15, 23, 42, 0.7);
    color: #fff;
    font-size: 0.72rem;
    padding: 0.2rem 0.5rem;
    border-radius: 4px;
    display: inline-flex;
    align-items: center;
    gap: 0.25rem;
}

.vovo-icon {
    display: inline-block;
    vertical-align: -2px;
    flex-shrink: 0;
}

.vovo-icon-light {
    color: #fff;
}

.vovo-hk-list-card-body {
    display: flex;
    flex-direction: column;
    justify-content: center;
    gap: 0.3rem;
}

/* v0.9.23: price-row wraps the price and the StandardStatus pill on
   one line — flex baseline alignment so the pill sits nicely against
   the large price number. flex-wrap: wrap keeps the pill from getting
   clipped at narrow card widths. */
.vovo-hk-list-card-price-row {
    display: flex;
    align-items: baseline;
    gap: 0.6rem;
    flex-wrap: wrap;
}

.vovo-hk-list-card-price {
    font-size: 1.4rem;
    font-weight: 700;
    color: #0f172a;
    text-decoration: none;
}

.vovo-hk-list-card-address {
    font-weight: 600;
    color: #1e293b;
    text-decoration: none;
}

.vovo-hk-list-card-city {
    font-size: 0.85rem;
    color: #64748b;
}

.vovo-hk-list-card-specs {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem 1.25rem;
    font-size: 0.85rem;
    color: #334155;
    margin-top: 0.4rem;
}

.vovo-hk-list-card-specs span {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    white-space: nowrap;
}

.vovo-hk-list-card-specs-secondary {
    color: #64748b;
    font-size: 0.8rem;
}

.vovo-hk-list-card-mls {
    color: #94a3b8;
}

.vovo-hk-list-card-actions {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    justify-content: space-between;
    gap: 0.75rem;
    min-width: 300px;
}

.vovo-hk-favorite-btn {
    width: 38px;
    height: 38px;
    border-radius: 50%;
    border: none !important;
    background: var(--vovo-favorite-icon-bg, #ffffff) !important;
    color: var(--vovo-favorite-icon-color, #334155) !important;
    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.35);
    font-size: 1.35rem;
    cursor: pointer;
}

.vovo-hk-favorite-btn.active {
    background: var(--vovo-favorite-icon-active-bg, #e11d48) !important;
    color: var(--vovo-favorite-icon-active-color, #ffffff) !important;
    border-color: transparent !important;
}

.vovo-hk-list-card-buttons {
    display: flex;
    flex-direction: row;
    gap: 0.5rem;
    width: 100%;
}

.vovo-hk-list-card-buttons .vovo-hk-btn-outline,
.vovo-hk-list-card-buttons .vovo-hk-btn-solid {
    flex: 1;
    white-space: nowrap;
}

.vovo-hk-btn-outline,
.vovo-hk-btn-solid {
    display: block;
    text-align: center;
    padding: 0.55rem 1rem;
    border-radius: 6px;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
    text-decoration: none;
    white-space: nowrap;
}

.vovo-hk-btn-outline {
    background: #fff;
    border: 1px solid #2563eb;
    color: #2563eb;
}

.vovo-hk-btn-outline.active {
    /* v0.9.22: was hard-coded #2563eb (blue). Now reads the shared
       --vovo-basket-active-bg custom property so the list-view
       In Basket button matches the detail-page button and the small
       icon in save-actions.php — one source of truth, admin-
       configurable via Settings > Appearance & Badges. Fallback
       matches the setting's new green default. */
    background: var(--vovo-basket-active-bg, #10b981);
    border-color: var(--vovo-basket-active-bg, #10b981);
    color: #fff;
}

.vovo-hk-btn-solid {
    /* v0.9.37: !important added to every property here. Live-site
       screenshots showed every "solid" button (Search, Reset, Apply)
       rendering as a hollow pink-outlined button matching the theme's
       own accent color instead of the filled blue this rule specifies
       — the same class of Hello Elementor button-reset conflict this
       project has fixed repeatedly elsewhere (thumb buttons, clear
       buttons, nav arrows), just never applied here since this rule
       predates that pattern being established. */
    background: #2563eb !important;
    border: 1px solid #2563eb !important;
    color: #fff !important;
}

.vovo-hk-btn-solid:hover {
    background: #1d4ed8 !important;
}

@media (max-width: 820px) {
    .vovo-hk-list-card {
        grid-template-columns: 1fr;
    }

    .vovo-hk-list-card-actions {
        flex-direction: row;
        align-items: center;
        min-width: 0;
    }

    .vovo-hk-list-card-buttons {
        flex-direction: column;
    }
}

/* ===== HomeKey filter bar (Split view) ===== */
.vovo-hk-filter-bar {
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 10px;
    padding: 0.85rem 1rem;
    margin-bottom: 1rem;
}

.vovo-hk-filter-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.6rem;
    align-items: center;
    position: relative;
}

.vovo-hk-filter-search {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    flex: 1 1 260px;
    border: 1px solid #cbd5e1;
    border-radius: 6px;
    padding: 0.5rem 0.75rem;
}

.vovo-hk-filter-search input {
    border: none;
    outline: none;
    font-size: 0.9rem;
    flex: 1;
    min-width: 0;
}

/* v0.9.36: MLS# is now its own standalone field (Tony: "MLS# seems
   lost within city search field... should have its own border") —
   previously nested inside .vovo-hk-filter-search sharing one border
   with the city chips, distinguished only by a thin border-left
   divider line that wasn't visually clear enough. It's now a sibling
   .vovo-hk-filter-field, which already gets its own full border from
   the generic `.vovo-hk-filter-field input[type="text"]` rule below —
   this class only needs to set a sensible width. */
.vovo-hk-filter-mls-field {
    flex: 0 0 auto;
}

.vovo-hk-filter-mls {
    width: 130px;
}

.vovo-hk-filter-field select,
.vovo-hk-filter-field input[type="text"] {
    padding: 0.5rem 0.6rem;
    border: 1px solid #cbd5e1;
    border-radius: 6px;
    font-size: 0.85rem;
}

.vovo-hk-filter-dropdown-btn {
    padding: 0.5rem 0.85rem;
    border: 1px solid #cbd5e1;
    border-radius: 6px;
    background: #fff;
    font-size: 0.85rem;
    cursor: pointer;
    position: relative;
    display: flex;
    align-items: center;
    gap: 0.35rem;
}

.vovo-hk-filter-badge {
    background: #2563eb;
    color: #fff;
    border-radius: 999px;
    font-size: 0.7rem;
    padding: 0.05rem 0.4rem;
}

.vovo-hk-filter-dropdown {
    position: absolute;
    top: calc(100% + 0.5rem);
    left: 0;
    background: #fff;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
    padding: 1rem;
    z-index: 50;
    display: flex;
    flex-direction: column;
    gap: 0.75rem;
    min-width: 220px;
}

.vovo-hk-filter-dropdown-more {
    min-width: 280px;
}

.vovo-hk-filter-dropdown label {
    display: flex;
    justify-content: space-between;
    align-items: center;
    gap: 0.5rem;
    font-size: 0.85rem;
}

.vovo-hk-filter-checkbox {
    justify-content: flex-start !important;
}

/* v0.9.25: With Photos checkbox on the main filter bar (moved out of
   the More Filters popup, see filter-bar-homekey.php). The base
   `.vovo-hk-filter-checkbox` rule was written for popup use where the
   parent is a column-flex; here on the main row we need horizontal
   centering to line up with the other pill-shaped inline controls
   (Beds/Baths selects, More Filters button). White-space nowrap
   keeps the "Photos" label from wrapping when the row gets tight. */
.vovo-hk-filter-checkbox-inline {
    display: inline-flex !important;
    align-items: center;
    gap: 0.35rem;
    white-space: nowrap;
    font-size: 0.85rem;
    color: #334155;
    padding: 0 0.5rem;
    cursor: pointer;
}

.vovo-hk-filter-checkbox-inline input[type="checkbox"] {
    margin: 0;
    cursor: pointer;
}

.vovo-hk-filter-group {
    border-top: 1px solid #f1f5f9;
    padding-top: 0.5rem;
    display: flex;
    flex-direction: column;
    gap: 0.35rem;
}

.vovo-hk-filter-group-label {
    font-weight: 600;
    font-size: 0.8rem;
    color: #64748b;
}

.vovo-hk-filter-search-btn {
    padding: 0.55rem 1.25rem;
}

/* ==========================================================================
   v0.9.33 — Two Card + Map AJAX filter bar additions (city tag-input,
   × clear buttons, Reset button, larger Search button, sort-in-toolbar,
   results loading state). Every rule below is scoped to markup that
   only exists when $vovo_hk_ajax_mode is true in
   partials/filter-bar-homekey.php, i.e. the Two Card + Map layout only
   — none of this reaches the Classic Split layout's filter bar.
   ========================================================================== */

/* City tag-input: replaces the plain text "Location" field. Chips render
   inline inside the same bordered box the text input used to occupy, so
   the overall filter-bar row height and alignment don't shift compared
   to the Classic Split field it's standing in for. */
.vovo-hk-city-field {
    position: relative !important;
    flex-wrap: wrap;
    align-items: center;
}

.vovo-hk-city-chips {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 0.3rem;
    flex: 1;
    min-width: 0;
}

.vovo-hk-city-chip {
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    background: #eff6ff;
    border: 1px solid #93c5fd;
    color: #1d4ed8;
    border-radius: 999px;
    padding: 0.2rem 0.35rem 0.2rem 0.65rem;
    font-size: 0.8rem;
    white-space: nowrap;
}

/* v0.9.33: Tony's confirmed design — each selected city chip carries
   its own × in the corner to remove just that one city, rather than a
   single "clear all cities" control.
   v0.9.35: every property below made !important. Hello Elementor's
   global button reset (border/background/padding on bare `button`
   elements) was overriding these un-!important declarations on the
   live site, turning what should be a small filled circle into a
   larger bordered rectangle that visually reads as an empty box —
   exactly the pattern this project has hit before with every other
   custom button (nav arrows, basket flyout, etc.) and always had to
   resolve the same way: escalate to !important. */
.vovo-hk-city-chip-remove {
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    width: 1.1rem !important;
    height: 1.1rem !important;
    min-width: 1.1rem !important;
    border-radius: 999px !important;
    border: none !important;
    background: rgba(29, 78, 216, 0.15) !important;
    color: #1d4ed8 !important;
    font-size: 0.85rem !important;
    line-height: 1 !important;
    cursor: pointer !important;
    padding: 0 !important;
    margin: 0 !important;
}

.vovo-hk-city-chip-remove:hover {
    background: rgba(29, 78, 216, 0.3) !important;
}

.vovo-hk-city-input {
    border: none !important;
    outline: none;
    font-size: 0.9rem;
    flex: 1 1 100px;
    min-width: 80px;
    padding: 0.2rem 0 !important;
}

.vovo-hk-city-suggestions {
    position: absolute !important;
    top: calc(100% + 0.35rem);
    left: 0;
    /* v0.9.36: was `right: 0` (stretched to match the full width of
       the city field container, which can be quite wide) — Tony asked
       for a narrower popup. Fixed width with a sane min/max instead of
       tracking the parent's full width. */
    width: 260px;
    min-width: 220px;
    max-width: 320px;
    background: #fff !important;
    border: 1px solid #e2e8f0;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
    padding: 0.5rem;
    z-index: 200 !important;
    max-height: 260px;
    overflow-y: auto;
}

.vovo-hk-city-suggestion {
    display: flex !important;
    align-items: center !important;
    justify-content: space-between !important;
    gap: 0.5rem;
    padding: 0.4rem 0.5rem;
    font-size: 0.85rem;
    cursor: pointer;
    border-radius: 6px;
}

.vovo-hk-city-suggestion:hover {
    background: #f8fafc;
}

/* × clear buttons — every clearable field (MLS, Price, Property Type,
   Beds, Baths, Keyword) gets one of these, hidden by default via the
   `hidden` attribute (server-rendered based on whether the field
   already has a value, then kept in sync by JS afterward). */
.vovo-hk-filter-field-clearable {
    position: relative;
    display: flex;
    align-items: center;
}

.vovo-hk-filter-field-clearable select,
.vovo-hk-filter-field-clearable input {
    padding-right: 1.8rem !important;
}

/* v0.9.35: every property below made !important, same reasoning as
   .vovo-hk-city-chip-remove above — Hello Elementor's global button
   reset was overriding the un-!important version, and on the live
   site every × button rendered as an oversized bordered rectangle
   with no visible glyph rather than the small filled circle intended.

   Separately: the `hidden` attribute stopped working at all for these
   buttons. The browser's own built-in `[hidden] { display: none }`
   rule and a plain `.vovo-hk-clear-btn { display: inline-flex }` rule
   have IDENTICAL specificity (0,1,0 — one attribute selector, one
   class selector) — when two rules tie on specificity, the one later
   in the cascade wins, and this plugin's stylesheet always loads after
   the browser's UA stylesheet. So `display: inline-flex` was silently
   beating `display: none` on every hidden clear button, making them
   show up even on empty fields that should have no × to click. Fixed
   by adding an explicit `[hidden]` override below with !important,
   which now wins outright regardless of cascade order. */
.vovo-hk-clear-btn {
    position: absolute;
    right: 0.4rem;
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex !important;
    align-items: center !important;
    justify-content: center !important;
    width: 1.2rem !important;
    height: 1.2rem !important;
    min-width: 1.2rem !important;
    border-radius: 999px !important;
    border: none !important;
    background: #e2e8f0 !important;
    color: #475569 !important;
    font-size: 0.85rem !important;
    line-height: 1 !important;
    cursor: pointer !important;
    padding: 0 !important;
    margin: 0 !important;
}

.vovo-hk-clear-btn[hidden] {
    display: none !important;
}

.vovo-hk-clear-btn:hover {
    background: #cbd5e1 !important;
}

/* The Price field's × sits inline in the closed toggle button itself
   (there's no single "Price" input to anchor an absolutely-positioned
   × against — it's a min/max pair behind a dropdown), right after the
   count badge. */
.vovo-hk-clear-btn-inline {
    position: static;
    transform: none;
    margin-left: 0.3rem;
    width: 1.1rem !important;
    height: 1.1rem !important;
    min-width: 1.1rem !important;
}

/* Reset button — deliberately styled as a lighter secondary action so
   Search (filled, larger — see below) stays the visually dominant
   button on the bar. */
.vovo-hk-btn-secondary {
    /* v0.9.37: display/border-radius/font-size/font-weight/cursor/
       text-decoration added — this class previously only got its
       padding from .vovo-hk-filter-reset-btn and relied on being a
       <button> for everything else. Now that Classic Split's Reset
       renders as a plain <a> link (see filter-bar-homekey.php), it
       needs its own complete button-like appearance rather than
       inheriting a button element's natural defaults. */
    display: inline-block !important;
    background: #fff !important;
    border: 1px solid #cbd5e1 !important;
    color: #334155 !important;
    border-radius: 6px;
    font-size: 0.85rem;
    font-weight: 600;
    text-align: center;
    text-decoration: none !important;
    cursor: pointer;
    white-space: nowrap;
}

.vovo-hk-btn-secondary:hover {
    background: #f8fafc !important;
}

.vovo-hk-filter-reset-btn {
    padding: 0.55rem 1.1rem;
}

/* v0.9.33: Search button made larger/more prominent per Tony's
   request. `.vovo-hk-btn-solid` already provides the filled primary
   blue background — this modifier only bumps size, it never touches
   the Classic Split layout's Search button since that button never
   gets the `--lg` class (see the non-ajax branch of
   filter-bar-homekey.php, which renders the button without it). */
.vovo-hk-filter-search-btn--lg {
    padding: 0.75rem 1.75rem;
    font-size: 0.95rem;
    font-weight: 600;
}

/* Sort dropdown, now living in the toolbar (two-card-map-page.php)
   instead of inline in the filter bar — same field look as the other
   filter-bar selects so it doesn't read as a different control. */
.vovo-hk-sort-field select {
    padding: 0.45rem 0.6rem;
    border: 1px solid #cbd5e1;
    border-radius: 6px;
    font-size: 0.85rem;
    background: #fff;
}

/* Brief dim-and-wait cue while an AJAX refresh is in flight, so a slow
   connection doesn't look like a broken click. */
.vovo-hk-results-loading {
    opacity: 0.5;
    pointer-events: none;
    transition: opacity 0.15s ease;
}


/* ==========================================================================
   Detail page — "Professional — Full Sidebar" (.vovo-fsb-*, v0.9.15
   rewrite). Single persistent 2-column CSS Grid running the full page
   height: left column holds gallery + all property info (title, specs,
   About, Details, Features, courtesy); right column holds all sidebar
   widgets. Both columns share the same grid track top-to-bottom, so
   the sidebar continues down the right the entire way regardless of
   which column is taller. Same reasoning for CSS Grid over flex-wrap
   as elsewhere in this file — a grid with fixed track sizes doesn't
   silently stack when the container is a bit narrower than expected.

   Every class name here is prefixed .vovo-fsb-* and is unique to the
   Full Sidebar layout — no reuse of .vovo-dps-* (owned by Wide Image
   below), .vovo-dpw-*, .vovo-ggrid-*, or .vovo-gstack-*. The
   .vovo-dpw-summary-row still uses .vovo-dps-address / .vovo-dps-city
   as an intentional cross-share; the Full Sidebar layout uses its own
   .vovo-fsb-address / .vovo-fsb-city to stay fully independent.

   The previous structure (a two-block .vovo-dps-top-row above +
   .vovo-dps-main-full below) has been retired — those class rules are
   removed. .vovo-dps-address / .vovo-dps-city rules stay because Wide
   Image (detail-homekey-sidebar.php) still uses them.
   ========================================================================== */
.vovo-fsb-grid {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 1.5rem;
    align-items: start;
    margin-top: 1.25rem;
}

.vovo-fsb-main-col {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    /* Grid items default to min-width: auto which lets wide content
       (long address strings, wide tables) push the column wider than
       the fr track — min-width: 0 forces the track to hold its size. */
    min-width: 0;
}

/* v0.9.16: defensive gallery containment. In v0.9.15 the address /
   specs row / virtual tour button appeared to be missing on the Full
   Sidebar template — traced to the shift-right regression pushing them
   into the `overflow-x: clip` area. Now that the shift is fixed, this
   rule guarantees the gallery block cannot expand its own width beyond
   the main column no matter what the inner .vovo-gstack-row does, so
   the flex items that come after it (address h1, city p, specs-row,
   About block, ...) stay in their expected vertical positions.

   v0.9.18: retired `overflow: hidden` and the fixed 460px `min-height`.
   The overflow rule was clipping the Virtual Tour link that sits
   below the .vovo-gstack-row inside gallery-stacked.php — the link
   is a legitimate part of the gallery block and needs to be visible
   (issue #9). The fixed min-height was also causing the visible clip
   of the main image + 3rd thumbnail (issue #8) on containers where
   the inner .vovo-gstack-row rendered taller than 460px: the wrapper
   held at 460px and overflow:hidden hid the rest. Now the wrapper
   sizes to content — the inner .vovo-gstack-row still asserts its
   own min-height for empty-photos fallback (see gallery block below),
   and `max-width: 100%` on the wrapper is still enough width
   containment on its own without the overflow rule. */
.vovo-fsb-gallery {
    width: 100%;
    max-width: 100%;
}

.vovo-fsb-sidebar-col {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
    min-width: 0;
}

.vovo-fsb-address {
    margin: 0.5rem 0 0.2rem;
}

.vovo-fsb-city {
    color: #64748b;
    margin: 0 0 0.6rem;
}

@media (max-width: 900px) {
    .vovo-fsb-grid {
        grid-template-columns: 1fr;
    }
}

/* .vovo-dps-address / .vovo-dps-city retained (used by Wide Image). */
.vovo-dps-address {
    margin: 1.5rem 0 0.2rem;
}

.vovo-dps-city {
    color: #64748b;
    margin: 0 0 0.6rem;
}

/* ==========================================================================
   Detail page — "Professional — Wide Image" (.vovo-dpw-*, grid gallery)
   The gallery gets its own full-width row with nothing beside it. The
   sidebar sits beside the property info section further down instead —
   the opposite pairing from the Full Sidebar layout above. Same CSS
   Grid reasoning applies to the content row.
   ========================================================================== */
.vovo-dpw-gallery-full {
    margin-top: 1.25rem;
}

.vovo-dpw-summary-row {
    margin: 1.5rem 0;
}

.vovo-dpw-content-row {
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: 2rem;
    align-items: start;
}

.vovo-dpw-sidebar-col {
    display: flex;
    flex-direction: column;
    gap: 1.25rem;
}

@media (max-width: 900px) {
    .vovo-dpw-content-row {
        grid-template-columns: 1fr;
    }
}

/* ==========================================================================
   Shared between both layouts above (widget cards, save-actions reset)
   ========================================================================== */
.vovo-hkp-widget-card {
    border: 1px solid #e2e8f0;
    border-radius: 10px;
    padding: 1.25rem;
}

.vovo-hkwi-save-actions-wrap .vovo-save-actions {
    position: static !important;
    display: flex;
    gap: 0.5rem;
    margin: 0.75rem 0;
}

/* ==========================================================================
   Detail sidebar CTA buttons — Request a Showing (solid) + Add to
   Basket (outline, converts to green "In Basket ✓" when active).
   The .vovo-hkd-* classes are used from PHP in class-vovo-detail-sidebar
   but had no CSS rules of their own before v0.9.18 — the buttons picked
   up ambient theme styling, which meant "In Basket" also looked like
   "Add to Basket" (no visual distinction between the two states).
   These rules give the buttons a consistent look inside the widget
   card AND paint the in-basket variant green regardless of theme.

   The in-basket color is intentionally hard-coded green here for
   v0.9.18 to match Tony's explicit "show that button in green" spec.
   v0.9.19 (#14) will unify this with the list-view In Basket button
   via a shared admin color setting, at which point this hard-coded
   value gets promoted into a CSS variable driven by the admin field.
   ========================================================================== */
.vovo-hkd-cta-btn {
    /* v0.9.22: display + width + margin-top + gap escalated to
       !important. v0.9.19 through v0.9.21 kept bumping the numeric
       values (0.4→0.6→0.75rem gap, 0.75→1→1.25rem margin-top) but
       Tony's live-site screenshot showed the buttons STILL rendering
       side-by-side and compact — the theme was overriding
       `display: flex; width: 100%` with a higher-specificity rule
       like `.vovo-hkp-widget-card button { display: inline-block;
       width: auto }`, flattening the block layout regardless of
       what values we set on the plain class selector. Same escape
       hatch pattern as `.vovo-gstack-thumb-btn` in v0.9.20 —
       narrowly scoped to this single class, documented as
       intentional, not a general pattern to reach for. */
    display: flex !important;
    align-items: center;
    justify-content: center;
    gap: 0.75rem !important;
    width: 100% !important;
    padding: 0.75rem 1rem;
    margin-top: 1.25rem !important;
    border-radius: 8px;
    font-size: 0.95rem;
    font-weight: 600;
    text-decoration: none;
    cursor: pointer;
    transition: background 0.15s, color 0.15s, border-color 0.15s;
    box-sizing: border-box;
}

.vovo-hkd-btn-solid {
    background: #0f172a;
    color: #ffffff;
    border: 1px solid #0f172a;
}

.vovo-hkd-btn-solid:hover {
    background: #1e293b;
    border-color: #1e293b;
    color: #ffffff;
}

.vovo-hkd-btn-outline {
    background: #ffffff;
    color: #0f172a;
    border: 1px solid #cbd5e1;
}

.vovo-hkd-btn-outline:hover {
    background: #f8fafc;
    border-color: #94a3b8;
}

.vovo-hkd-basket-btn.vovo-in-basket {
    /* v0.9.22: was hard-coded #10b981. Now reads from the same
       admin setting (`vovo_basket_icon_active_bg_color`) that the
       list-view icon uses, so the button, the icon, and the list-
       view In Basket button all render identical color across
       surfaces. Fallback `#10b981` matches the setting's new
       default (green), so if `emit_width_custom_properties` fails
       to run the button still lands on the same green shade. */
    background: var(--vovo-basket-active-bg, #10b981);
    color: #ffffff;
    border-color: var(--vovo-basket-active-bg, #10b981);
}

.vovo-hkd-basket-btn.vovo-in-basket:hover {
    /* Reuses the same variable but relies on CSS `filter: brightness()`
       to produce a slightly darker hover state without needing a
       second admin field — cheaper than emitting a computed shade
       server-side, and works regardless of what color the admin
       picks (dark shades filter to slightly darker, light shades
       filter to still-lighter-but-tinted). */
    background: var(--vovo-basket-active-bg, #10b981);
    border-color: var(--vovo-basket-active-bg, #10b981);
    color: #ffffff;
    filter: brightness(0.9);
}

.vovo-hkd-basket-btn-icon {
    flex-shrink: 0;
    /* currentColor on the SVG picks up the button's text color, so the
       heart glyph re-tints from dark slate → white when the button
       flips to the green in-basket state without a separate rule. */
}

/* v0.9.19: Save (favorite) CTA button variant.
   Same structural rules as .vovo-hkd-basket-btn — inline SVG icon
   next to a label span, `.active` toggled by syncFavoriteButtons in
   vovo-favorites-basket.js. Icon fill is CSS-driven so the JS never
   has to touch the SVG's paint state:
     • Default (not favorited): outline heart (stroke visible, fill
       transparent). Uses the button's currentColor so it inherits the
       dark slate text color.
     • Active (favorited): solid red heart matching the standard
       favorite-active color (#e11d48 = same value the small heart
       icon in save-actions.php uses via vovo_favorite_icon_active_
       bg_color's default). Both stroke and fill flip to that color
       so the heart reads as a filled shape.
   Cross-CTA visual consistency: the favorite button sits in the same
   .vovo-hkd-cta-btn base rule as the basket button, so button size,
   padding, border radius, and hover chrome match without duplicating
   the layout rules. */
.vovo-hkd-favorite-btn-icon {
    flex-shrink: 0;
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linejoin: round;
    stroke-linecap: round;
}

.vovo-hkd-favorite-btn.active {
    color: #e11d48;
    border-color: #e11d48;
}

.vovo-hkd-favorite-btn.active .vovo-hkd-favorite-btn-icon {
    fill: currentColor;
    stroke: currentColor;
}

.vovo-hkd-favorite-btn.active:hover {
    background: #fef2f2;
    color: #e11d48;
    border-color: #e11d48;
}

/* ==========================================================================
   Gallery — grid variant (Professional — Full Sidebar)
   Fully isolated .vovo-ggrid-* class names — shares nothing with the
   classic gallery's CSS (see gallery-grid.php's docblock for why that
   matters: a shared .vovo-gallery-main class previously pulled in an
   unrelated aspect-ratio:16/9 rule that fought with this layout's own
   height inside a CSS grid, a confirmed real cause of broken rendering).
   Only the lightbox classes below are intentionally shared (a genuine
   page-level singleton modal, position:fixed, independent of the
   calling gallery).
   ========================================================================== */
.vovo-ggrid-row {
    display: grid;
    grid-template-columns: 2fr 1fr;
    gap: 0.75rem;
    /* v0.9.28: `!important` escape hatch. Tony's live site had one
       listing rendering the address heading cleanly and another
       hiding the heading behind the gallery — same template, same
       CSS. Root cause: theme's `img { height: auto !important }`
       overrode our `.vovo-ggrid-main img { height: 100% }`, letting
       portrait/square listing photos render at their natural
       aspect ratio and grow the row past 460px, which pushed the
       address heading off the visible area. Locking the row height
       + the child img sizing with `!important` (see the img rule
       below) reliably beats the theme override.
       v0.9.29: `overflow: hidden` added as belt-and-suspenders.
       Even with the height locked, `object-fit: cover` should keep
       the child img within the box — but adding overflow: hidden
       here means ANY child that somehow escapes (a rogue theme
       pseudo-element, a mis-sized DOM badge, etc.) is clipped by
       the container rather than leaking into the adjacent content
       zones (Virtual Tour button, print icon, address heading). */
    height: 380px !important;
    overflow: hidden;
}

.vovo-ggrid-main {
    position: relative;
    border-radius: 10px;
    overflow: hidden;
    height: 100%;
    /* v0.9.32: dark matte from v0.9.31 removed. The `object-fit:
       contain` approach produced a visible bug on Tony's live site
       (bars on top + apparent bottom crop simultaneously on landscape
       W13582456) — likely the interaction between fixed-height parent,
       `max-height: 100% !important`, `object-position: center 35%`,
       and the theme's `img { height: auto !important }` cascade
       leaving the img element sized inconsistently with what
       object-fit was computing against. Reverting the child img to
       `object-fit: cover` with default center-center object-position
       makes the frame fill edge-to-edge on any source aspect ratio.
       Tony explicitly accepted minor edge cropping in exchange for
       clean, bar-free framing. The full-uncropped view is still
       reachable via "View all N photos" (lightbox still uses
       object-fit: contain — see vovo-detail.css). */
}

.vovo-ggrid-main img {
    /* v0.9.28: sizing !important (see .vovo-ggrid-row above for the
       root-cause reasoning).
       v0.9.32: reverted v0.9.31's `contain` + `object-position:
       center 35%` back to plain `object-fit: cover` with default
       center-center positioning. The v0.9.31 approach produced a
       reproducible bug on the live site: landscape source
       W13582456 rendered with black bars on top AND apparent
       cropping on the bottom simultaneously — impossible under a
       pure contain implementation, so the interaction of
       `max-height: 100% !important`, `object-position: center 35%`,
       and the theme's `img { height: auto !important }` cascade
       was leaving the img element sized in a way that fought the
       object-fit computation. Plain cover with center-center is
       predictable across every browser and every theme override
       we've hit: image always fills the container edge-to-edge,
       excess dimension gets clipped evenly. Tony explicitly
       accepted minor edge cropping in exchange for clean framing.
       For customers who want the full uncropped photo, the
       "View all N photos" lightbox continues to use `object-fit:
       contain` (see .vovo-lightbox-main img in vovo-detail.css)
       so one click gets them the whole image. */
    width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    object-fit: cover !important;
    object-position: center center !important;
    display: block;
}

.vovo-ggrid-dom-badge {
    position: absolute;
    top: 1rem;
    left: 1rem;
    background: rgba(15, 23, 42, 0.85);
    color: #fff;
    font-size: 0.75rem;
    padding: 0.3rem 0.7rem;
    border-radius: 5px;
}

.vovo-ggrid-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    /* v0.9.22: was rgba(15,23,42,0.55) — semi-transparent dark
       washed out on bright/white images (kitchen photos, sunlit
       exteriors) making the arrows blend in. Now solid dark 0.9
       opacity + a subtle white outer ring so the button reads
       as a distinct control on any image color, plus a drop-shadow
       so it separates cleanly from busy backgrounds.
       v0.9.30: flipped to white bg + dark arrow. On the 88 Olde
       Town Road listing (dark sunset-sky main photo), dark-on-dark
       blended into invisibility. White pill + dark arrow +
       stronger drop-shadow reads clearly on ANY image color \u2014 the
       pattern most professional real-estate sites use. Border
       removed (white pill on any bg already has enough visual
       separation via the shadow).
       v0.9.31: `!important` on background + color + visibility +
       opacity + z-index. Tony reported the arrows STILL invisible
       after the v0.9.30 flip \u2014 either browser cache or a theme
       rule was overriding. `!important` on a class selector beats
       any wildcard/tag theme rule without matching specificity.
       z-index: 3 ensures the button stacks above the image + any
       overlays (DOM badge, view-all button).
       v0.9.37: width/height/padding/margin/box-sizing also made
       !important, preemptively — same fix just applied to Full
       Sidebar's equivalent .vovo-gstack-nav after Tony reported those
       arrows sitting low instead of vertically centered. Same
       vulnerability here (a theme button-padding reset can silently
       change this button's actual box shape since these properties
       never had !important), just not yet reported on this layout. */
    background: rgba(255, 255, 255, 0.95) !important;
    color: #0f172a !important;
    border: none !important;
    width: 40px !important;
    height: 40px !important;
    padding: 0 !important;
    margin: 0 !important;
    box-sizing: border-box !important;
    border-radius: 50%;
    font-size: 1.4rem;
    font-weight: bold;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
    opacity: 1 !important;
    visibility: visible !important;
    z-index: 3 !important;
}

.vovo-ggrid-prev { left: 1rem; }
.vovo-ggrid-next { right: 1rem; }

.vovo-ggrid-viewall {
    position: absolute;
    left: 1rem;
    bottom: 1rem;
    background: rgba(15, 23, 42, 0.85) !important;
    color: #fff !important;
    border: none !important;
    border-radius: 6px;
    padding: 0.5rem 0.9rem;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}

.vovo-ggrid-viewall:hover {
    background: rgba(15, 23, 42, 0.95) !important;
}

.vovo-ggrid-thumbs {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-template-rows: 1fr 1fr;
    gap: 0.75rem;
    height: 100%;
}

.vovo-ggrid-thumb {
    /* v0.9.32: reverted v0.9.31's `contain` + dark-matte back to
       plain `cover` for the same reason as .vovo-ggrid-main img
       above — the v0.9.31 approach produced inconsistent rendering
       on Tony's live theme. Cover keeps each thumb tile filled
       edge-to-edge; minor edge cropping is acceptable. See the
       .vovo-ggrid-main img rule for the full rationale. */
    width: 100% !important;
    height: 100% !important;
    object-fit: cover !important;
    object-position: center center !important;
    border-radius: 8px;
    cursor: pointer;
    display: block;
}

.vovo-ggrid-virtualtour {
    display: inline-block;
    margin-top: 0.6rem;
    padding: 0.4rem 0.9rem;
    border: 1px solid #2563eb;
    color: #2563eb;
    border-radius: 6px;
    font-size: 0.85rem;
    text-decoration: none;
}

@media (max-width: 900px) {
    .vovo-ggrid-row {
        grid-template-columns: 1fr;
        height: auto;
    }

    .vovo-ggrid-main {
        height: 300px;
    }

    .vovo-ggrid-thumbs {
        height: 160px;
    }
}

/* ==========================================================================
   Gallery — stacked variant (Professional — Wide Image)
   Same full isolation as .vovo-ggrid-* above, under .vovo-gstack-*.
   ========================================================================== */
.vovo-gstack-row {
    display: grid;
    /* v0.9.21: was `2.4fr 1fr` (thumbs column ~29% of row width).
       Nudged to `2.2fr 1fr` (~31%) so the thumb photos have a bit
       more horizontal room to breathe. Still gives the main photo
       clear visual priority (that's Full Sidebar's whole point) but
       moves closer to Wide Image's `2fr 1fr` proportional feel. */
    grid-template-columns: 2.2fr 1fr;
    gap: 0.75rem;
    /* v0.9.29: reverted the v0.9.18 `min-height` back to a fixed
       `height` with `!important`. Rationale: `min-height` lets the
       row grow when a listing has a tall/portrait main image, which
       was causing v0.9.28's Virtual Tour + print icon to overlap the
       gallery on those listings (Tony's containment complaint). A
       fixed height with `overflow: hidden` keeps the frame stable —
       tall images get cropped by `object-fit: cover` on the child
       img (see rule below), which is the standard real-estate site
       behavior. Customers who want to see the full uncropped image
       still have the "View all N photos" lightbox.
       v0.9.37: 380px → 480px. Tony: "make image section more in
       height to fit images more better, currently its cutting off
       from bottom." 380px was cropping typical landscape real-estate
       photos more aggressively than intended at this column's ~2.2fr
       width; 480px gives noticeably more vertical room while staying
       a fixed, predictable frame (still object-fit: cover, still has
       the full-photo lightbox for anyone who wants the uncropped
       view). */
    height: 480px !important;
    overflow: hidden;
}

.vovo-gstack-main {
    position: relative;
    border-radius: 10px;
    overflow: hidden;
    height: 100%;
    /* v0.9.32: v0.9.31 dark matte removed, same rationale as
       .vovo-ggrid-main above. See .vovo-ggrid-main img for full
       explanation. */
}

.vovo-gstack-main img {
    /* v0.9.32: reverted to plain `cover` + center-center, same fix
       as .vovo-ggrid-main img — see that rule for the full
       rationale. Live-site bug (bars + crop combo) was reproducing
       on Full Sidebar just as reliably as on Wide Image because
       both use the same fixed-height parent + object-fit:contain +
       max-height:100% pattern. Plain cover is stable. */
    width: 100% !important;
    height: 100% !important;
    max-height: 100% !important;
    object-fit: cover !important;
    object-position: center center !important;
    display: block;
}

.vovo-gstack-dom-badge {
    position: absolute;
    top: 1rem;
    left: 1rem;
    background: rgba(15, 23, 42, 0.85);
    color: #fff;
    font-size: 0.75rem;
    padding: 0.3rem 0.7rem;
    border-radius: 5px;
}

.vovo-gstack-nav {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    /* v0.9.30: same white-on-dark flip as .vovo-ggrid-nav (see that
       rule's docblock for reasoning). Full Sidebar and Wide Image
       keep separate selectors per class-isolation project rule.
       v0.9.31: same !important bulletproofing as .vovo-ggrid-nav.
       v0.9.37: width/height/padding/margin/box-sizing also made
       !important — Tony reported these arrows sitting near the
       bottom of the gallery instead of vertically centered. The
       `top: 50%; transform: translateY(-50%)` math here was always
       correct, but this rule never had the same padding/box-sizing
       bulletproofing already proven necessary on
       .vovo-gstack-thumb-btn (v0.9.21) — a theme button reset adding
       its own padding/height could silently change this button's
       actual box shape out from under the un-!important width/height,
       shifting where its visual center actually lands even though
       the centering math itself was never wrong. */
    background: rgba(255, 255, 255, 0.95) !important;
    color: #0f172a !important;
    border: none !important;
    width: 40px !important;
    height: 40px !important;
    padding: 0 !important;
    margin: 0 !important;
    box-sizing: border-box !important;
    border-radius: 50%;
    font-size: 1.4rem;
    font-weight: bold;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
    opacity: 1 !important;
    visibility: visible !important;
    z-index: 3 !important;
}

.vovo-gstack-prev { left: 1rem; }
.vovo-gstack-next { right: 1rem; }

.vovo-gstack-viewall {
    position: absolute;
    left: 1rem;
    bottom: 1rem;
    background: rgba(15, 23, 42, 0.85) !important;
    color: #fff !important;
    border: none !important;
    border-radius: 6px;
    padding: 0.5rem 0.9rem;
    font-size: 0.85rem;
    font-weight: 600;
    cursor: pointer;
}

.vovo-gstack-viewall:hover {
    background: rgba(15, 23, 42, 0.95) !important;
}

.vovo-gstack-virtualtour {
    display: inline-block;
    margin-top: 0.6rem;
    padding: 0.4rem 0.9rem;
    border: 1px solid #2563eb;
    color: #2563eb;
    border-radius: 6px;
    font-size: 0.85rem;
    text-decoration: none;
}

.vovo-gstack-thumbs {
    display: flex;
    flex-direction: column;
    /* v0.9.21: gap reduced 0.75→0.4rem. At 0.75rem the three stacked
       thumbs left significant vertical whitespace between them; at
       0.4rem each thumb now claims ~150px of the 460px column
       height (was ~145px) while still reading as distinct rectangles. */
    gap: 0.4rem;
    height: 100%;
}

.vovo-gstack-thumb-btn {
    position: relative;
    flex: 1;
    /* v0.9.19: aggressively strip all browser/theme button chrome so
       the thumbnail reads as a plain image click target — visually
       identical to .vovo-ggrid-thumb on Wide Image (which is a plain
       <img>, no button chrome at all). Themes commonly add hover
       backgrounds, borders, and box-shadows to any <button>, and
       matching theme selectors sometimes use !important. Explicit
       overrides on the interaction pseudo-states below defeat those
       so the thumbnail stays clean.

       Semantically we KEEP the <button> element (gallery-stacked.php
       renders <button> rather than <img> with onclick) because the
       action is programmatic image swap — button is the correct
       role for accessibility. Only the visual chrome comes off.

       v0.9.21: `padding`, `margin`, `box-sizing` promoted to
       `!important`. v0.9.20's paint-property escape hatch didn't
       cover box-model — a leaked theme rule like
         `.entry-content button { padding: 0.5rem }`
       was leaving the child <img> shrunk inside a padded box, which
       is what Tony saw as "empty spaces around the thumbnails".
       box-sizing: border-box makes any residual theme border count
       toward the button's own dimensions rather than expanding
       past them. */
    -webkit-appearance: none;
    appearance: none;
    padding: 0 !important;
    margin: 0 !important;
    box-sizing: border-box;
    border: none;
    border-radius: 8px;
    overflow: hidden;
    cursor: pointer;
    background: none;
    box-shadow: none;
}

.vovo-gstack-thumb-btn:hover,
.vovo-gstack-thumb-btn:focus,
.vovo-gstack-thumb-btn:active {
    /* v0.9.20: escalated to `!important` on every property a theme
       might use to paint hover chrome. The v0.9.19 attempt used plain
       overrides and lost the specificity contest against theme rules
       like `.elementor-widget button:hover` (higher selector chain)
       or `button:hover { background-color: ... !important }` (which
       nothing but another `!important` can beat). Rather than try to
       out-specificity every possible theme, `!important` is the
       intentional escape hatch here — narrowly scoped to this single
       thumbnail selector, not a global pattern. Covers every paint
       property a theme might use so no single leaked declaration
       re-adds visible chrome.

       v0.9.22: added `transition: none !important` too. Themes
       commonly set `transition: all 0.2s` on all buttons, and when
       our `!important` clears flip values (background, transform,
       etc.) on hover, that transition animates the change — which
       reads visually as a shake/jitter on hover, and worse it can
       propagate up if the parent grid re-flows for a frame. Killing
       the transition entirely on interaction states makes hover
       instant and stable. */
    background: none !important;
    background-color: transparent !important;
    background-image: none !important;
    border: 0 !important;
    box-shadow: none !important;
    outline: none !important;
    transform: none !important;
    transition: none !important;
}

/* Keyboard-only focus indicator so tab-navigation stays accessible
   without adding chrome for mouse users. `:focus-visible` matches
   only when the browser judges focus was reached by keyboard, not
   by click — which is exactly when the ring is useful. `!important`
   here too so the base clear above doesn't strip it back off. */
.vovo-gstack-thumb-btn:focus-visible {
    outline: 2px solid #2563eb !important;
    outline-offset: 2px !important;
}

.vovo-gstack-thumb-btn img {
    /* v0.9.21: !important on width/height so a leaked theme rule
       like `.entry-content img { max-width: 100% }` combined with
       an intrinsic image size smaller than the click target can't
       leave gray whitespace around the photo.
       v0.9.32: reverted v0.9.31's `contain` + dark-matte back to
       plain `cover` + center-center. Same live-site rendering bug
       as .vovo-ggrid-main img / .vovo-gstack-main img; same
       clean-cover fix. See .vovo-ggrid-main img for full rationale. */
    width: 100% !important;
    height: 100% !important;
    max-width: 100% !important;
    max-height: 100% !important;
    object-fit: cover !important;
    object-position: center center !important;
    display: block;
}

.vovo-gstack-more {
    position: absolute;
    inset: 0;
    background: rgba(15, 23, 42, 0.55);
    color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 1.4rem;
    font-weight: 700;
}

@media (max-width: 900px) {
    .vovo-gstack-row {
        grid-template-columns: 1fr;
        height: auto;
    }

    .vovo-gstack-main {
        height: 300px;
    }

    .vovo-gstack-thumbs {
        flex-direction: row;
        height: 120px;
    }
}
